Angular: force resolve again - javascript

My dependencies to be resolved rely on data which changes. How do you force angular to resolve dependencies again?
$routeProvider.
when('/blah', {
templateUrl: '/static/views/myView.html',
controller: 'myCtrl',
resolve: {
theData: function(myFactory) {
return myFactory.promise;
}
}
}).
i.e, every time this when block is executed I want to re-resolve the dependency.
Is this trivial, or not so much?

Resolve block is rechecked every time route changes. In your case you need to return new promise each time, instead of how you are doing it currently. Looks like you cache and return always the same promise object.
resolve: {
theData: function(myFactory) {
return myFactory.getSomething();
}
}
Make myFactory.getSomething return new promise in each invocation.

Related

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.

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.

Promise dependency resolution order in angular ui-router

I have set up a top-level controller that is instantiated only when a promise (returned by a Config factory) is successfully resolved. That promise basically downloads the Web app configuration, with RESTful endpoints and so on.
$stateProvider
.state('app', {
url: '/',
templateUrl: 'views/_index.html',
controller: 'MainCtrl',
resolve: {
config: 'Config'
}
});
This setup allows me to kind-of assert that the configuration is properly loaded before any lower controller gets a chance to use it.
Now I need to inject, in a deeper nested controller, another factory that uses Config and only works when it is resolved (look at it like a $resource wrapper that needs some Web service URLs). If I do:
$stateProvider
.state('app.bottom.page', {
url: '/bottom/page',
templateUrl: 'views/_a_view.html',
controller: 'BottomLevelCtrl',
resolve: {
TheResource: 'MyConfigDependingResource'
}
});
it looks like the resolve evaluation order does not follow the controller hierarchy from top to bottom, but from bottom to top, therefore:
app.bottom.page is entered
ui-router attempts to resolve MyConfigDependingResource, but the injection fails,
because Config has never been initialized
The ui-router resolution stops because of an error (without even throwing Errors, but that's another issue), and Config is never initialized by the top level controller
Why is ui-router resolving dependencies in a reverse order? How can I easily resolve my TheResource object after the top level MainCtrl has resolved Config (without relying on $inject, of course)?
UPDATE: from this plnkr's log you can see that the top level resolve is attempted only after the nested controller has started its own resolving process.
Similarly to #Kasper Lewau's answer, one may specify a dependency on resolves withing a single state. If one of your resolves depends on one or more resolve properties from the same resolve block. In my case checkS relies on two other resolves
.state('stateofstate', {
url: "/anrapasd",
templateUrl: "views/anrapasd.html",
controller: 'SteofsteCtrl',
resolve: {
currU: function(gamMag) {
return gamMag.checkWifi("jabadabadu")
},
userC: function(gamUser, $stateParams) {
return gamUser.getBipi("oink")
},
checkS: ['currU', 'userC', 'gamMag', function(currU, userC, gamMag) {
return gamMag.check(currU, userC);
}]
}
})
**PS: **Check the "Resolves" section of the following document for more details about the inner-workings of resolve.
Resolve objects run in parallel, and as such doesn't follow a certain hierarchy.
However, nested resolves are possible by defining a higher order resolve object as a dependency to your secondary resolves.
$stateProvider
.state('topState', {
url: '/',
resolve: {
mainResolveObj: ['$someService', function ($someService) {
return 'I am needed elsewhere!';
}]
}
})
.state('topState.someOtherState', {
url: '/some-other-place',
resolve: {
someOtherResolveObj: ['mainResolveObj', function (mainResolveObj) {
console.log(mainResolveObj); //-> 'I am needed elsewhere!'.
}]
}
});
Kind of a bad example, but I take it you get the gist of it. Define the name of a higher level resolve object as a dependency to your lower level resolve and it'll wait for it to resolve first.
This is how we've solved preloading certain data before lower order resolve objects, aswell as authentication requirements (among other things).
Good luck.
I agree with you that the resolves should chain, and I've hit many problems around this area.
However, they don't, so the solution I have come up with is to use my own promise stored in a service which you resolve when the data is complete. I have tried my best to edit your plunkr to work but to no avail. I am no longer hitting your errors though, so hopefully you can work from this: http://plnkr.co/edit/Yi65ciQPu7lxjkFMBKLn?p=preview
What it's doing:
Store the config in a state alongside a new promise object
myapp.service('state', function($q) {
this.load = $q.defer();
this.config = {}
})
On your first resolve, store your config in this service and once ready resolve the new promise we created above.
myapp.factory('Config', function($http, $log, state) {
return $http.get('example.json')
.then(function (data) {
angular.extend(state.config, data.data);
state.load.resolve();
});
});
The final and most important step is to not call our second the content of the childs resolve function until after our promise above is resolved:
myapp.factory('MyConfigDependingResource', function($log, state) {
return state.load.promise.then(function() {
if (!state.config.text) {
$log.error('Config is uninitialized!');
}
else {
// Do wonderful things
}
return
});
});
The main thing you will need to be aware of is that the config is now stored in a state. There should be ways around this but this is how I've done it before.
While they cannot be chained, you can call one from another:
var resolve2 = ['$stateParams',function($stateParams){
// do resolve 2 stuff
...
return true;
}];
var resolve1 = ['$stateParams',function($stateParams){
// do resolve 1 stuff
...
// now do resolve2 stuff
return $injector.invoke(resolve2,this,{$stateParams:$stateParams});
}];
$stateProvider.state("myState", {
resolve: {
resolve1: resolve1
}
});

Controller is loaded before resolve is resolved in AngularJS

My AngularJS resolve doesn't finish before the controller is loaded.
Background: Say I have a "my profile" page that is requested (user loads website.com/user). I would like to have angular intercept the request and ask for the appropriate data from the server.
The server will reply differently depending on whether the user is logged in/active/etc. I would like angular to get the data from the server before it loads the template, and before it initiates the appropriate controller. So far it does not.
App.js code:
.when('/user',
{
templateUrl:'/templates/user',
controller: 'my_profile',
resolve: {
some_cute_function_name: function($timeout){
$timeout(function(){
console.log('here123');
},5)
}
}
Meanwhile, controller code:
console.log('about to update scope');
It turns out that the controller is initiated before the resolve is done. I know this because the greater the number of seconds before the $timeout is done, the more likely I am to see this:
about to update scope
here123
as opposed to the other way around.
Is there any way to ensure that the resolve is resolved before the controller is initiated?
Much appreciated!
Your resolver needs to return a promise:
.when('/user',
{
templateUrl:'/templates/user',
controller: 'my_profile',
resolve: {
some_cute_function_name: function($timeout){
return $timeout(function(){
console.log('here123');
return { some: 'data' };
},5);
}
}
AngularJS describes resolve as:
"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." (emphasis added)
I take it that unless any of the dependencies are promises, angular won't wait for resolve to finish before instantiating the controller (even if I have operations that take a long time to finish, like a $timeout).
Solution: you have to make sure the resolve involves a promise.

Angular resolve not working as expected on refresh

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);
});

Categories

Resources