I am new to angular so it is probably easy question. I have this factory resource at the moment:
angular.module('resources.survey', ['ngResource'])
.factory('Survey', function ($resource) {
return $resource('/backend/surveys/:surveyId/data', {surveyId: '#id'});
});
Controller:
.controller('PagesCtrl', function (Survey) {
var survey = Survey.get({surveyId: 2});
//now I want to change survey variable and share it between two controllers
});
There are no problems with ngResource I can get the data from server. However I want to manipulate with the data from the server and use the same data in other controllers (probably using DI) and allow data manipulation there as well. I know that it can be done with $rootScope, but I was wondering if there is any other way.
Your service should cache the response for the resource request in something like array of surveys and dispense surveys from this array instead of directly returning a resource object.
Controllers would only share data if the same reference for the survey is returned.
Roughly it would look like
.factory('Survey', function ($resource,$q) {
var surveys[];
return {
getSurvey:function(id) {
var defer=$q.defer();
//if survery contains the survey with id do //defer.resolve(survey[i]);
// else query using resource. On getting the survey add it to surveys result and resolve to the newly added survey.
}
}
});
angular.module('resources.survey', ['ngResource'])
.factory('Survey', function ($resource) {
return $resource('/backend/surveys/:surveyId/data', {surveyId: '#id'});
})
.controller('MyCtrl', function($scope,Survey){
//here you can call all the $resource stuff
});
Here is complete documentation and example how to use it:
http://docs.angularjs.org/api/ngResource.$resource
I managed to create a resource that can handle what I wanted. It is probably not as advanced as Chandermani suggested. But it works for my needs.
angular.module('resources.survey', ['ngResource'])
.factory('Survey', function ($resource) {
var resource = $resource('/backend/surveys/:surveyId/data',
{surveyId: '#id'}
);
var Survey = {};
var data = []; //saves the data from server
Survey.get = function(surveyId) {
if(angular.isDefined(data[surveyId])){
return data[surveyId];
}
return data[surveyId] = resource.get({surveyId: surveyId});
};
return Survey;
});
And to call basically I call it like this:
.controller('QuestionsCtrl', function (Survey) {
Survey.get(1).newData = 'newData'; //adding additional data
console.log(Survey.get(1));
});
I guess this can be improved.
Related
I have been working with wrapping my head around the "angularjs" way of thinking (Angular 1) and I have a relatively ok grasp as I work my way through a small personal project. I am at a bit of a roadblock, not because I cannot get it to work, but I would like to know what the proper way to set up the data in my application.
The basic situation is this:
I have 3 json files:
categories.json
products.json
vendors.json
These hold the data (which I will fetch from a database later but am simplifying for now).
I basically need to load the data from all three of these files so that I can form a variable holding all "Products" (which is a JS class I declared separately).
I started off by storing the data inside one controller (relevant code below):
myApp.controller('productListController', ['$scope', '$http', '$q', function ($scope, $http, $q) {
var promises = [];
promises.push(getCategories($http));
promises.push(getVendors($http));
promises.push(getProducts($http));
$q.all(promises).then(function (response) {
//categories = response[0];
//vendors = response[1];
//products = response[2];
$scope.products = createProductList(response);
$scope.vendors = response[1].data;
$scope.vendorChecked = getCheckedVendors($scope.vendors);
})
This worked fine but I realized that I need this data in other views, which led me to try to move this code into a service.
The problem I had when doing this is that I do not know of a way for the controller to know that the service is done fetching the data so that I can then save it in the ProductListController $scope.
I would need to have a way to for example:
myApp.service('ProductService', ['$http', '$q', function ($http, $q) {
self = this;
var promises = [];
promises.push(getCategories($http));
promises.push(getVendors($http));
promises.push(getProducts($http));
$q.all(promises).then(function (response) {
//These three I would like to update in ProductListController
//when it is done.
self.products = createProductList(response);
self.vendors = response[1].data;
self.vendorChecked = getCheckedVendors(self.vendors);
})
Is this the correct approach to take? If so, how can I let the controller know that the service is done fetching the data and save for example:
$scope.products = ProductService.products;
$scope.vendors = ProductService.vendors;
$scope.categories = ProductService.categories;
Is this even the correct approach? Another approach I thought of was to use a factory instead of a service. Then I had another problem because I had for example:
return {
getProducts: function() {
//http get request code in here
return promise
},
getVendors: function() {
//http get request code in here
return promise
},
getCategories: function() {
//http get request code in here
return promise
},
getAllData: function () {
//in here I want to use the three promises in the 3 functions above
//but I am not able to call them from here. If I was able to do that
//then I could call this method from ProductListController and get the
//data that way.
}
I am sorry if this is long but I wanted to describe the different things I tried. I know I can make it work but I want to learn the right way, or a couple of right ways.
It is better to always return promise:
var promises = [];
promises.push(getCategories($http));
promises.push(getVendors($http));
promises.push(getProducts($http));
return $q.all(promises)
If you also not satisfied that in each controller you should call createProductList,getCheckedVendors - consider putting this tranforms to $http transformResponce https://docs.angularjs.org/api/ng/service/$http.
Or you can create your own promise. (Using $q.defer https://docs.angularjs.org/api/ng/service/$q).
Using servie or factory actually doesnt matter. This is factory:
var factory = {};
factory.getProducts: function() {
return promise
}
factory.getCategories: function() {
return promise
}
factory.getVendors: function() {
return promise
}
factory.getAllData: function () {
var promises = [];
promises.push(factory.getProducts());
promises.push(factory.getCategories());
promises.push(factory.getVendors());
return $q.all(promises)
}
return factory;
And in controler you just have:
MyFactory.getAllData().then(...)
Currently developing an app which requires pulling in data using an API, and running into the following problem.
In my controller I pull in a list of products from an API, add them to $rootScope, than loop over in order to display a list of all products.
When viewing an individual product, I loop over the list and display the product with the requested ID into a view like so
getProduct: function(productID) {
var products = $rootScope.products;
for (var i =0; i<products.length; i++) {
if (products[i].id == parseInt(productID)) {
return products[i];
}
}
}
This all works fine, except for if you visit the individual product URL without first going through the main list page, the list of products is unavailable as the call to the API is never made.
What is the best way to about resolving this?
Creating another separate API call from this view seems very messy and overly complicated and I was wondering if there is a standard or better approach!
I would create an angular service for product that has a private array for products with a getter & setter. This will allow you to stay away from using $rootScope to pass data between controllers (which is a good thing).
Then have a function that makes a call to the API for each product detail page. You can then pass the ID to the page in the URL using $routeParams
By putting this all in a service it becomes reusable to all your controllers and quite clean.
var myModule = angular.module('myModule', []);
myModule.service('productService', function() {
var products = [1,2,3,4,5];
this.addProduct = function(id){
products.push(id);
}
this.getProducts = function(){
return products;
}
this.getProductDetail = function(id){
//$http.get(...
}
});
Edit: example implementation as requested
// Don't forget to inject your service into your controller
myModule.controller('myCtrl', ['productService', function (productService) {
// Add a product to your array
productService.addProduct(6); //products = [1,2,3,4,5,6]
// Retrieve products
var myProducts = productService.getProducts(); //myProducts = [1,2,3,4,5,6]
// You can do this a few different ways but assuming
// getProductDetail returns a promise from its $http call
productService.getProductDetail(4).then(function (productDetail) {
$scope.productDetails = productDetail;
});
}]);
Came up with a solution based on your answer (removing the dependency on $rootScope really helped) so marking your answer as good to go but wanted to share what I came up with in case anyone finds it useful!
Essentially, in the product detail controller which deals with displaying an individual product we say, if we already have the product information within the application, pull it from that, and, if we don't then make a call to the API.
I have an extra service called config which I need to access the my API in case anyone is wondering whats going on there :)
Service
productsApp.factory('$productService', function($q,$http,$rootScope,$cookies,$state,$configService) {
var products = '';
return {
//Get all products
getProducts: function() {
//$http.get....
//Dont forget to use $q.defer() for your promise
},
returnProducts: function() {
return products;
},
//Get specific product
getProduct: function(productID) {
for (var i =0; i<products.length; i++) {
if (products[i].id == parseInt(productID)) {
return products[i];
}
}
}
}
});
Controller
productsApp.controller('productDetailCtrl', function($scope,$rootScope,$productService,$stateParams,$configService,$cookies) {
var products = $productService.returnProducts();
if (products == '') {
$configService.getToken().then(function(response){
$productService.getProducts().then(function(response){
$scope.product=$productService.getProduct($stateParams.productID);
}, function(error){
console.log(error);
});
}, function(error){
console.log(error);
});
} else {
$scope.product = $productService.getProduct($stateParams.jobID);
};
});
I'm trying to take the response of an $http request and save it to a custom cache. I want to then use that cache to display data into the view. I thought the cache would be checked automatically on each request before fetching new data, but that doesn't seem to be working for me.
The problem I'm having is I can't seem to save the data. The following function needs to make 2 requests: articles and images.
getImages: function() {
var cache = $cacheFactory('articlesCache');
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
});
});
return cache;
}
This creates the custom cache, but there's no data in the returned object. I know now that I can't save the data this way, but I don't know why or how I would go about doing it.
Can anyone explain how storing response data works? Where, if at all, does using promises come in here?
Your return statement executes before the code in your then function does. If you want to return the cache you'll want to run everything through the $q service and then return the resolved promise.
This is probably not the best way to use $cacheFactory. Typically you'd expose your cache as a service at a higher level and then access the cache via the service where needed.
So on your main module you'd have something like this to create the cache.
.factory('cache', function ($cacheFactory) {
var results = $cacheFactory('articleCache');
return results;
})
Then where ever you need the cache you inject it into the controller and use cache.get to retrieve the data from it.
If you want to use $q to implement this, your code would look something like the code below. (Disclaimer: I've never used $q with $cacheFactory like this, so without all of your components, I can't really test it, but this should be close.)
var imageService = function ($http, $q,$cacheFactory) {
var imageFactory = {};
imageService.cache = $cacheFactory('articlesCache');
imageFactory.getImages = function () {
var images = $q.defer();
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
images.resolve(cache.get('articlesCache'))
});
});
return images.promise
app.factory('ImageService', ['$http', '$q', '$cacheFactory', imageService]);
});
I adapted the code from this answer: How to get data by service and $cacheFactory by one method
That answer is just doing a straight $http.get though. If I understand what you're doing, you already have the data, you are posting it to your server and you want to avoid making get call to retrieve the list, since you have it locally.
As I scour the internet, I am finding so many different ways to manage the data model that is used in our Angular templates, but they all only show a small part of the bigger picture. In a large application we need to glue together API data to some form of JavaScript model which in turn is used within our template, but I am not sure how this should all be managed.
After reading this article on AngularJS models I am now aware that I should be wrapping all of my models in a service so the information is available across multiple controllers. One of the only things is that it does not explain how to tie these Services in with API requests.
Here is my current implementation.
Customer Model
var Customer = function (obj) {
var self = this;
if (!obj) {
self.Name = null;
self.Address = null;
self.PrimaryEmailAddress = null;
self.SecondaryEmailAddress = null;
self.PhoneNumber = null;
} else {
self = obj;
}
return self;
}
Then in my controller I use this model on my $scope like
Customer Controller
app.controller('CustomerController', [function($scope, API){
$scope.model = {};
API.Account.getCustomer({id:'12345'}, function(data){
$scope.model = new Customer(data);
});
}]);
This is what my API service looks like
API Service
app.factory("API", ["$resource", function ($resource) {
var service = {};
service.Account = $resource('/WebApi/Account/:type/:id', {},
{
getCustomer: { method: 'GET', params: { type: 'Customer' } }
});
return service;
}])
This has worked fairly well up until now, when I realized that there is API information that is gathered in a parent controller that is now needed in a child controller
Going back to the article linked above, I now can change my models around so they are wrapped in an Angular Service and are therefore available across multiple controllers. This new CustomerService may look like this
NEW CustomerService
app.factory('CustomerService', function() {
var CustomerService = {};
var customer = new Customer();
CustomerService.getCustomer = function() { return customer; }
CustomerService.setCustomer = function(obj) { customer = obj; }
return CustomerService;
});
This isn't quite like the article (much simpler) but it basically contains the OO-like functions to get and set the Customer object.
The problem with this way is we still don't have access to the actual API endpoint to get the customer data from the server? Should we keep the API Service and the Model Services seperate or try to combine them into a single Service? If we do this then we also need a way to differentiate between actually getting fresh data from the server and just getting the currently set Customer object in the singleton object.
I would like to know your initial response to this predicament?
UPDATE
I came across this article that brings together all the functionality of a model including the API requests into a factory service
Model service and API service should be separate. The API service can depend on model service and return model objects instead of directly returning what came from api.
app.factory("API", ["$resource",'Customer','$q' function ($resource,Customer,$q) {
var service = {};
service.Account = function(accountId) {
var defer=$q.defer();
var r=$resource('/WebApi/Account/:type/:id', {},
{
getCustomer: { method: 'GET', params: { type: 'Customer' }}
});
r.getCustomer({id:'12345'}, function(data){
defer.resolve(new Customer(data));
})
return defer.promise;
}
return service;
}]);
This way you have combined the model service and API service. I am assuming Customer is a model service rather than the Customer object.
Update: Based on your follow up question, i thought there could be a better way, but i have not tried it. Rather than creating own promise every time we can use the resource promise:
service.Account = function(accountId) {
var r=$resource('/WebApi/Account/:type/:id', {},
{
getCustomer: { method: 'GET', params: { type: 'Customer' }}
});
return r.getCustomer({id:'12345'}).$promise.then(function(data) {
return new Customer(data);
});
}
Since the then method itself returns promise that is resolved to return value of the then callback statement this should work.
Try this approach and share your findings
I have two factories as follows:
angular.module('categoryResource', ['ngResource', 'userResource'])
.factory('categories', function ($http, users){
var factory = {};
factory.getCategories = function () {
return $http.get('/api/userCategories');
};
factory.getCategoriesUsers = function () {
//call factory.getCategories(), parse the request to make it nice and tidy
//then call users.getUsers(), tidy them up in the same way as categories, join them together and return them all in a nice package.
};
var handleCategoriesUsers = function (data, status) {
}
return factory;
});
angular.module('userResource', ['ngResource'])
.factory('users', function ($http) {
var factory = {};
factory.getUsers = function () {
return $http.get('/api/users');
};
return factory;
});
I've already made a nice treeView with the Categories but I want to also add Users to those categories; so to this end I thought I'd just format the users into something my treeView algorithm can already work with.
My question is, Can I somehow do It within the categories factory (or even a different module/factory altogether) and just return the joined results at the same time? If yes, how? Do I need to define a handler for $http.get like I usual? then in that handler, call the other get then define a handler for that and then return the result?
I'm not 100% sure I understand the question, but have you looked at $q.all? It seems like you want to compose/combine multiple promises into a single, easy to handle package. I'd give you a description and some code samples, but egghead did it better: https://egghead.io/lessons/angularjs-q-all
Is this what you're looking for?
EDIT: In case just dumping a link is frowned upon:
var all = $q.all([one.promise, two.promise, three.promise]);
all.then(success)
function success(data) {
console.log(data);
}