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.
Related
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.
Basically we have a lot of controllers and templates and we wanted to dynamically load both the controller and the template for a given route.
Something along the lines of:
$routeProvider.when('/page/:page', {
templateUrl: fuction(params) {
return '/templates/pages/' + params.page;
},
controller: function(params) {
return params.page + 'Controller';
}
})
However we don't receive the params argument for the controller function. Is there any way to achieve this kind of flexibility?
I guess you can use the $injector.instantiate function which will invoke the given function using new with the dependencies injected. Since a controller is used as a constructor you just need to wrap the controller function like this:
controller: function () {
return $injector.instantiate(function (params) { ... });
}
This is just a guess, I don't guarantee it will work.
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
};
} ()),
...
I am currently populating $scope arrays like this:
$scope.categories = [];
$scope.getCategories = function() {
ItemService.getCategories().success(function(categories) {
$scope.categories = categories;
});
}
$scope.getCategories();
Is this really the easiest way to do it?
Do you actually need a getCategories function ? If this is the only place it's called, then you can just remove and leave the service call in directly.
Otherwise your code is as short as it gets for using promises I think, if you're using v1.2. Before 1.2, angular did have automatic promise unwrapping. That is, instead of
//$scope.getCategories = function() {
ItemService.getCategories().success(function(categories) {
$scope.categories = categories;
});
//}
You could do this instead, which seems much more elegant:
//$scope.getCategories = function() {
$scope.categories = ItemService.getCategories();
//}
Issues with this mean it's recently been removed though - https://github.com/angular/angular.js/issues/4158 . I think it's possible an alternative will be added in the future if there's a good way to do it though.
You could simply use the resolve property of your router (assuming you're using a router) to inject the results from ItemService.getCategories() into your controller. resolve properties block the route from completing until they're fully resolved--it unwraps promises automatically. The code would look something like:
angular.module('MyModule').config(function ($routeProvider) {
$routeProvider.when('/mypage', {
templateUrl: '/mypage.html',
controller: 'MyController',
resolve: {
categories: function (ItemService) { return ItemService.getCategories(); }
}
});
});
angular.module('MyModule').controller('MyController', function (categories, $scope) {
$scope.categories = categories;
});
I saw some sample code here Delaying AngularJS route change until model loaded to prevent flicker
And straight away I though this was the right way to go, I need to have my controller LOAD only when a resolve is finished loading, normally most of the examples around tell you to put the code under resolve in the routeprovder as an inline function, but this sounds wrong. The controller needs it so why not have the controller implement the function to resolve. This sounded just what I was looking for ie. This seems to use the prototype pattern ??
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function(Phone, $q) {
// see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
}
}
Problem is I have my controller like so
'use strict';
angular.module('TestApp')
.controller('ItemsCtrl', function ($scope) {
});
So how do I apply a new function on the controller when my controller is declared inside a module ?
What I really need is something like
TestCtrl.resolve = {
items: function( $q) {
..........
return deferred.promise;
}
}
This then would allow me to have in my routeprovider..
when('/items', {
templateUrl: 'views/items.html',
controller: 'TestCtrl',
resolve: 'TestCtrl.resolve'}). // Need to use ' around resolve?
But I am confused how I would get this to work ?
I would really love any feedback, I am at a loss.
Not possible to define like 'TestCtrl.resolve' if you want to use resolve with .controller syntax then you have to define inline within route provider . The advantage of inline resolve in routeprovider is that you can reuse controller easily but using same controller and changing the logic in resolve function
You can also use a service:
resolve: {data : function(service) {
return service.getData();
}}