Dynamic directive templates using app level resolve AngularJS - javascript

Currently i am implementing a completely stateless token-based authentication with AngularJS and a REST API as backend service. This works pretty well, but about one state i am currently not aware of and wanted to ask you how to handle it.
My intend is only to save my authentication token in the local storage from the client. On initial loading from the application i want to retrieve all user information from the REST API (GET /me) and save it in the $scope from my AngularJS project.
After implementing it, i have several problems to be sure that the retrieved user information are completely resolved and could work with the informations (like the access rights from the user).
I just want you to show an example where i ran into a kind of "asynchronous problem".
angular.directive('myDirective', ['MyAsyncService', function(MyAsyncService) {
return {
restrict: 'E',
templateUrl: function(element, attributes) {
console.log(MyAsyncService.getData());
}
}
}]
At this example my asynchronous service haven't the information yet, when the directive is rendered. Also retrieve the async data in the resolve function from ngRoute/ui-router or in the run() method from AngularJS couldn't solve my problem.
So back to my question ... It is better to save some information from the user on client side to avoid some of this problems? From my point of view it would be better not to save any kind of user information (like access rights, username, id, email address, ...) due security purposes.
How you handle you the authentication and authorization in a token-based AngularJS application?
Thanks in advance, hopefully you can get me back on track.

You need to resolve() authentication - especially this type of authentication at the app level.
If you aren't using ui.router - take a look.
When we resolve() on a route what we are saying is - do not do anything else until this thing is finished doing what it's doing.
This also assumes you are familiar with promises.
Your resolve dependency will ultimately expect that this asynchronous request is returning a promise so that it may chain controller logic after the callback finishes execution.
For example:
$stateProvider
.state('myState', {
resolve:{
auth: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/myauth'});
}
},
controller: function($scope, auth){
$scope.simple = auth;
}
});
But there's more. If you want the resolve to happen at the begining of application execution for any route - you also need to chain your state declarations:
$stateProvider
.state('app', {
abstract: true,
templateUrl: 'app/views/app.tpl.html',
resolve:{
auth: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/myauth'});
}
},
controller: function($scope, auth){
$scope.resolvedauth = auth;
}
}
.state('app.home', {
url: '/home',
templateUrl: 'home/views/home.tpl.html',
controller: function($scope){
//Should be available.
console.log($scope.$parent.resolvedauth);
}
});
Now that your children inherit from the app level resolve, you could control the template by doing something like this:
.directive('myDirective', function() {
return {
templateUrl: function(elem, attr){
return 'myDirective.'+attr.auth+'.html';
}
};
});
And the declaration itself:
<my-directive auth="resolvedauth.status" />

Related

How to use resolves for routes to lazy load data in angular js

Resolves are great, resolve for routes - it's a great way to handle loading of required data, authentication.
// Create a function that uses the NameService
// to return the getName promise.
var getName = function(NameService) {
return NameService.getName();
};
$routeProvider
.when('/home', {
templateUrl: '/home.html',
controller: 'MainController'
})
.when('/profile', {
templateUrl: '/profile.html',
controller: 'ProfileController',
/* only navigate when we've resolved these promises */
resolve: {
name: getName
}
})
But in some cases you may want to lazy load data when your route changes. There is a tough balance between how much data you want to front-load to a route - mainly due to user experience of the page not switching instantly, and how big of a payload you want to deliver.
looking for better ways to use resolves for routes for such situations.

Load configuration data in Angular Applicatoin

In my angular application, I want to load some user specific configuration data from server, if user authorized. This data will use all over the application. I have implemented cookie base token authentication. How can I delay application until this data loading complete?
You can use resolve in route. Example from ng-doc:
app.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/Book/:bookId', {
templateUrl: 'book.html',
controller: 'BookController',
resolve: {
// I will cause a 1 second delay
delay: function($q, $timeout) {
var delay = $q.defer();
$timeout(delay.resolve, 1000);
return delay.promise;
}
}
});
Instead of timeout promise you can return any promise what you wish.
In this case your controller will be started after route.resolve promise resolving.
Also look at this answer. It looks like very relevant for your case.

Basic AngularJS service request

I am trying to make changes to a very basic sample app so to decouple the static portion of the application from the server side based on Springboot. I want to run the AngularJS static portion of the app on nginx and make remote service calls to the Springboot application.
Here is gist of the application ...
angular.module('blah', ['ent1', 'errors', 'status', 'info', 'ngRoute', 'ui.directives']).
config(function ($locationProvider, $routeProvider) {
$routeProvider.when('/errors', {
controller: 'ErrorsController',
templateUrl: 'assets/templates/errors.html'
});
$routeProvider.otherwise({
controller: 'MyController',
templateUrl: 'assets/templates/albums.html'
});
}
);
function MyController($scope, $modal, $location, $http, a, b, st) {
$scope.init = function() {
console.log($location.host());
---> $location.host("http://somewhereelse:8080/");
console.log($location.host());
....
};
}
Some details have been removed for readability sake.
As you can see, I have injected $location into my controller, which I assume is where I would change the host. However, the before and after console.log still show me "localhost".
How do I make a remote API call to a service using AngularJS? Like I said, it is probably a very basic question.
Hope you are trying to make some API calls from your angular application. For that you can use angular $http service.
A sample code will look like this:
// Simple GET request example:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
For More details see the official doc:
https://docs.angularjs.org/api/ng/service/$http

Angular JS Authentication maintaince

I'm looking for the best architectural solution.
I have following html:
<body ng-controller="individualFootprintController as $ctrl">
<div ng-hide="$ctrl.authenticated">
<h1>Login</h1>
With Corporate Account: click here
</div>
And controller:
function individualFootprintController($http) {
var self = this;
$http.get("/is_auth").success(function () {
self.authenticated = true;
})
.error(function() {
self.authenticated = false;
}
);
}
Questions:
1) Is this controller appropriate place for having this logic?
2) I want to have actual "is_authenticated" value. How can make this happen, if I want to fire request only once
Presumably authentication with the backend requires an actual token of some kind. I.e. you don't just set a true/false flag somewhere and call it authentication, but to be able to communicate with the backend you need to include a username/password/cookie/token in the request or the request will be denied.
A controller is a bad place to store such a thing, since controllers aren't permanent. Or at least you shouldn't make them permanent to the extent possible. Also, storing the token in a controller doesn't allow anything else access to it.
Whether you are logged in or not should be based on whether you have a valid authentication token or not.
This token must be stored in a canonical place, best suited for that is a service.
Other services get the token from there and also decide whether the app is currently "logged in" based on whether a token is available.
A rough sketch of how that should be structured:
app.service('AuthService', function () {
this.token = null;
});
app.service('FooService', function (AuthService, $http) {
$http.get(..., AuthService.token, ...)
});
app.controller('LoginStatusController', function (AuthService) {
Object.defineProperty(this, 'isLoggedIn', {
get: function () { return AuthService.token != null; }
});
});
<div ng-controller="LoginStatusController as ctrl">
<div ng-hide="ctrl.isLoggedIn">
When you actually log in and obtain a token, you set AuthService.token and it will be available to all other services and controllers. If and when the token becomes invalid or unset, all services and controllers lose their authenticated status.
What I usually do is the following :
Using ui-router
Take advantage of the resolve hook (which resolves some args and inject them into controller), and define my routes as subroutes of a main one which checks auth on each route change
scripts/services/user.js
angular.module('yourmodule')
.service('userSrv', function ($http, $q) {
var srv = {};
srv.getAuthenticatedUser = function() {
return $http.get("/authenticated_user");
};
return srv;
});
scripts/routes.js
angular
.module('yourmodule')
.config(function ($stateProvider) {
$stateProvider
.state('authenticated', {
abstract: true,
template: '<ui-view />',
controller: 'AuthenticatedCtrl',
resolve: {
signedInUser: function(userSrv, $q) {
return userSrv.getAuthenticatedUser()
.then(function(null, function() {
//Catch any auth error, likely 403
//And transform it to null "signedInUser"
//This is the place to handle error (log, display flash)
return $q.resolve(null);
});
}
}
})
.state('authenticated.myspace', {
url: '/myspace',
templateUrl: 'views/myspace.html'
});
});
Take advantage of $scope inheritance inside your view
scripts/controllers/authenticated.js
angular.module('yourmodule')
.controller('AuthenticatedCtrl', function (signedInUser, $scope, $state) {
//Here you set current user to the scope
$scope.signedInUser= signedInUser;
$scope.logout = function() {
//this is where you would put your logout code.
//$state.go('login');
};
});
views/myspace.html
<!-- here you call parent state controller $scope property -->
<div ng-hide="signedInUser">
<h1>Login</h1>
With Corporate Account: click here
</div>
1) I want to have actual "is_authenticated" value. How can make this happen, if I want to fire request only once
My solution asks for authenticated user on each route change. This seems strange but this is actualy viable and fast. The query should not be > 30ms it's a very small SELECT under the hood. THOUGH, asking "am I authenticated" and "get the authenticated user" are pretty the same thing, except one return a boolean, and the other return the user. I suggest, like I just show you, to handle "am I authenticated" question by requesting the authenticated user, then booleaning it with "if(user)" (null value handling).
2) Is this controller appropriate place for having this logic?
Yes and no. As you can see, the controller is the very place to "set the user to the scope thing", but scope inheritance allow you to not repeat it for each route. Though, http api logic should be ported to a service, and routing event ("get the authenticated user for this page, please" is a routing event IMHO) should be set in a separate file.
NB: if you need complete route "protection" (like redirection on non-authenticated, ask another question and I'll be glad to answer it)

AngularJS: Keeping a promise around to guarantee something has loaded

My AngularJS needs to load a mapping (from my API) that is needed by the rest of the application to continue to make API calls. My current solution is saving the Promise that is used to load the map and having making every future API call using promise.then(...). Is this the right solution? Is it ok to keep a promise around and repeatedly call .then() on it?
As #blackhole noted, Promise.then() on an already-resolved promise is fast. It's not zero work, but it's just a quick check.
But if this data is a pre-requisite for your application, it seems a terrible burden in CODE to have to check it every time an API call needs to be made. What if you add a second pre-requisite? It's really messy to have to check this every single time in the future.
Both ngRoute and uiRouter allow you to require a resolved promise before starting a controller. They're great patterns for this in an Angular app because you can be granular - you can have a lot of smaller pre-requisites through the app that need to be resolved before those views start.
Here's a sample for ngRoute:
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/', {
controller: 'MainCtrl',
templateUrl: 'main.html',
resolve: {
init: ['MyService', function(MyService {
return MyService.getMyData();
}]
}
});
}]);
and here's one for uiRouter:
getMyData.$inject = ['MyService'];
function getMyData(MyService) {
return MyService.getMyData();
}
$stateProvider.state('home', {
url: '^/',
templateUrl: 'main.html',
controller: 'MainCtrl',
resolve: {
myData: getMyData
}
});
Since most apps want some form of routing anyway, it's an easy pattern to let the router do this for you, but as #Dave Newton pointed out, you could also do this with .run() if you want to roll-your-own.
If you cannot or do not want to use one of the routing mechanisms, you can also manually trigger Angular's bootstrap after loading the asset. Documentation for this is provided here:
https://docs.angularjs.org/guide/bootstrap
But basically you're doing this:
angular.element(document).ready(function() {
angular.bootstrap(document, ['myApp']);
});
Then you remove the ng-app directive from your top-level DOM element, wherever you put it.

Categories

Resources