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.
Related
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.
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.
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" />
I want my Angular application to resolve a promise before changing the route to a certain path:
va.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/sendungen', {templateUrl: '../partials/sendungen.php', controller: 'OverviewCtrl',resolve: {
shipments: oc.fetchAllShipments
}}).
// ...
}]);
The funcion fetchAllShipments():
oc.fetchAllShipments = function(shipment){
shipment.fetchAllShipments().then(function(promise){
shipment.allShipments = promise.data;
});
};
The controller shall then copy the data from the shipment service to its $scope:
va.controller('OverviewCtrl',function($scope,$http,shipment){
$scope.allShipments = shipment.allShipments;
});
Everything is working fine as long as i change routes from within the application, e.g I load the mainpage, then switch to /sendungen
However, if i am already on that path and decide to refresh the page, the application is loaded before the data seems to be resolved. This happens only occasionally and seems to be depending on how fast he script was executed.
How do i prevent that behaviour?
The function in the resolve should return a promise, not like in your oc.fetchAllShipments method.
resolve - An optional map of
dependencies which should be injected into the controller. If any of
these dependencies are promises, the router will wait for them all to
be resolved or one to be rejected before the controller is
instantiated. If all the promises are resolved successfully, the
values of the resolved promises are injected and $routeChangeSuccess
event is fired.
For example:
resolve: {
shipments: ['$q', function($q){
var deffered = $q.defer();
shipment.fetchAllShipments().then(function(res){
deffered.resolve(res);
});
return deffered.promise;
}]
}
The quick and dirty fix will be to use $timeout:
va.controller('OverviewCtrl',function($scope,$http,shipment, $timeout){
$timeout(function(){$scope.allShipments = shipment.allShipments}, 1000);
});
What is the better solution to hide template while loading data from server?
My solution is using $scope with boolean variable isLoading and using directive ng-hide, ex: <div ng-hide='isLoading'></div>
Does angular has another way to make it?
You can try an use the ngCloak directive.
Checkout this link http://docs.angularjs.org/api/ng.directive:ngCloak
The way you do it is perfectly fine (I prefer using state='loading' and keep things a little bit more flexible.)
Another way of approaching this problem are promises and $routeProvider resolve property.
Using it delays controller execution until a set of specified promises is resolved, eg. data loaded via resource services is ready and correct. Tabs in Gmail work in a similar way, ie. you're not redirected to a new view unless data has been fetched from the server successfully. In case of errors, you stay in the same view or are redirected to an error page, not the view, you were trying to load and failed.
You could configure routes like this:
angular.module('app', [])
.config([
'$routeProvider',
function($routeProvider){
$routeProvider.when('/test',{
templateUrl: 'partials/test.html'
controller: TestCtrl,
resolve: TestCtrl.resolve
})
}
])
And your controller like this:
TestCtrl = function ($scope, data) {
$scope.data = data; // returned from resolve
}
TestCtrl.resolve = {
data: function ($q, DataService){
var d = $q.defer();
var onOK = function(res){
d.resolve(res);
};
var onError = function(res){
d.reject()
};
DataService.query(onOK, onError);
return d.promise;
}
}
Links:
Resolve
Aaa! Just found an excellent (yet surprisingly similar) explanation of the problem on SO HERE
That's how I do:
$scope.dodgson= DodgsonSvc.get();
In the html:
<div x-ng-show="dodgson.$resolved">We've got Dodgson here: {{dodgson}}. Nice hat</div>
<div x-ng-hide="dodgson.$resolved">Latina music (loading)</div>