Prevent calling angular factory every time template is loaded - angularjs

I'm using AngularJS and Angular Routing. I have template 'home.html' and controller for that template 'homeController'. Also, I have this factory:
app.factory('therapyService', function ($http) {
return {
getTherapies: function () {
//return the promise directly.
return $http.get('http://localhost:8080/application/api/rest/therapy').then(function (result) {
//resolve the promise as the data
return result.data;
});
}
}
});
In controller:
therapyService.getTherapies().then(function (results) {
$scope.therapies = results;
...
}
Problem is whenever I click on that page, that http call is called all over again. I want it called only first time when page is loaded first time. How to do it?

You can enable caching for individual calls by passing a configuration argument to the $http call:
return $http.get('url', { cache: true }).then(...)
If you see that most of your calls need to be cached and very few uncached, then you can enable the caching by default for your calls during the configuration phase of your application:
var myApp = angular.module('myApp',[])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.cache = true;
}])
Then you can explicitly disable caching for some calls by passing { cache: false }. I would recommend explicitly enabling caching. Caching is an optimization and enabling it by default can risk breaking the application if you forget to disable it at some point.

Related

angularjs calling controller functions while application load

I have a $promise service in controller and i want to call these services while application start.
CustomerService.fetchReligion.list().$promise.then(function(response){
$scope.religionList = response;
$window.localStorage.setItem('religionList', JSON.stringify(response));
}, function(error) {
// error handler
});
CustomerService.fetchCaste.list().$promise.then(function(response){
$scope.casteList = response;
$window.localStorage.setItem('casteList', JSON.stringify(response));
}, function(error) {
// error handler
});
How do i call these services?
One way to do this is by using UI-Router. UI-Router has a "Resolve" functionality in which, whenever the view changes to a particular state, promises shall be resolved and made into a value before the corresponding controller is instantiated. This means that whenever the new state/view (and its controller) is loaded, it is ensured that the required objects/values have already been retrieved and are ready for use.
The following snippet is directly taken from UI-Router's documentation.
$stateProvider.state('myState', {
resolve:{
...
// Example using function with returned promise.
// This is the typical use case of resolve.
// You need to inject any services that you are
// using, e.g. $http in this example
promiseObj: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/someUrl'});
},
// Another promise example. If you need to do some
// processing of the result, use .then, and your
// promise is chained in for free. This is another
// typical use case of resolve.
promiseObj2: function($http){
return $http({method: 'GET', url: '/someUrl'})
.then (function (data) {
return doSomeStuffFirst(data);
});
},
...
// The controller waits for every one of the above items to be
// completely resolved before instantiation. For example, the
// controller will not instantiate until promiseObj's promise has
// been resolved. Then those objects are injected into the controller
// and available for use.
controller: function($scope, simpleObj, promiseObj, promiseObj2, translations, translations2, greeting){
...
// You can be sure that promiseObj is ready to use!
$scope.items = promiseObj.data.items;
$scope.items = promiseObj2.items;
...
}
})

Angular ui-router: Is there a way to set a template load timeout?

Is there a way to set a default "time out" for a template load?
My template says "pending" (Chrome > Network Tab) even after a minute. It's an intermittent problem...just every once in a while a template will take an indefinite amount of time to load.
What I would like is to be able to handle the error instead of waiting indefinitely for my page to load.
There isn't an obvious way to do this. When you supply templateUrl, the $http request that is made does no have a timeout property. To be able to cancel, you'd need to make your own $http call.
This is possible with a templateProvider property. You can wrap this functionality in a service to make it less cluttered:
.factory("viewTemplateSvc", function($http, $templateCache){
return {
get: function(url, timeout){
return $http
.get(url, {
cache: $templateCache,
headers: { Accept: 'text/html' },
timeout: timeout // timeout in milliseconds
})
.then(function(response) {
return response.data;
}); {
}
}
})
.config(function($stateProvider){
$stateProvider
.state("stateA",
{
templateProvider: function(viewTemplateSvc){
return viewTemplateSvc.get("views/stateA.html", 2000);
},
// etc...
});
})
This is, of course, rather messy and not very DRY. You can, of course, rely on some default timeout, but you need to specify the templateProvider for every state.
One way to avoid this, is to use $stateProvider.decorator and substitute every occurance of templateUrl with templateProvider:
$stateProvider.decorator("views", function(state, viewsFn){
var views = viewsFn(state);
angular.forEach(views, function(config, name){
if (config.templateUrl){
var url = config.templateUrl;
config.templateUrl = undefined;
config.templateProvider = function(viewTemplateSvc){
return viewTemplateSvc.get(url, 2000);
}
}
});
return views;
});
Then, you could just continue using templateUrl as before.

Caching data using $cacheFactory vs regular javascript variable AngularJS

The InvoiceModel below has a getByCustomer() function that lists the invoices for a given customer.
I'm just wondering what's the difference between caching data to a regular javascript variable compared to using $cacheFactory.
Regular Javascipt Variable:
angular.module('app', ['ngResource'])
.factory('Invoice', ['$resource',
function($resource) {
return $resource('http://foo.bar/invoices');
}
])
.factory('InvoiceModel', ['Invoice',
function(Invoice) {
var customer_invoices = [];
return {
getByCustomer: function(customer_id) {
if (customer_invoices[customer_id] == undefined) {
customer_invoices[customer_id] = Invoice.get({customer_id: customer_id});
}
return customer_invoices[customer_id];
}
};
}
]);
$cacheFactory
angular.module('app', ['ngResource'])
.factory('Invoice', ['$resource',
function($resource) {
return $resource('http://foo.bar/products');
}
])
.factory('InvoiceModel', ['Invoice', '$cacheFactory',
function(Invoice, $cacheFactory) {
var customerInvoicesCache = $cacheFactory('customerInvoicesCache');
return {
getByCustomer: function(customer_id) {
var invoices = customerInvoicesCache.get(customer_id);
if (!invoices) {
customerInvoicesCache.put(Invoice.get({customer_id: customer_id}));
}
return invoices;
}
};
}
]);
I wouldn't use a $cacheFactory in the fashion you have shown. You're kind of using it the way $http or $resource would use it internally.
Instead, you can configure an $http or $resource object to cache the responses for specific queries. You then merely use the $http or $resource as normal. It handles the caching for you.
Example with $resource:
.factory('Invoice', ['$resource',
function($resource) {
return $resource('http://foo.bar/products', {}, {
query: { cache: true }
});
}
])
The above overrides the query method of the resource to enable caching with the default $cacheFactory. The first time your code calls the query method the response will get cached. Any subsequent calls to the query method will use the cached response.
Example with $http:
$http.get('/the-url', { cache: true }).then(...)
// or by passing in your own cache factory
var cache = $cacheFactory('myCacheFactory');
$http.get('/the-url', { cache: cache }).then(...);
Clearing the cache:
When you need to clear the cache, you get the $cacheFactory and call it's remove() function to clear the cache for the associated URL:
// default cache factory
var defaultCache = $cacheFactory('$http');
defaultCache.remove('/some/url');
// custom cache factory
var cache = $cacheFactory('myCacheFactory');
cache.remove('/custom-cache-url');

How can I directly inject $http into a provider?

All I need to do is to download a json file and assign it to OCategories in PCategory provider after I set the path. However I get an error that $http doesnt exist. How can I inject it into my provider and download inside of the setPath function?
var app = angular.module('NSApp',
[
'ui.bootstrap',
'MDItem',
'MDUser',
'MDNotification',
'MDUpload'
]
);
app.config(function(PCategoriesProvider)
{
PCategoriesProvider.setPath('data/csv/categories.json');
});
MDItem/provider/category.js
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set. Trying to download categories.');
OCategories = $http.get(oPath);
},
$get : function() {
return {
categories : OCategories
}
}
}
});
You can never inject service instances into config functions or providers, since they aren't configured yet. Providers exist to configure specific services before they get injected. Which means, there's always a corresponding provider to a certain service. Just to clarify, here's a little example configuring $location service using $locationProvider:
angular.module('myModule').config(function ($locationProvider) {
$locationProvider.html5Mode(true);
});
So what happens here, is that we configure $location service to use its html5mode. We do that by using the interfaces provided by $locationProvider. At the time when config() is executed, there isn't any service instance available yet, but you have a chance to configure any service before they get instantiated.
Later at runtime (the earliest moment ist the run() function) you can inject a service. What you get when injecting a service is what its providers $get() method returns. Which also means, each provider has to have a $get() function otherwise $injector would throw an error.
But what happens, when creating custom services without building a provider? So something like:
angular.module('myModule').factory('myService', function () {
...
});
You just don't have to care about, because angular does it for you. Everytime you register any kind of service (unless it is not a provider), angular will set up a provider with a $get() method for you, so $injector is able to instantiate later.
So how to solve your problem. How to make asynchronous calls using $http service when actually being in configuration phrase? The answer: you can't.
What you can do, is run the $http call as soon as your service gets instantiated. Because at the time when your service get instantiated, you're able to inject other services (like you always do). So you actually would do something like this:
angular.module('myModule').provider('custom', function (otherProvider, otherProvider2) {
// some configuration stuff and interfaces for the outside world
return {
$get: function ($http, injectable2, injectable3) {
$http.get(/*...*/);
}
};
});
Now your custom provider returns a service instance that has $http as dependency. Once your service gets injected, all its dependencies get injected too, which means within $get you have access to $http service. Next you just make the call you need.
To make your this call is getting invoked as soon as possible, you have to inject your custom service at run() phrase, which looks like this:
angular.module('myModule').run(function (custom, injectable2) {
/* custom gets instantiated, so its $http call gets invoked */
});
Hope this makes things clear.
Since all services are singletons in angular you could simply store a variable in a factory with the $http promise. And then when the factory is called at startup it will download the json.
You can then also expose a method on the factory that refreshes the data.
I know this is not the exact answer to your question, but I thought I'd share how I would do it.
angular.module('MDItem').factory('PCategories', function ($http, PCategoriesPath) {
var service = {
categories: [],
get: function () {
if (angular.isUndefined(PCategoriesPath)) {
throw new Error('PCategoriesPath must be set to get items');
}
$http.get(PCategoriesPath).then(function (response) {
service.categories = response.data;
});
}
};
// Get the categories at startup / or if you like do it later.
service.get();
return service;
});
// Then make sure that PCategoriesPath is created at startup by using const
angular.module('MDItem').const('PCategoriesPath', 'data/csv/categories.json');
angular.module('app').controller('myCtrl', function ($scope, PCategories) {
$scope.categories = PCategories.categories;
// And optionally, use a watch if you would like to do something if the categories are updated via PCategories.get()
$scope.$watch('categories', function (newCategories) {
console.log('Look maa, new categories');
}, true); // Notice the true, which makes angular work when watching an array
})
You have to inject $http in the function $get, because that's the function called by the injector.
However, to download the categories you would be better off using promises:
angular.module('MDItem').provider('PCategories',function(){
var OCategories;
var OPath;
return{
setPath: function(d){
OPath = d;
console.log('Path is set');
},
$get : function($http) {
return {
fetch: function () {
var deferred = $q.defer();
$http.get(oPath).then(function (value) {
deferred.resolve(value);
}
return deferred.promise;
}
}
}
}
});
I implemented what I wanted with a diffrent approach which is quite simple and effective. Just add a dummy controller in the main index.html(NOT PARTIAL). Data is now shared between all my modules and controllers and everything is downloaded once. :) Oh I love AJ.
...
<div ng-controller="initController" hidden></div>
...
initController:
angular.module('NSApp',[]).controller("initController",function($scope, $http, FCategory, FLocation){
$http.get('data/json/categories.json').then(function (response) {
FCategory.categories = response.data;
});
$http.get('data/json/cities.json').then(function (response) {
FLocation.cities = response.data;
});
$http.get('data/json/regions.json').then(function (response) {
FLocation.regions = response.data;
});
});
And now you can access it:
angular.module('MDTest', []).controller("test",function($scope, FCategory, FLocation){
$scope.categories = FCategory.categories;
FCategory factory
angular.module('MDItem').factory('FCategory', function ($http) {
var service = {
categories: [],
....
};
return service;
});

Angularjs Post-Receive Hook or Similar?

Is there a way to call a function every time after a response is returned from a server without explicitly calling it after in the callback?
The main purpose is that I do have a generic error handler service that I call in every request's callback and I want to specify it somewhere and it shall be called automatically.
I gave Gloopy a +1 on solution, however, that other post he references does DOM manipulation in the function defined in the config and the interceptor. Instead, I moved the logic for starting spinner into the top of the intercepter and I use a variable in the $rootScope to control the hide/show of the spinner. It seems to work pretty well and I believe is much more testable.
<img ng-show="polling" src="images/ajax-loader.gif">
angular.module('myApp.services', ['ngResource']).
.config(function ($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
var spinnerFunction = function (data, headersGetter) {
return data;
};
$httpProvider.defaults.transformRequest.push(spinnerFunction);
})
//register the interceptor as a service, intercepts ALL angular ajax http calls
.factory('myHttpInterceptor', function ($q, $window, $rootScope) {
return function (promise) {
$rootScope.polling = true;
return promise.then(function (response) {
$rootScope.polling = false;
return response;
}, function (response) {
$rootScope.polling = false;
$rootScope.network_error = true;
return $q.reject(response);
});
};
})
// other code left out
If you mean for requests using $http or a $resource you can add generic error handling to responses by adding code to the $httpProvider.responseInterceptors. See more in this post.
Although it is about starting/stopping spinners using this fiddle you can add your code in the 'stop spinner' section with // do something on error. Thanks to zdam from the groups!

Resources