I have a simple question about best practices.
A service loads once at load time.
Let's say there is a method called getUser in the service called user.
I have to call getUser in several controllers.
The GET request will happen twice right?
Is there a good practice to check whether the data has already been fetched to avoid this second call?
Yes, the call gets executed twice. You can use angular's built in $http cache option, or you could use an existing module like angular-cache, or other libraries such as Breeze or Amplify. You can also try handling it yourself, probably the worst option.
Related
I wonder what is the best practice for storing lists from different API's and display them without the need to make redundant API requests.
Suppose user logged in - i want to store all the pages static lists for dropdown menus in a way that they will be available to all the controllers - by doing the API requests once.
The options that i came across are the following:
creating a factory and store the data in $rootScope.
Use sessionStorage.
What is the best way?
Consider storing data in rootscope a very bad practice.
I'd suggest using this package
https://github.com/jmdobry/angular-cache
You have nicely explained inside documentation why would you use this package instead of angulars built in $cacheFactory service.
When expecting a call to the API should I include the entire URL including all the parameters, or do I just need a partial match?
Should I be listening for a call to the exact URL :
http://address.of.api/stuff/123?include=thing,anotherthing.name;
Or do I just need this :
/stuff/123
$httpBackend is part of Angular's Mock environment designed to replace real backend by fake one, or rather by imitating how Angular's $http works without real backend.
As much as I love Angular, I find $httpBackend over-engineered and unnecessarily complicated for what it does:
It is not recommended way to throw complicated code inside your tests. The more you do it, the more chance is that you create errors in that testing code instead of what you are actually supposed to test.
It promotes the bad practice of placing $http (or other low-level services in your abstraction hierarchy) freely around your code, as you can later use that $httpBackend to mock it away.
Instead it works cleaner to isolate any reference to low level methods into dedicated methods of your own, whose single responsibility is to make http requests. These dedicated methods should be able to work with real backend, not a fake one!
More details here on Angular testing
I'm trying to use angulars $http, with both a cache and an interceptor.
The quick question:
Currently when angular gets the answer from the server, it first caches it, and then it passes it through the interceptor.
Is it possible to force angular to first pass it through the interceptor and only then cache it?
The long issue:
The server responds every call with a format similar to:
{permission: BOOL, data:[...]}
In the interceptor response I check the permission, and if correct I throw it away and pass only the data field to the application level. If it fails I reject and popup an error, etc... (Maybe I should do it in a transformResponse function, but I'll endup facing the same issue).
Then for some API calls I'm requesting a bunch of resources like that:
/resource/ALL
And it obviously caches this request and answer, but what I want to do next is fake caching every resource that I received.
So forthcoming calls to /resource/{some resource id} are already cached, cause I've already received it in the call where I requested ALL.
The problem I'm facing is, when I want to fake cache, on the application level, I lost the "{permission: BOOL" part, cause I've thrown it in the interceptor.
Some notes:
1- Of course I could also fake the permission part, and just hardcode it, but I feel it's not an option since if I later add / modify / remove metadata it's another place I've to look at.
2- An other way would be to don't throw the metadata on the interceptor / transformResponse, but again this is not an option since I don't want to select the 'data' field every time I call $http on the application level.
So I think the best option for me would be to cache after the interceptor, and not before.
Hope I made the issue clear, and any answer is welcome!
tl;dr
I'm really struggling to find the appropriate pattern here. How should I best configure a generalized provider to a specific use-case? I can't use $http as a dependency in .configure(); can I?
longer, boring explanation:
I am trying to create a generalized provider which I may reuse in Angular. I have it working, however it requires configuration.
The intention is to provide a fallback REST service to use in saving data to the server, but with provision to save offline in local-storage. Therefore, I need to provide appropriate $http calls for each instance of this provider.
Is it possible to provide appropriate $http calls with .configure() or else should I try and figure out how to inject $http into the provider from the start and then configure it afterward??
It's frustrating... and may change in AngularJS 2.0... But for now, yes, it is not possible to do this. There is a very high wall between the .configure() and .run() states, so you can't access $http from a .configure() function. The reason is that it hasn't actually been created. At this stage, all that exists is the provider. Once all of the dependencies are configured, then the http provider will be used to make the real $http service.
I'm not sure exactly what you're trying to do, but there are two excellent AngularJS developers that are good to follow who have some advanced patterns in projects they've shared: Pascal Precht and Brian Ford. Here are two projects that make heavy use of provider/service concepts as well as $http-driven services:
https://github.com/angular-translate/angular-translate
https://github.com/btford/angular-modal
Angular Modal, especially, does $http work to load its templates. There might be use cases in there that are similar to what you're trying to do.
I have an application which has some specific (non-trivial) initialization requirements, and it's not really clear what the best practice solution to this is. Sorry for the wall of text. The question itself is not that complex, but I need to make sure my reasoning is clear.
First, the application itself:
It has user authentication, though it is only forced at two points in time:
The first time the application is loaded (the very first time). I'll just call this requirement (1) through the rest of the question.
On a need-to basis when interacting with server-side. This part I have already solved with something similar to http://ngmodules.org/modules/http-auth-interceptor, though a custom solution (which is required because the application needs to use some services that I don't want to be Angular dependent). I'll call this requirement (2) through the rest of the question.
There are two controllers relevant to this question:
A navigation bar controller (fixed, not bound to the view).
The controller applied to the view used (ng-view).
It is started manually using angular.bootstrap.
This question is about the user authentication handling. Requirement (2), where a user has to authenticate on a need-to basis, is already solved. It is currently handled like the following:
Some server-side request is performed by one of my Angular service modules. The request can potentially result in a 401 response if the applied authentication token has expired (or doesn't exist all-together).
The application service module which made the request discovers the 401 response and applies a $rootScope.$broadcast('app:auth').
The authentication broadcast is picked up by some code using $scope.$on('app:auth'), shows a modal authentication dialog, and then makes sure the original service request promise is resolved / rejected (rejected if the user presses cancel in dialog).
The only differences between requirement (1) and (2) is that (1) should be a forced authentication dialog (the user cannot simply reject it with 'cancel' or 'esc'-button) and that (1) should happen as early in application initialization as possible.
Now, my issue is with requirement (1), really, and Angular best practices. There are a couple of ways to do this that I can see:
Perform this one-time authentication outside of Angular completely. The downside here is obviously that I have to write essentially duplicate logic for both the modal dialog box and the initialization. Some of this can be shared, but not all.
Perform this one-time authentication in some special (fixed) controller of the application (like the navigation bar controller).
Perform this one-time authentication in angular.module.run.
The aim here is obviously to "force" an authentication on the user before he (or the application) can trigger something else in the application.
I would love to use number (3), since I would then be able to re-use all code already in use by requirement (1). However, you then instead run into the question of where to place the event-listening code. No controllers / parts of the application are yet started at this point (only the injections are complete).
If I place the logic for authentication events in an application controller, that controller won't even have started at that point, and thus won't have been able to register with the event. If I place the $rootScope.$broadcast inside a $timeout with 0 delay, my navigation bar controller have started, but not my view-bound controller. If I place the $rootScope.$broadcast inside a $timeout with 100 ms delay, both my controllers have started (on MY computer).
The issue obviously being that the amount of delay I need to use is dependent on the computer and exactly what scope the event handler code is placed in. It's also probably dependent on exactly in which order Angular initialize the controllers found through-out the DOM.
An alternative version of (3) might also be to do the $rootScope.$broadcast in angular.module.run, and have the event-listener attached to the $rootScope itself. I'm leaning towards this being the most straith-forward way to do it.
See the following plunker (which tries to higlight the timing issue only): http://plnkr.co/edit/S9q6IwnT4AhwTG7UauZk
All of this boils down to the following best-practice question, really:
Where should application-wide code and non-trivial application initialization code really be placed? Should I consider the $rootScope as the actual "application"?
Thanks!
The short answer :
Application wide code should be in a service.
Application initialization code should be in the run block.
Longer answer :
Application wide code like your Authentication should be defined in a service. This service should expose API's which the rest of your application can interact with in order to achieve that task. Ofcourse the job of the service is to hide the implementation details. The service itself should take care of where it fetches the authentication information from ( initially ) - perhaps from cookies, perhaps from your local storage or session storage.. Or perhaps it even does a http call. But all this gets encapsulated into that Authentication Service.
Because now you have written a separate service and you can inject stuff into your run block you are good to go. You dont really need the $rootScope. The $rootScope is another injected service. But because it participates in the dirty checking mechanism and seemingly this service need not.. you dont need to over burden $rootScope with this additional task. Its not its job and perhaps it can be delegated to some other service whose only task is authentication. Because your service is also a singleton it is amazing at maintaining states as well. You could perhaps set a flag , something like isAuthenticated which can be checked later if need be.
Oh, between your modal should also be a service.. See the $dialog service in Angular UI if you havent already. Which means that authentication can directly work with the $dialog service.
You should put application-wide non-trivial initialization code in providers. Providers offer the most flexibility with regards to initialization, because they can be used to configure the service before the instance of the service is actually created by the $injector.
app.provider('service', function() {
// add method to configure your service
this.configureService = function() { ... };
this.$get = function (/*injectibles*/) {
// return the service instance
return {...};
};
});
The config block is your opportunity to initialize your providers. Inject your provider into your config function (notice the required 'Provider' suffix) and perform any initialization code that you need to setup your provider. Remember, that the provider is not the service - it is the thing that the $injector will use to create your service.
app.config(function(serviceProvider) {
serviceProvider.configureService();
serviceProvider.setTimeout(1000);
serviceProvider.setVersion('1.0);
serviceProvider.setExternalWebService('api/test');
... more configuration ...
};
There are several reasons why providers and config blocks are suitable for initialization:
config blocks are called only once and very early in the application life cycle
providers are configurable - meaning you can initialize the provider before actually creating the service.
The main purpose of the config block is initialization. It supports injection of providers as an opportunity to perform the initialization.
Providers are singletons (like factories and services) - meaning that one service instance is created by the $injector and then shared between all controllers, directives, etc - basically any where that the service is injected.
Now for requirements (1) and (2) - I think you're on the right track. I suggest creating an authLogin directive that shows or hides a modal login dialog based on an "IsAuthenticated" property that is being watched on the scope. This would take care of the requirement to show the login modal dialog when the application starts up. Once the user authenticates successfully, set the IsAuthenticated property to true (which would then hide the dialog).
The second requirement is handled through an HTTP interceptor. When a request is made and the user is not authenticated, the service would broadcast the event starting from the $rootScope downwards towards the child scopes. You can have the same authLogin directive listen for the event and handle it by setting the IsAuthenticated property to false. Since IsAuthenticated is a watched property, it would trigger the modal login dialog so the user can log in again.
There are many ways you could implement requirements (1) and (2). I offered a slight variation on your approach, but in general it is the same approach.