I'm using Oauth to manage session users in my AngularJS app. My Angular app needs to know information about the session user because it is used in my $resource objects(to make REST calls like GET /user/:user/projects). I want this user variable to be global and synchronous, so making an async call with $resource or $http to get the user adds boilerplate to the code that needs this session user(because it has to wait for the user information to return from server).
Right now I'm using this dirty hack that resolves the issue: in my index.ejs file(notice that the user is rendered server side with ejs)
<script type="text/javascript">
var session_user = <%- user %>;
</script>
and then I do
$rootScope.user = session_user
The problem is that when I unit test the controller, of course session_user is not defined.
I'm thinking that maybe I could create a service that returns this session_user variable, so I can use a mock for testing. Any other ideas? What is the recommended aproach to this kind of problems?
It doesn't seem like that much of dirty hack actually. It would seem problematic only if the session user would change without reloading your html from the server (and thereby getting a new session user).
Angular modules have a constant function that I use for this sort of thing:
angular.module("MyModule", [])
.constant("User", window.session_user);
And then you can treat User as a injectable value in your controllers.
Related
I am building a large application with Web API and AngularJs. I built the secure web api with authentication and claim-based authorizations. One requirement is that different users can view different modules on the same template.
I am new to AngularJs. I did the authentication in client side with the tokens. Also, in web api, I created a service to get all the permission given a user id. The response is a list of resource(contoller)/action(method) pairs. How do I implement the correct layout based on authorization rules on client side? Does that solely rely on web api permissions response and show/hide (ng-hide/ng-show) content based on the permissions?
Is this a good approach? What other modules/directives do I need to look into? Such as the loader for not loading the nested route until user request the parent route.
To add complexity, this site also need to work in bi-lingual. I think ng-translate. I mentioned this because it may open up another discussion on whether this may favor MVC instead of AngularJs. But the preference is Angular if the above two problem can be resolved.
All the authentication & authorisation & validation should be done server-side. You can adjust the user interface based on the roles/claims the server tells the browser the current user has.
One way to do this is to create something like a roles/userprofile controller, which will respond with a list of roles the current user has. On the client side you’ll probably want something you can inject everywhere, so you’re able to determine user interface behaviour.
myApp.factory(‘myUser’, function(Roles, $q) {
// Create a promise to inform other folks when we’re done.
var defer = $q.defer();
// For this example I’m using ngResource
Role.query({
/*
No params — let the server figure out who you ‘really’ are.
Depending on WebApi configuration this might just be
as simple as this.User (in context of the controller).
*/
}, function(roles) {
var user = {
roles: roles,
isInRole: function(role) {
return user.roles.indexOf(role) !== -1;
}
};
defer.resolve(user);
});
return defer;
});
Because the factory above is returning a promise we can enforce that myUser is resolved before a certain route/controller instance is created. One little trick I use is to gather all my route definitions in one object, loop through them with an angular.forEach and add a resolve.myUser property to each of them. You can use this to pre-load/initialize other stuff too.
Now inject the myUser anywhere you like:
myApp.controller(‘MyController’, function($scope, myUser) {
// Expose it on the current scope
$scope.myUser = myUser;
});
… and in your markup …
<div class=“my-content-thingy”>
<p>Lorem del ipsum …</p>
<button class=“btn” ng-if=“myUser.isInRole(‘content-editor’)”></button>
</div>
Note: You’ll probably want to use ng-if and not ng-show; the latter keeps the element in the DOM.
Just keep in mind that you don’t authenticate anything on the client side; that all done server side. A simple way is to place Authorize attributes on the appropriate controller actions.
Hope this helps.
A proper approach is to build AngularJS routing configuration as per Authorization on the server. This should be build just after the user is authorized and before the AngularJS app is initialized. That way the authorized user sees a "complete" app based on his roles etc. Using ng-show/ng-hide is not a good way to do it. Also each view should be doing only one thing. So load separate views based on the task that needs to be completed.
Regarding multi language support, this is independent of Authorization. Some time ago, I wrote a custom AngularJS filter that used the jQuery i18next plugin. It was a pretty simple implementation.
However you can now use https://github.com/i18next/ng-i18next
(Sorry for misunderstanding the problem).
I think that using ng-hide/show is not much of a problem. At the end of the day, the user does not have access to the data. I think it should rely on the api permissions + show/hide of presentation. Depends on the complexity you want... You can use MVC with angularjs since it's a large application.
In AngularJs is there any way to share object between two web pages without using $cookies. For example my Login page controller have different angular.module and my other controller have different angular.module but I need to share credentials between Login page and another page without using $cookies.
Between independent page
You can use the standard HTML5 sessionStorage and localStorage object
For simple synchronous storage access HTML 5 introduces the localStorage attribute on the Window object:
localStorage["status"] = "Idling.";
LocalStorage is like cookie, sessionStorage will be clean when closing your browser.
In angular
You can use a factory which is technically a singleton. But if you refresh your page, all JS will be re-initialize and you will lose your data. A Service is also possible.
Here is a link on an other topic explaining difference between Services and Factory : AngularJS: Service vs provider vs factory
To create Services/Factory, give a look at Angular official documentation, it is well explained.
Perfect mix
What you need to do is create a Service, and at each modification you stringify it to store on a local/session Storage. On load, when angular create your service, you look in your storage for initialization value and your object is back.
This is quite common for authentification for exemple. You store a token to keep authentification when refreshing ;). Let me know if any difficulty to implement.
Have you considered creating a Service which stores these login credentials? You could then use Dependency Injection to have this Service available in both controllers.
Sharing the information between different pages is a critical task which can be done with the help of Controllers.
In AngularJS, we can share the data between controllers in three ways:
Factory: Object is created inside the factory and return it .
Service: With the service, you just have a standard function that uses the this keyword to define function.
Provider: With the provider, there’s a $get you define and it can be used to get the object that returns the data.
I'm using AngularFire to create a web app. Several of my controllers use a lot of the same functions to access the data in Firebase, so I am trying to write a service so that I don't have to rewrite the same functions for every controller. The problem is that I can't seem to get the Firebase dependency into the service. I'm using the following code:
angular.module("myApp", ["firebase","ngDraggable"])
.factory("GetData",['$firebase','FirebaseConn',function($firebase,FirebaseConn){
$firebase(new Firebase("https://XXXX.firebaseio.com/")).$asObject().$bindTo(scope, "firebaseData");
return {
};
}]);
But when I try to run this, I get the following error:
Unknown provider: FirebaseConnProvider <- FirebaseConn
I am able to access Firebase through my controllers, so it's not a problem connecting to firebase.js or angularfire.js.
How should I inject Firebase into my service so I can access the data in all of my controllers? Or, is there a better way to go about this? I'm rather new to Angular so if there's a better way to share functions between my controllers I'm all ears.
You inject them precisely the same way that you do in Controllers and Directives.
This is a common error in AngularJS, and it means you're injecting something that isn't injectable. It almost always means either you missed a dependency (probably not in this case) or that you're asking for something that doesn't exist in the first place.
This is almost certainly your problem. You're asking for firebase and getting it, but it can't find FirebaseConn. What is that, some variable of yours that you're using to track your connection? Because you aren't using it, and the AngularFire docs I just looked at don't, either.
Consider something more like the following:
angular
.module("myApp", ["firebase", "ngDraggable"])
.service("firebaseManager",['$firebase', function($firebase) {
var ref = new Firebase("https://XXXX.firebaseio.com/"),
sync = $firebase(ref);
this.getData = function() {
return sync.$asObject();
};
});
Obviously, customize this to suit. Two comments:
You probably do want a service instead of a factory. This is a common point of confusion when you first start using AngularJS. You only need a factory if you plan to get involved in the instantiation of the service in some way. A service is just a shortcut form of a factory with the most common usage - the one you probably want.
You will now inject this service firebaseManager into your controllers. When you do, they will be able to call firebaseManager.getData() and any other methods you define. firebaseManager will be a singleton, so all of this will go through one common Firebase connection.
I am using AngluarJS since 2 days and i am now trying to build my authentication system.
I have several controllers within i need to reflect connected user credentials. Something like:
<body>
<div ng-controller="topController">
<h1>{{User.name}}</h1>
</div>
<div ng-controller="contentController">
<h1>{{User.age}}</h1>
</div>
<div ng-controller="bottomController">
<h1>{{User.email}}</h1>
</div>
</body>
So since it's not appropriate to load the User into a controller, i guess the best way is to work with an external service and get it back on the different controllers's callbacks.
But by doing that, i am wondering how (where) could we load the session at the fisrt loading of the page (let's imagine i just have a single view to ignore the router mechanism) ?
I am looking for a kind of "main" point.
I am using the facebook authentication so my question is not really about how to use the cookies with angular, but more about the "global logic" to set up.
If someone could give me an idea about how to do that, or give me some explanation about a best way to think the authentication mechanism, it would be nice !
You generally do global initialization code by passing an initialization function to the run method of your main app module like so:
angular.module('myApp', [/* some dependencies */])
.run(function(/* some dependencies */) {
// Do anything you want to do after all modules load
// but before your DOM/directives are processed by Angular.
});
See here: http://code.angularjs.org/1.2.6/docs/api/angular.Module#methods_run
This would seem to be a good place to do any session stuff you want to do with Facebook. If your Facebook interactions are asynchronous, then you'll probably need to delay your main app controllers ("topController", "contentController" etc) from doing their thing until your figure out what's happening with the session (since it won't happen synchronously in this run function). One solution to this could be to set up routing so that you don't get routed to the view with those main app controllers until the session logic finishes. In the run initialization function, when your asynchronous FB session stuff is done (and you've stored the session info in a Service or something), you can make a call to change routes so you load the main app view, and at that point whatever Service you're storing the session in should be all set with your FB session.
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.