I need help with the design of my AngularJS application.
I have a factory, which provides a resource object for me, to a restful web service.
product.factory("productResource", function ($resource) {
var resource = $resource("/fooo/rest/products/:id", {}, {
query: {
method: "GET",
isArray: true
}
});
return resource;
});
I have a service, which provides a method, to query all products from the resource factory.
product.service("productDao", function (productResource) {
this.getProductModel = function () {
var data = productResource.query(function () {
});
return data;
}
});
The controllers invoke the service, to get the data they need:
Controller 1
header.controller("header.selection.product", function ($scope, productDao) {
$scope.products = null;
productDao.getProductModel().$promise.then(function (result) {
$scope.products = result;
});
});
Controller 2,3,4...
They look nearly the same, but they're using the data for different purposes.
Now i don't know a correct way, to implement a "datasource", which is accessed from all controllers. I don't want to make a request for all the data, for each controller startup, it should only be made once.
How can i automatically change data of all other controllers, when a product gets updated in one controller, the service should do an update (not implemented yet), when the update was successful they "centralized data" should change, and all controller data with it.
I read about using rootScope for this purpose, but some people say, don't just create factories/services.
If you use $http with the cache option, only one of your controllers will make the "real" request, all the others will get the products from the cache.
$http.get(url, { cache: true })
Or you can emit the results from inside your factory and let the controllers listen that.
getProducts: ->
url = productsApiEndPoint
$http.get(url, { cache: true })
.then(
(data) ->
$rootScope.$emit('YourFactory.getProducts', data);
(error) ->
//handle error
)
I prefer the first approach. Its more clear.
You can use the mediator pattern and coordinate the data flow. Create a service that will be your mediator, emit from that service to your controllers when the data arrives. Emit from a controller to that service when the controller change the data and then the service will emit back to the rest of the controllers
On your factory emit the data to the mediator service:
product.factory("productResource", function ($resource) {
var resource = $resource("/fooo/rest/products/:id", {}, {
query: {
method: "GET",
isArray: true
}
});
resource.query(...)
.$promise.then(function(products) {
$rootScope.$emit('ProductsFetched', products);
});
});
On your mediator service listen and emit to the controllers.
product.service("productDao", function (productResource) {
$rootScope.$on('ProductsFetched', function(data){
$rootScope.$emit('ProductsChange', data);
});
$rootScope.$on('ProductsChangeFromControllers', function(data){
$rootScope.$emit('ProductsChange', data);
});
});
On your controllers listen and emit to the mediator service:
header.controller("header.selection.product", function ($scope, productDao) {
var products = null;
$rootScope.$on('ProductsChange',function(data){
products = data;
});
// do something with the products
$rootScope.$emit('ProductsChangeFromControllers', products);
});
Maybe there are some syntax mistakes, but I just wanted to give you an idea how to structure it.
Related
Hii I m using following code. I am reading a json file name is "users.json". If i read this file in controller through $http everything works fine. but i want to use the data that i read from file, again and again in different controller so i made a factory for this. but in factory when i read data from that json file through $http.get() and in return when i call that service method in my controller and it returns Object { $$state: Object }
app.factory('AboutFactory',['$http',function ($http) {
return {
getter: function () {
return $http({
method : 'GET',
url : '/home/penguin/Modeles/users.json',
cache : true
})
.then(function (response) {
return response.data
})
}
}
}])
Result of getter function is a promise. so you should use it like this:
AboutFactory.getter().then(function(res)
{
console.log(res);
});
That's because the $http service returns a promise as mentioned in the documentation:
The $http API is based on the deferred/promise APIs exposed by the $q
service. While for simple usage patterns this doesn't matter much, for
advanced usage it is important to familiarize yourself with these APIs
and the guarantees they provide.
You can think of a promise as if you give a top secret message to someone to deliver personally to a friend, then when delivered, report back to you with a message back from your friend.
You provide the message (the request object) to the person so that they can attempt to make the delivery of the message (send the request).
The attempted delivery has taken place (the request has been sent), it either:
a) was delivered successfully (successful response) or
b) your friend was not in so the letter could not be delivered (non success response).
You can then act depending on the response you get back
a) Message was delivered (it was a successful request) and you got a letter back (do something with the response) or
b) Message failed to get delivered (request wasn't successful), so you can maybe try again later or do something else as you don't have the information you requested
Here is an example of using the $http service with the $q service:
// app.js
(function() {
'use strict';
angular.module('app', []);
})();
// main.controller.js
(function() {
'use strict';
angular.module('app').controller('MainController', MainController);
MainController.$inject = ['AboutFactory'];
function MainController(AboutFactory) {
var vm = this;
AboutFactory.getter().then(function(data) {
// do something with your data
vm.data = data;
}, function(error) {
// give the user feedback on the error
});
}
})();
// about.service.js
(function() {
'use strict';
angular.module('app').factory('AboutFactory', AboutFactory);
AboutFactory.$inject = ['$http', '$q']
function AboutFactory($http, $q) {
var service = {
getter: getter
};
return service;
function getter() {
// perform some asynchronous operation, resolve or reject the promise when appropriate.
return $q(function(resolve, reject) {
$http({
method: 'GET',
url: 'https://httpbin.org/get',
cache: true
}).then(function(response) {
// successful status code
// resolve the data from the response
return resolve(response.data);
}, function(error) {
// error
// handle the error somehow
// reject with the error
return reject(error);
});
});
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<div ng-app="app" ng-controller="MainController as MainCtrl">
<pre>{{MainCtrl.data | json}}</pre>
</div>
Try this approach. It will work as per your expectation.
Read JSON file in controller through $http service as it is works fine.
For sharing the response data from one controller to another you can create a service and store the data into that service.
Service :
app.service('setGetData', function() {
var data = '';
getData: function() { return data; },
setData: function(requestData) { data = requestData; }
});
Controllers :
app.controller('myCtrl1', ['setGetData',function(setGetData) {
// To set the data from the one controller
var data = 'Hello World !!';
setGetData.setData(data);
}]);
app.controller('myCtrl2', ['setGetData',function(setGetData) {
// To get the data from the another controller
var res = setGetData.getData();
console.log(res); // Hello World !!
}]);
Here, we can see that myCtrl1 is used for setting the data and myCtrl2 is used for getting the data. So, we can share the data from one controller to another controller like this.
I was recomended to use Angular services in order to centralize many repetative functions that were store in my controller, so I am rewriting my code using services now.
It seemed simple at first but cant seem to find a good structure to fetch my ajax data (only once), then store it in my service for my controller to reuse the cached data when ever it needs it. At the moment I keep getting errors saying: TypeError: Cannot read property 'sayHello' of undefined.
I believe this is because theres is a delay to fetch my ajax data via my service before the controller loads. Im not quite certain how I can optimize this. Would anyone have a better stucture to this?
Service:
app.service('MyService', function ($http) {
this.sayHello = function () {
var deferred = $q.defer();
$http({
method: 'GET',
url: 'AJAX PATH',
headers: { "Accept": "application/json;odata=verbose;charset=utf-8"}
}).then(function(data){
var configurations = data;
var configurations.data_result_1 = configurations.data_result_1.split("\r\n");
var configurations.data_result_2 = configurations.data_result_2.split("\r\n");
deferred.resolve(configurations);
}
return deferred.promise;
};
this.sayHello(); //Run the function upon page laod.
});
Controller:
app.controller('AppController', function (MyService, $scope) {
$scope.configurations = null;
$scope.configurations = function() { MyService.sayHello() };
});
I recommend you to use another way to declare the service:
app.factory("MyService", function($http){
var configurations = {};
$http({
method: 'GET',
url: 'AJAX PATH',
headers: { "Accept": "application/json;odata=verbose;charset=utf-8"}
}).then(function(data){
configurations = data;
configurations.data_result_1 = configurations.data_result_1.split("\r\n");
configurations.data_result_2 = configurations.data_result_2.split("\r\n");
});
return {
getConfigurations: function(){
return configurations;
}
}
In your controller you can use a $watch, then when the configurations objects changes you take the information:
.controller("YourCtrl", function($scope,MyService){
var vm = this;
vm.configurations = {};
$scope.$watchCollection(function () { return MyService.getConfigurations()},function(newValue){
vm.configurations = newValue;
});
Totally agree with Bri4n about store configuration in the factory. Not agree about the controller because you said you don't want to watch, but only load data once.
But you $http already return a promise so as Brian said this is nice (just $q is useless here so you can delete it from injection). And I just wrapped http call in function, and the exposed function just check if configurations are already loaded. If yes, just return configurations else load it and then return it.
app.factory("MyService", function($http,$q){
var configurations = {};
function loadConfig(){
$http({
method: 'GET',
url: 'AJAX PATH',
headers: { "Accept": "application/json;odata=verbose;charset=utf-8"}
}).then(function(data){
configurations = data;
configurations.data_result_1 = configurations.data_result_1.split("\r\n");
configurations.data_result_2 = configurations.data_result_2.split("\r\n");
});
}
return {
getConfigurations: function(){
If( !!configurations ){
return configurations;
}
//Else loadConfig.then return configurations
}
}
In your controller you can just get config without need to know if it is already loaded.
.controller("YourCtrl", function(MyService){
var vm = this;
// If configurations already loaded return config, else load configurations and return configurations.
vm.configurations = MyService.getConfigurations();
I write on my phone so my code is not perfect I can't write properly.
OK, on second thought, it looks like you are not using the dependency array notation properly. Change your code to:
app.service('MyService', ['$http', function ($http) {
// do your stuff here
}]);
and for the controller:
app.controller('AppController', ['MyService', '$scope', function(MyService, $scope) {
// do your stuff here
}]);
Im writing my first app with Angular and now faced up with the problem... I have address for POST request with authentication token. Something like:
http://example.com/orders?authentication_token=123456
So I need to make ng-submit or ng-click that send that request and get a bunch of items and show them on the page...
Also, I have a body for them:
{
"order": {
"seller_id":84,
"price":123,
"delivary_date":"12-12-2025",
}
}
So, what the best way to do that?
So you will have to make one angular service which would communicate with server and fetch the data and one angular controller which will interact with service to get the data and display over the UI.
Lets say service name is MyService:
app.service('MyService', function($http) {
var params = {}; // some parameters
this.getData = function(successCallback, failureCallback) {
$http.post("URL", params).then(function(data) {
successCallback(data);
}, function(data, status) {
failureCallback(data, status);
});
}
});
Controller name is MyCntrl:
app.controller('MyCntrl', function($scope, MyService) {
function successCallback(data) {
$scope.itemList = data;
}
function failureCallback(data, status) {
$scope.itemList = {};
}
$scope.handleClick = function() {
MyService.getData(successCallback, failureCallback);
}
});
I believe it would help you to resolve your requirement!!!
Assume you have a orderCtrl. ng-click or ng-submit is based on your app requirement. Call the function someFunction() that triggers $http post and you can handle the success and failure response.
app.controller('orderCtrl', function($scope, $http) {
$scope.someFunction = function(){
var data = {}; // prepare your data here.
$http({
method : "POST",
url : "specify your url here",
data : data
}).then(function mySucces(response) {
var response = response.data;
// handle your success here
}, function myError(response) {
// handle the failure here
});
});
});
Note :
If you are using a form and you want to trigger this function after user filling all the information, then use ng-submit. If it is independent then use ng-click.
I'm saying again, it's all depends on what you are doing.
I have an ngResource object like this:
[...].factory('Event', ['$resource', function ($resource) {
return $resource('/events/:id', {id: '#id'}, {
resume: {url: '/events/:id/resume'},
signUpload: {url: '/events/:id/sign-upload'},
});
}]);
But when I call myModel.$resume(); or myModel.$signUpload() the returned data gets automatically saved to my model. However, the returned data is not my model attributes, but actually another completely different return.
I need to avoid auto-saving the returned data from the server. Is there anything out-of-the-box to do that? I couldn't find it here: https://docs.angularjs.org/api/ngResource/service/$resource
Thanks
For this case you can try to not use resource, but create service.
app.service('eventService', ['$http, $q', function ($http, $q) {
this.signUpload = function(eventId) {
var defer = $q.defer();
$http.get('/events/' + eventId + '/sign-upload')
.then(function(result) {
defer.resolve(result.data);
})
.catch(function(err) {
defer.reject(new Error(err));
});
return defer.promise;
}
// same for other function
}]);
Inject this service in controller, and just do eventService.signUpload(eventId);
My basic premise is I want to call back to the server to get the logged in user in case someone comes to the site and is still logged in. On the page I want to call this method. Since I am passing the user service to all my controllers I don't know which controller will be in use since I won't know what page they're landing on.
I have the following User Service
app.factory('userService', function ($window) {
var root = {};
root.get_current_user = function(http){
var config = {
params: {}
};
http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
Here is an empty controller I'm trying to inject the service into
app.controller('myResourcesController', function($scope, $http, userService) {
});
So on the top of my index file I want to have something along the lines of
controller.get_current_user();
This will be called from all the pages though so I'm not sure the syntax here. All examples I found related to calling a specific controller, and usually from within another controller. Perhaps this needs to go into my angularjs somewhere and not simply within a script tag on my index page.
You could run factory initialization in run method of your angular application.
https://docs.angularjs.org/guide/module#module-loading-dependencies
E.g.
app.run(['userService', function(userService) {
userService.get_current_user();
}]);
And userService factory should store authenticated user object internaly.
...
if (data.success == true) {
root.user = data.user;
}
...
Then you will be able to use your factory in any controller
app.controller('myController', ['userService', function(userService) {
//alert(userService.user);
}]);
You need to inject $http through the factory constructor function, for firsts
app.factory('userService', function ($window, $http) {
var root = {};
root.get_current_user = function(){
var config = {
params: {}
};
$http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
in your controller you can say
$scope.get_current_user = UserService.get_current_user();
ng attributes in your html if needed. besides this, i am not sure what you need.