What do do while resolve function is resolving? - angularjs

I like the ability for Angular's router to 'resolve' data for me before transferring control to a controller. How can I show something to the user (spinner, "loading...", etc.) while waiting for a route's resolve function to complete, in the case of an ajax call getting run for the resolve function?
Here's an example from a router that shows what I mean:
.when('/users/:userId/profile', {
templateUrl: 'views/profile.html',
controller: 'ProfileCtrl',
resolve: {
userProfile: ['$route', 'ProfileService', function ($route, ProfileService) {
return ProfileService.loadProfile($route.current.params.userId);
}]
}
})
As I understand it, "ProfileCtrl" does not get created until "userProfile" has been resolved. That means I can't put code there to run while waiting for the resolve function to complete. In my case "ProfileService.loadProfile" makes and HTTP request, so for the sake of this example, let's say it takes a few seconds to return.
What's the recommended way to show something to the user while waiting for this?

You can use $routeChangeStart and $routeChangeSuccess to set some boolean that is used to display a loading animation or whatever in your view:
angular.module('myApp').run(function($rootScope) {
$rootScope.$on('$routeChangeStart', function() {
$rootScope.isLoading = true;
})
$rootScope.$on('$routeChangeSuccess', function() {
$rootScope.isLoading = false;
})
})
in your template you'd have something like:
<div class='loading' ng-show='isLoading'></div>
Or since that is linked to a template which may or may not be available, put class on the page body:
<body ng-class='loading: isLoading'>
</body>
and style it however you like.
As per https://docs.angularjs.org/api/ngRoute/service/$route 'Once all of the dependencies are resolved $routeChangeSuccess is fired.' There is also routeChangeError if you want to tell users when there is a problem with your ajax/resolve. ui-router has analogous stateChangeStart etc.

Related

AngularJS UI-Router Access $stateParams from State's data object

Given the following state in ui-router:
.state('some.state', {
url: '/some/:viewType',
templateUrl: 'myTemplate.html',
controller: 'SomeStateController',
controllerAs: 'vm',
data: {
authorizedFor: [SOME_ROLE]
}
}
I'm trying to use the "data" object for a state to help control access to authorized states. Separately, I handle the $stateChangeStart event to look at data.authorizedFor and act accordingly.
The problem, though, is that the list of authorized roles might change based on the value of :viewType. I thought I could let data:{} be a function, inject $stateParams, and handle the logic there...but that won't do.
So, I tried using the params object instead, but at the $stateChangeStart time, the :viewType is not yet accessible from $state.params or $stateParams.
Stepping through in dev tools, I noticed that $state.transitionTo.arguments is populated, but it seems awfully hacky to go that route.
params: {
authorizedFor: function($state) {
console.log($state.transitionTo.arguments[1].viewType); // has value I need
}
}
Any suggestions?
My suggestion is to use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.
for example:
$stateProvider
.state('profile', {
url: '/profile',
templateUrl: 'profile.html',
resolve:{
'ProfileService': function(ProfileService){
return ProfileService.promise_skillRecommendation_mock;
}
}
})
The profileService code:
var app = angular.module('app').service("ProfileService", function($http){
var myData = null;
var promise_skillRecommendation_mock =
$http.get('Mock/skillRecommendation-mock.json')
.success(function(data){
myData = data;
});
return{
promise_skillRecommendation_mock: promise_skillRecommendation_mock,
get_skillRecommendation: function(){
return myData;
}
};
});
and the controller code which will use this service is:
angular.module('app').controller('ProfileController', function($scope, $http, ProfileService){
$scope.skillRecommendation = ProfileService.get_skillRecommendation();
The object in resolve below must be resolved (via deferred.resolve() if they are a promise) before the controller is instantiated. Notice how each resolve object is injected as a parameter into the controller.
by using this code, the page will be displayed only after that the promise will be resolved.
for more info please view this page: https://github.com/angular-ui/ui-router/wiki

Resolve must contain all promises even from controller?

Probably it's just as easy as I think it is, but I cannot really find an answer to my question on the internet, so I hope you guys know the answer just by looking at a small piece of my code.
Problem: I'm using the UI router in Angular and it loads the template before all the data is loaded. So all input fields receive the correct values AFTER the template is already loaded. So the input fields are empty for a second or two....
I think my resolve is not as it should be:
So my ui-router code looks something like this (check the resolve object):
$stateProvider.state('teststate', {
url: '/test/',
templateUrl: 'app/page/template.html',
controller: 'testCtrl',
resolve: {
access: ["Access", function(Access) { return Access.isAuthenticated(); }],
UserProfile: 'UserProfile'
}
});
Now the controller contains the promise to get some data from an API url:
function TestCtrl($scope, $state, $stateParams, TestService) {
TestService.get($stateParams.id).then(function(response) {
$scope.data = response;
});
}
Now the service (which connects to the API) should return the promise to the Controller:
TestService.factory('TestService', ['Restangular', function(Restangular) {
var factory = {};
factory.get = function(id) {
return Restangular.one('api/test', id).get();
}
return factory;
}]);
Now, could the problem be, that because the TestService.get() (which connects to the API) within the Controller, gets executed NOT before the template is loaded, because it's not inside the resolve object? So the UI router doesn't resolve the call to the API? I'm just curious or I should move all methods which make API calls, to the resolve object of each stat inside the $stateProvider.
I could run a lot of tests, but if someone just directly knows the answer by just looking at this question, it helps me a lot.
Your assumptions are all correct.
If you resolve the TestService.get in routing config the data would be readily available to controller as an injectable resource
If you don't want your controller to run and your template to show before all your API calls are finished, you have to put all of them inside ui-routers resolve.
However, if API requests can take a little while it seems better UX to transition to the new page immediately and show some kind of loading indicator (e.g. block-ui) while your API call is running.

Angular Ui-router Resolve concept

I was reading the docs of ui-router but I couldn't grasp the concept of resolves for controllers in each state. I am not able to figure out where should we use resolve and why the controller attached to a state is not enough (as we can inject any dependencies in it we want) ?
I've tried going through docs and other tutorials several times but its quite confusing , Can someone please explain it with its real life application?
Imagine you want to create a modal and pass some data to it. I'm using the angular-ui-bootstrap modals for this example.
var openExampleModal = function () {
var modalInstance = $modal.open({
templateUrl: "Modal.html",
controller: "ModalController",
size: "lg"
});
return modalInstance.result;
};
Now if you want to pass some data to this modal on initialization, you can either save it in your $rootScope or some data service, or you can use resolve to inject it into your controller directly without having to use anything else.
var openExampleModal = function (myData) {
var modalInstance = $modal.open({
templateUrl: "Modal.html",
controller: "ModalController",
size: "lg",
resolve: {
sampleData: function () {
return myData;
}
}
});
return modalInstance.result;
};
and in your controller you would have:
MyController.$inject = ["sampleData"];
function Mycontroller(sampleData) {
//You can access the data you passed on via sampleData variable now.
};
Resolve is used to inject your own custom objects into the controller, not for injecting dependencies.
A resolve is simply a value that is passed to the controller upon instantiation (which are used like an injected value). The neat thing about them is that if the value returned is a promise, the view/controller won't load until the promise has resolved.
The way you use them is by adding a resolve key to your route state, and returning the object you want injected into your controller (also naming it). For example:
.state('example', {
url: '/page',
templateUrl: 'sometemplate.html',
controller: 'SomeCtrl',
resolve: {
injectionName: function(){
// return a value or promise here to be injected as injectionName into your controller
}
}
});
Then inside your controller you simply add the resolve name to the controller injected values:
.controller('SomeCtrl', function($scope, injectionName){
// do stuff with injectionName
});
Just note that if you do return a promise, the value that is injected is the result of the promise (not the promise itself). Also note that if the promise errors the view/controller will not load, and an error will be thrown. As #koox00 commented, this error will fail silently unless $stateChangeErrorError is handled (usually in your apps primary run() function).
So why would you use this? Well if not inferred from above, you do this usually when you want your view/controller to wait until some async process has completed before loading a particular state. This saves you from creating loaders or loading processes for every single view/controller, moving it to a simple definition of what needs to be loaded.
As said by Jean-Philippe you can use resolve if you want to load some data before switching to a certain state. Resolve waits and blocks until the data is arrived and only then the state transition is done.
It is an highly discussed topic whether using a resolve or loading the data on the fly within the controller. I would say: It depends on your use case :)
Further info from supercool todd motto: https://toddmotto.com/resolve-promises-in-angular-routes/

How to update data on entering state in AngularJS UI-Router?

I'm using UI-Router for AngularJS and here is the question - at the moment when I'm clicking a link which sends me to specific state (using ui-sref) I want to send AJAX request to back end, get data, and render them in the template related to this new state. To which event should I listen for making AJAX request? Could you please give me a code example for this listener? I understand that the question seems to be simple, but I'm new in AngularJS world.
Thank you.
Have a look at the resolve property on your state configuration. See ui-router wiki
Then inject the resolved property as a dependency of the controller.
Example:
$stateProvider.state('about', {
templateUrl: 'about.html',
controller: 'AboutController',
resolve: {
something: function ($http) {
// make ajax request
return $http.get(...).then(function (response) {
return response.data;
});
}
}
})
In the controller, we inject the data that will be resolved, i.e. something :
app.controller('AboutController', function (..., something, ...) {
// The data resolved by ui-router is ready when the controller is instantiated
}

Angular Route Precheck

I have angular app
<body ng-app="appName">
<div class="container" ng-view=""></div>
I have routes
$routeProvider
.when('/', {
templateUrl: 'partials/main',
controller: 'MainCtrl'
})
.when('/login', {
templateUrl: 'partials/login',
controller: 'LoginCtrl'
})
I want to call a service before every route. Say for example if I have not loaded the profile data I want load profile data and store it in $rootscope. How should I do this?
You can use the $route's resolve property to call a function that will be executed prior to the route change:
From the AngularJS API Docs:
resolve - {Object.<string, function>=} - 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. If any of the promises are rejected the $routeChangeError event is fired. The map object is:
This is usually meant to inject the route's controller with additional parameters but there's no reason you could do more.
$routeProvider
.when('/login',{
templateUrl : 'partials/login',
controller: 'loginCtrl',
resolve : {
some_extra_controller_param : ['$route','someService',function($route,someService){
// do stuff here that you would feel necessary to have done prior to route change
someService.doSomething();
return true; // or return an object of data maybe your controller could use
]}
}
});
Of course the some_extra_controller_param will be injected into your controller as the last parameter so make sure you return something in the resolve, loginCtrl might look like this:
.controller('loginCtrl',['$scope','some_extra_controller_param',function($scope,some_extra_controller_param){
...
]});
EDIT: You may want to setup the resolve function to use promises as the route's controller will wait on promises to be "resolved" before instantiating the controller.
EDIT:
var myBeforeRouteCheck = {
something_to_be_resolved : ['$route','someService',function($route,someService){
// assuming your service runs some kind of function that returns a promise
return someService.someFunc().then(
function(data){
...do successful things...;
return somethingToInjectedParam;
},
function(){
... error ...
return false;
});
}]
};
then in your route:
.when('/login',{
...
resolve: myBeforeRouteCheck
}

Resources