UI router nested view not resolving - angularjs

I am using UI Router Multiple Views concept.
The documentation says that i can have resolve on individual views. As you can see in my plunker, I have a resolve on my child view. The resolve returns a promise, which in my case, is resolved in the parent controller. I have simulated my problem with a setTimeOut. Basically, after 5 seconds, the child view should load (with an alert saying "In Child State") but it doesn't !!!
Relevant code is below but please instead refer to Plunker.
'child#parent': {
template: '<h4>This is Child State</h4>',
controller: childCtrl,
resolve: {
trd: function(tradeFactory) {
console.log('in resolve of child');
return tradeFactory.ready();
}
}
}

(Check updated and working plunker) The point here is:
all resolve must be already resolved, before the views (with its controllers) are rendered
And that is not true in your scenario. In fact, there is a race condition, ending up in the deadlock.
one view is waiting for a resolve (is provided with a promise)
other view's controller is ready to trigger resolve (make a promise resolved), but... it cannot be triggered, while waiting for its sibling
The original snippet
views: {
'': {
...
// inside of this, you expect to resolve the below promise
controller: parentCtrl
},
'child#parent': {
...
resolve: {
trd: function(tradeFactory) {
// this is a promise waiting for resolve
// implemented in parent controller ... deadlock
return tradeFactory.ready();
}
}
}
So, the above is not working. We have to do all inside of the resolve, e.g. this way:
views: {
'': {
...
controller: parentCtrl,
// here do the resolve
resolve: {
parentReady: function(tradeFactory){
setTimeout(function(){
tradeFactory.ResolveReady()
}, 1500);
}
}
},
'child#parent': {
...
resolve: {
trd: function(tradeFactory) {
console.log('in resolve of child');
return tradeFactory.ready();
}
}
}
There is updated plunker

Related

Does UI-Router Resolve Work with Angular 1.5 Components?

So, UI-router resolves were a thing of beauty in angular 1:
$stateProvider.state('myState', {
resolve:{
myVarFromResolve: function(){
return 'test';
}
}
})
controller: function($scope, myVar){
$scope.myVar= myVarFromResolve;
if(true){console.log($scope.myVar}; //logs 'test'
}
How do I do the same with an Angular 1.5. component (example below)?
export default function FooComponent() {
let fooComponent = {
restrict: 'E',
templateUrl: 'foo.html',
controller: controller,
controllerAs: 'foo',
bindToController: true,
};
return landingComponent;
}
And the resolve...
.state('fooState', {
parent: 'someParent',
url: '/fooUrl',
template: '<foo></foo>',
resolve:{
myVarFromResolve: function(){
return 'test';
}
}
})
I read a guide on how to do this, but I don't really understand it. Seems like the functionality isn't fully in place and that's a hack.
Looks like the answer is "Yes, kinda"...
I was given a work-around to this problem by a team member that works for angular-ui-router 0.2.18 (use npm list angular-ui-router --depth=0 to check your version quickly). It does work while using ES6 classes.
The work around is based on the fact that there is no (easy/known) way to get the variable passed into the controller, like you would in Angular 1 before component architecture introduced in 1.5. Instead, we create a service method that creates a promise (or returns an http call). Then you simply put the promise in the resolve. The promise method WILL return before the controller loads. So while you do not have a easy to use var, you have the data you need in a service.
resolve: {
preloadSomeVariableIntoAService: function (MyExampleService) {
return MyExampleService.presetSomeVariable();
}
//Note that 'preloadSomeVariableIntoAService' won't be used anywhere in our case
}
The service could be something like this:
export default class MyExampleService {
constructor( $q ) {
this.q = $q;
this.myVariableFromResolve = undefined;
}
presetStreamStatus(){
var deferred = this.q.defer();
return $http.get(someUrl).then(response) => {
this.myVariableFromResolve = response.data;
deferred.resolve(events);
});
return deferred;
};
};
You will then be able to get myVariableFromResolve from the service. I am not sure you have to use $q deferred, but it seems to be necessary since I am not simply returning the call but also setting the variable. Not sure. Hope this helps someone.

UI-Router | Inject a service(or the resolved object) in the template function of the nested views

I have a scenario where I want to render the template only when some condition is met(on the basis of data we get from the REST call). So, I did the following :
My Code (by injecting the factory into the template function):
.state('dashboard.deal.xyz', {
url: "/xyz/:dealId",
resolve: {
permissions: function(dealUnit, $state) {
console.log('Inside the resolve of permissions :::', $state.params.dealId);
return dealUnit.getUnitPermissions('123412341234DealId');
}
},
views: {
"viewA": {
template: function(dealUnit) {
//Here I want to inject either 'permissions' (the resolve object)
//or the 'dealUnit' factory which gets me the permissions from the server directly.
return '<h1>return the template after getting the permissions resolve object</h1>';
}
},
"viewB": {
template: "viewB"
}
}
})
My 'dealUnit' factory is working fine and returns an object when I use it inside the 'resolve' of the state. But, that factory is not being injected when I inject it inside the template function of the nested view.
Am I doing it correctly? And if not then how should I go about doing it ?
In case, we want to do some "magic" before returning the template... we should use templateProvider. Check this Q & A:
Trying to Dynamically set a templateUrl in controller based on constant
Because template:... could be either string or function like this (check the doc:)
$stateProvider
template
html template as a string or a function that returns an html template as a string which should be used by the uiView directives. This property takes precedence over templateUrl.
If template is a function, it will be called with the following parameters:
{array.} - state parameters extracted from the current $location.path() by applying the current state
template: function(params) {
return "<h1>generated template</h1>"; }
While with templateProvider we can get anything injected e.g. the great improvement in angular $templateRequest. Check this answer and its plunker
templateProvider: function(CONFIG, $templateRequest) {
console.log('in templateUrl ' + CONFIG.codeCampType);
var templateName = 'index5templateB.html';
if (CONFIG.codeCampType === "svcc") {
templateName = 'index5templateA.html';
}
return $templateRequest(templateName);
},

Angular UI-Router state resolve depending on another resolve

EDIT: this actually works, the problem was coming from something else.
I am struggling with an edge-case scenario, basically:
$stateProvider.state('myState', {
resolve:{
resolveA: function($q){
return $q.when('whatever');
},
resolveB: function(resolveA){
return fetchSomethingBasedOn(resolveA);
}
}
);
This does not work as resolveA can't be injected in resolveB function.
There are a few options I already considered but rejected:
Make the resolveA returned value an object {resolveA, resolveB}. Discarded because this would have many side effects on my existing controllers.
Handle the resolveB behaviour at the controller level. Discarded because this state is an abstract high level one, I don't wan't to change all its children states/controllers
ui-router nested resolve doesn't work on the same state, but it should work on nested states. You can access resolved objects from the parent state.
$stateProvider
.state('parent', {
resolve:{
resolveA: function($q){
return $q.when('whatever');
}
}
)
.state('parent.child', {
resolve:{
resolveB: function(resolveA){
return fetchSomethingBasedOn(resolveA);
}
}
);
The option that seems OK-ish is to attach the result of resolveB to the resolveA object and have only one resolve. i.e:
$stateProvider.state('myState', {
resolve:{
resolveA: function($q){
return $q.when('whatever').then(function(resolveA){
return fetchSomethingBasedOnResolveA(resolveA).then(function(resolveB){
resolveA.resolveB = resolveB;
return resolveA;
})
});
}
}
);
The solution to your problem is by using promises. You must be sure that the promise for resolveA is resolved before start with resolveB.
The code should be something like this.
$stateProvider.state('myState', {
resolve:{
resolveA: function($q){
var def = $q.defer();
//whatever you want i.e.
User.resource.get(function(perfil){
def.resolve(perfil);
});
//
return def.promise;
},
resolveB: function(resolveA){
return fetchSomethingBasedOn(resolveA);
}});
And this should solve your problems.
Greetings.

Angular-UI Router - Resolve not waiting for promise to resolve?

So to summarise I am using angular-ui router resolve function to retrieve state specific data. However it doesn't seem to full wait for the promise to resolve:
state('parent.ChildState', {
url: '/myUrl?param1&param1',
templateUrl: 'views/list.view.html',
controller: 'MyController',
resolve: {
data: resolveData
}
}).
function resolveData($stateParams, Utils) {
var filters = Utils.getFilters($stateParams);
DataService.myDataObj = DataService.get(filters, function(result, headers) {
DataService.myDataObj = result;
});
return DataService.myDataObj;
// Note I have also tried returning directly the DataService.get call however this makes all the below console log statements as undefined (see below for the controller code to know what I mean). So I do the assignment first and then return that.
}
Now in the controller I had a function that executes on load like so:
function ExpensesController(DataService) {
$scope.viewData = DataService;
initData();
function initData() {
// this generally logs a ngResource and shows the full data obj on console
console.log($scope.viewData.myDataObj);
// this gets undefined on console
console.log($scope.viewData.myDataObj.someField1);
// this log fine, however why do I need to do promise
// resolve? should be resolved already right?
$scope.viewData.myDataObj.$promise.then(function() {
console.log($scope.viewData.myDataObj.someField1);
});
As your required data to resolve is async, you need to return a promise and add return statement inside your callback function.
function resolveData($stateParams, Utils) {
var filters = Utils.getFilters($stateParams);
return DataService.get(filters, function(result, headers) {
DataService.myDataObj = result;
return DataService.myDataObj
});
}
You can read ui-router resolve docs more about how resolver works and when they should return promise or pure values.
I don;t know if I have got your problem :), but here is what I feel is wrong
1) in the resolve return a promise, it should not be resolved
function resolveData($stateParams, Utils) {
var filters = Utils.getFilters($stateParams);
return DataService.get(filters);
}
2) In the controller you should inject the data that is declared in resolve not the DataService so your controller should be
function ExpensesController(data) {
$scope.viewData = data;
}

Use response from previous resolve object

I have an angularjs route with a resolve object with multiple properties like so:
.state('user', {
url: '/user/signup',
controller: 'CreateAccountCtrl',
templateUrl: 'createaccount.tpl.html',
resolve: {
practice: {
// Returns a promise
},
someOtherFunction: {
// Returns a promise, needs resolved object from practice
},
}
})
The problem is that I need the result from one of the resolves to process the other resolve. How can I implement this? Obviously I can just put all the http calls in one function and build a custom object but I am wondering if there's a more idiomatic solution.
Turn both properties of the resolve method into functions and pass one method by name to the other as an argument.
.state('user', {
url: '/user/signup',
controller: 'CreateAccountCtrl',
templateUrl: 'createaccount.tpl.html',
resolve: {
practice: function() {
// Returns a promise
},
someOtherFunction: function(practice) {
// Returns a promise, needs resolved object from practice
}
}
})
Also, this blog post is a great resource for using Angular UI Router. I learned this approach there.
Use an IIFE to return the resolve object:
...
resolve: (function () {
var practicePromise;
var someOtherPromise;
/* initialize the variables whichever way you want*/
return { /* this is the actual "resolve" object*/
practice: practicePromise,
someOther: someOtherPromise
};
} ()),
...

Resources