Same angular service injection into factory and Controller - angularjs

Currently for loading a minimum of 100 customer booking records, We have used Angular factory as InitializationFactory for having the data in hand before the page loads and this factory is registered in Routes. This factory makes use of customerService to talk to relevant API. This whole request takes round about 5-7seconds to complete. We have the requirement of applying filters on top of the fetched data.
Considering page performance in mind, We now want to fetch only 10 records with dynamic filters applied over it. Next set of records would be retrieved based on a click event. Now, in order to do so, I need to inject customerService into CustomerController as well.
So, by design, is it a good approach to inject customerService into factory and then into controller for further data retrieval?
Code Snippet:
Route.js
.state("app.customer.dashboard",
{
parent: "parent",
controller: "CustomerBookingController",
resolve: {
customerBookingListModel: [
"CustomerInitializationFactory",
function (customerInitializationFactory) {
return customerInitializationFactory.initializeCustomerBookingResult();
}
]
}
CustomerInitializationFactory.js
function CustomerInitializationFactory(customerService, $translate, bookingConstants) {
return {
initializeCustomerAccountResult: function() {
var params = "xyz";
var promise = customerService.getCustomerBookings(params)
.then(function(bookingResponse) {
var customerBookings = constructCustomerBookings(bookingResponse);
return customerBookings;
});
return promise;
} };
CustomerBookingController.js
function CustomerBookingController(customerBookingListModel,customerService???) {
$scope.LoadMoreBookings = function(params) {
var bookings = customerService.getCustomerBookings(params);
};

Related

$rootScope is not updated in SPA

My first controller:
angular.module('application')
.controller('FirstController',['$rootScope',function($rootScope) {
var data=[0,1,2];
$rootScope.items=data;
}]);
My second controller:
angular.module('application')
.controller('SecondController',['$rootScope',function($rootScope) {
$rootScope.items[0]=3;
console.log($rootScope.items); // [3,1,2]
}]);
When the second controller is running, its corresponding view is changed; however not the same happens when going back to the corresponding view of the first controller (both views are bound to $rootScope.items). Why that happens? I am using ui-router and FirstController has to do with the main page of the SPA and SecondController with another page. Moreover, by keeping track of $rootScope.items with:
<pre>
{{$root.items | json}}
</pre>
in both templates the second one is renewed to [3,1,2] and the first one remains [0,1,2].
Passing the same $scope between the two controllers isn't an ideal way of maintaining a single data model, and what you need to do is to establish a service module (or a factory) to manage the data for you, so that both controllers can talk to the factor for your data.
This is how you set up the factory
app.factory("Data",
function () {
var storage = [0,1,2]; // where your data is
return {
get: function () {
return storage;
},
set: function (toSet) {
storage = toSet;
return storage;
}
};
});
to let the controllers know where the data is, use
app.controller ("FirstController",
function ($scope, Data)
{
console.log(Data); // [0,1,2]
Data.set( [3,1,2]); // demoing change
}
same is for the second controller
app.controller ("FirstController",
function ($scope, Data)
{
console.log(Data); // [3,1,2]
}

AngularJS - Handling API Data

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);
};
});

Managing data models in AngularJS the correct way

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

Share the same data between few controllers using ngResource

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.

Angularjs service to manage dataset across multiple controllers

Basically the core of my app centers around a set of data retrieved from the server via a $http request. Once the data is available to the client (as an array of objects) I require it for multiple views and would like to maintain it's state between them, for example, if it has been filtered I would like only the filtered data to be available in the other views.
Currently I have a basic service retrieving the data and am then managing the state of the data (array) in an app-wide controller (see below). This works Ok but it is beginning to become a mess as I try to maintain the array length, filtered status, visible / hidden objects across controllers for each view as I have to keep a track of currentVenue etc in the app-wide controller. Note: I am using ng-repeat in each view to show and filter the data (another reason I would like to just have it filtered in a central spot).
Obviously this is not optimal. I assume I should be using a service to maintain the array of venue objects, so it would contain the current venue, current page, be responsible for filtering the array etc. and just inject it into each controller. My question is, how can I set up a service to have this functionality (including loading the data from the server on start; this would be a good start tbh) such that I can achieve this an then bind the results to the scope. ie: something $scope.venues = venues.getVenues and $scope.current = venues.currentVenue in each views controller.
services.factory('venues', function ($http, $q) {
var getVenues = function() {
var delay = $q.defer();
$http.get('/api/venues', {
cache: true
}).success(function (venues) {
delay.resolve(venues);
});
return delay.promise;
}
return {
getVenues: getVenues
}
});
controllers.controller('AppCtrl', function (venues, $scope) {
$scope.venuesPerPage = 3;
venues.getVenues().then(function (venues) {
$scope.venues = venues;
$scope.numVenues = $scope.venues.length;
$scope.currentPage = 0;
$scope.currentVenue = 0;
$scope.numPages = Math.ceil($scope.numVenues / $scope.venuesPerPage) - 1;
}
});
Sorry for the long wording, not sure how to specify it exactly. Thanks in advance.
The tactic is to take advantage of object references. If you move your shared data to an object, then set that object to $scope, any change on $scope is directly changing the service object since they are the same thing ($scope is referencing the service).
Here's a live sample demonstrating this technique (click).
<div ng-controller="controller-one">
<h3>Controller One</h3>
<input type="text" ng-model="serv.foo">
<input type="text" ng-model="serv.bar">
</div>
<div ng-controller="controller-two">
<h3>Controller Two</h3>
<input type="text" ng-model="serv.foo">
<input type="text" ng-model="serv.bar">
</div>
js:
var app = angular.module('myApp', []);
app.factory('myService', function() {
var myService = {
foo: 'abc',
bar: '123'
};
return myService;
});
app.controller('controller-one', function($scope, myService) {
$scope.serv = myService;
});
app.controller('controller-two', function($scope, myService) {
$scope.serv = myService;
});
I threw this together quickly as a starting point. You can restructure factory any way you want. The general idea is all data in scope has now been moved to an object in factory service.
Instead of resolving the $http with just the response array, resolve it with a much bigger object that includes the array from server. Since all data is now in an object it can be updated from any controller
services.factory('venues', function ($http, $q) {
var getVenues = function(callback) {
var delay = $q.defer();
$http.get('/api/venues', {
cache: true
}).then(function (response) {
/* update data object*/
venueData.venues=response.data;
venueData.processVenueData();
/* resolve with data object*/
delay.resolve(venueData);
}).then(callback);
return delay.promise;
}
var processVenueData=function(){
/* do some data manipulation here*/
venueData.updateNumPages();
}
var venueData={
venuesPerPage : 3,
numVenues:null,
currentVenue:0,
numPages:null,
venues:[],
updateNumPages:function(){
venueData.numPages = Math.ceil(venueData.numVenues / venueData.venuesPerPage) - 1;
},
/* create some common methods used by all controllers*/
addVenue: function( newVenue){
venueData.venues.push( newVenue)
}
}
return {
getVenues: getVenues
}
});
controllers.controller('AppCtrl', function (venues, $scope) {
venues.getVenues(function (venueData) {
/* now have much bigger object instead of multiple variables in each controller*/
$scope.venueData=venueData;
})
});
Now in markup reference venueData.venues or venueData.numPages
By sharing methods across controllers you can now simply bind a form object with ng-model's to a button that has ng-click="venueData.addVenue( formModel)" (or use ng-submit) and you can add a new venue from any controller/directive without adding a bit of code to the controller

Resources