I am using ui-router resolve in order to get some data from a service.
The thing is that I need to get a value from the parent $scope in order to call the service as shown bellow.
resolve: {
contactService: 'contactService',
contacts: function ($scope, contactService) {
return contactService.getContacts($scope.parentCtrl.parentObjectId);
}
}
I keep getting Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Also tried a few desperate attempts such as adding scope to the resolve object as shown bellow with not success.
scope: $scope
Any ideas?
That's impossible, scope hasn't been initialized at that point so you can't use it in the resolve object. You can access the scope in the controller after it's been initialized. The whole point of resolve is that it runs before controller initialization so that you can inject and directly access the resolved items in your scope.
If you need to pass a variable to the next state you can do that by using the $stateParams object which is available for use in resolve. You can add data to it when changing states, eg:
In your template, if you have a objectId in your scope:
<a ui-sref="statename({'id': objectId})">Change states</a>
Or in your controller:
$scope.go('statename', {'id': $scope.objectId});
You can then retrieve that in your resolve by using the $stateParams:
resolve: {
contactService: 'contactService',
contacts: function ($stateParams, contactService) {
return contactService.getContacts($stateParams.id);
}
}
As an alternative to the accepted solution, which requires another round trip to the server for the same resource (if you are getting the value from the server/api) you could $watch the parent from the child controller.
function ParentController($http) {
var vm = this;
$http.get(someResourceUrl).then(function(res) {
vm.someResource = res.data;
});
}
function ChildController($scope) {
// wait untill the parent gets the value
var unwatch = $scope.$watch('parent.someResource', function(newValue) {
if (newValue) {
// the parent has the value, init this controller
init(newValue);
// dispose of the watcher we no longer need
unwatch();
}
});
function init(someResource) {
// ... do something
}
}
function routerConfig($stateProvider) {
$stateProvider
.state('parent', {
url: '/parent',
controller: 'ParentController',
controllerAs: 'parent',
templateUrl: '...',
})
.state('parent.child', {
url: '/child',
controller: 'ChildController',
controllerAs: 'child',
templateUrl: '...',
});
}
Related
I want to resolve some value before I load the first page of my application, but it kept telling me
Unknown provider: programClassSummaryProvider <- programClassSummary <- HomeCtrl
I pretty sure I did it correctly, because I did the same thing for any other controller and routing. but it is not working for my homepage controller.
It seems like it load the controller first, before it is resolved in the routing. Anything wrong with my code?
In routing.js
$stateProvider
.state('home', {
url: '/home',
controller: 'HomeCtrl',
controllerAs: 'vm',
templateUrl: 'index_main.html',
resolve: {
programClassSummary: ['GroupDataFactory', function (groupDf) {
return groupDf.getProgramClassSummary();
}]
},
ncyBreadcrumb: {
skip: true
}
});
in controller.js
angular
.module('issMccApp')
.controller('HomeCtrl', homeCtrl);
homeCtrl.$inject = ['$scope', '$location', '$state', '$auth', 'programClassSummary'];
/* #ngInject */
function homeCtrl($scope, $location, $state, $auth, programClassSummary) {
var vm = this;
vm.isAuthenticated = isAuthenticated;
vm.programClassSummary = programClassSummary;
if (!$auth.isAuthenticated()) {
$state.go('login');
return;
}
function isAuthenticated() {
return $auth.isAuthenticated();
}
}
in factory.js
function getProgramClassSummary(showAll) {
var query = "";
if (showAll)
query = APIConfigObj.base_url + '/api/group/infor/programclasssummary?all=1';
else
query = APIConfigObj.base_url + '/api/group/infor/programclasssummary';
return $http.get(query)
.success(function (result) {
return result;
})
.error(function (err) {
return err;
})
}
I'd say, we really have to distinguish the UI-Router state world, and angular itself. Reason why is clearly defined here (extracted $resolve from UI-Router API documentation):
$resolve
resolve(invocables, locals, parent, self)
Resolves a set of invocables. An invocable is a function to be invoked via $injector.invoke(), and can have an arbitrary number of dependencies. An invocable can either return a value directly, or a $q promise. If a promise is returned it will be resolved and the resulting value will be used instead. Dependencies of invocables are resolved (in this order of precedence)
from the specified locals
from another invocable that is part of this $resolve call
from an invocable that is inherited from a parent call to $resolve (or recursively
from any ancestor $resolve of that parent).
There is a wroking plunker, which uses this index.html
<body ng-controller="RootCtrl">
a summary for a root:
<pre>{{summary}}</pre>
<ul>
<li>home
<li>other
</ul>
<div ui-view=""></div>
So, here we use some RootCtrl, which won't go through state machine UI-Router, it is angular basic stuff
The root controller must be defined as
.controller('RootCtrl', ['$scope', 'GroupDataFactory', function ($scope, groupDf) {
$scope.summary = groupDf.getProgramClassSummary();
}])
For a state home, we can use different approach, in fact the same as above (simplifed version below)
.state('home', {
url: "/home",
templateUrl: 'tpl.home.html',
resolve: {
programClassSummary: ['GroupDataFactory', function (groupDf) {
return groupDf.getProgramClassSummary();
}]
},
controller: 'HomeCtrl',
})
And its controller is now able to consume the locals
.controller('HomeCtrl', ['$scope', 'programClassSummary', function ($scope, summary) {
$scope.summaryForHome = summary;
}])
Check it in action here
I have defined my UI-Router states like this:
$stateProvider.state('contact', {
url: '/contactDemo',
views: {
'main': {
controller: 'contactMainController',
templateUrl: 'templates/contact.tpl.html'
}
}
}).state('contact.details', {
abstract: true,
controller: 'contactDetailsController',
templateUrl: 'templates/contact.details.tpl.html'
}).state('contact.details.add', {
url: '/add'
}).state('contact.details.filter', {
url: '/filter/{filterId}'
}).state('contact.details.filter.record', {
url: '/record/{recordId}',
resolve: {
record: ['$stateParams', 'Restangular', function($stateParams, Restangular) {
return Restangular.one('records', $stateParams.recordId).get();
}]
}
}).state('contact.details.filter.record.edit', {
url: '/edit'
});
Now I would like to inject my resolved record into contactDetailsController. If I do so, I get an Unknown provider error. I can't move the resolve into the abstract state, because from there I can't access the id inside $stateParams.
If I move the controller property down into the child state, my controller is never invoked.
Does anybody know how I can get the resolved property injected into the controller of an abstract parent state?
As documented here, resolve could be inherited. NOT injected from child to parent.
Inherited Resolved Dependencies
New in version 0.2.0
Child states will inherit resolved dependencies from parent state(s), which they can overwrite. You can then inject resolved dependencies into the controllers and resolve functions of child states.
$stateProvider.state('parent', {
resolve:{
resA: function(){
return {'value': 'A'};
}
},
controller: function($scope, resA){
$scope.resA = resA.value;
}
})
.state('parent.child', {
resolve:{
resB: function(resA){
return {'value': resA.value + 'B'};
}
},
controller: function($scope, resA, resB){
$scope.resA2 = resA.value;
$scope.resB = resB.value;
}
Simply solution where:
$stateParam is defined in the child
the resolve dependent on it should be injected into parent
cannot work - because one parent will be shared for many children.
Parent will stay while the children's params will differ..
Besides ui-router, I am using ui-bootstrap's $modal service.
I use resolves (actually passed inside a modal) on the onEnter property of the state (with url parameters) to activate modals (as mentioned in the docs|FAQ of ui-router).
I tried to access the $stateParams, however it seems to be an empty object when the resolves fire.
function onEnter($modal, $state) {
// simple handler
function transitionToOverlay() {
return $state.transitionTo('parent');
}
// actual modal service
$modal
.open({
size: 'sm',
resolve: { getY: getY },
controller: 'ChildCtrl as child',
template: template
})
.result
.then(transitionToOverlay)
.catch(transitionToOverlay);
}
// resolve
function getY($state, $stateParams) {
console.log('State resolve getY...');
console.log($stateParams); // returns {} empty object
return 'y'; // just a dummy resolve
}
Here's a plnkr for demonstration purposes.
UI-Router doesn't have any control over your $modal call. Resolves should go on state definitions if you would like UI-Router to inject them.
var state = {
url: '{id}',
name: 'parent.child',
resolve: { getY: getYfn }, // moved from $modal call
onEnter: function(getY) { // injected into onEnter
$modal.open({
resolve: { getY: function () { return getY; } }, // passed through to $modal.open
controller: 'ChildCtrl as child', // ChildCtrl injects getY
});
}
}
Just posting this in case someone has the same problem...
I had the same problem as in the original question but the selected answer didn't help me too much since I couldn't get to access the resolve defined directly in the state inside my modal controller.
However, I noticed $stateParams is still accessible in the onEnter function so it is possible to create a variable here and then use this variable inside the $modal.open() function.
.state('parent.child', {
url: 'edit/:id',
// If defining the resolve directly in the state
/*resolve: { // Here $stateParams.id is defined but I can't access it in the modal controller
user: function($stateParams) {
console.log('In state(): ' + $stateParams.id);
return 'user ' + $stateParams.id;
}
},*/
onEnter: function($modal, $stateParams, $state) {
var id = $stateParams.id; // Here $stateParams.id is defined so we can create a variable
$modal.open({
templateUrl: 'modal.html',
// Defining the resolve in the $modal.open()
resolve: {
user: function($stateParams) {
console.log('In $modal.open(): ' + $stateParams.id); // Here $stateParams.id is undefined
console.log(id); // But id is now defined
return 'user ' + id;
}
},
controller: ChildCtrl,
controllerAs: 'ctrl'
})
.result
.then(function(result) {
return $state.go('^');
}, function(reason) {
return $state.go('^');
});
}
})
Here is an example plnkr : http://plnkr.co/edit/wMMXDSsXLABFr0P5q2On
Also, if needing to define the resolve function outside the configuration object, we can do it like this:
var id = $stateParams.id;
$modal.open({
resolve: {
user: myResolveFunction(id)
},
...
});
And:
function myResolveFunction(id) {
return ['MyService', function(MyService) {
console.log('id: ' + id);
return MyService.get({userId: id});
}];
}
My question is two fold:
1 - I have a parent state and several child states using ui.router, there's one object (stored in mongodb) that is need in all states, but it gets updated by all states. In this scenario does it make sense to use the parent state's resolve option to populate the object?
2 - If this is the proper way to do this, how can I update that "reference" (the mock service injector created by the ui.router) to that object from every state.
To help in explain he's a example of the idea (lot's of code ommited)
.state('parent',resolve:{objectX:return x.promise;},...);
.controller('childstateCtrl',$scope,objectX){
$scope.data.objectX = objectX;
$scope.someEvent =function(){
// something updates objectX on the scope
}
}
.controller('otherChildCtrl',$scope,objectX){
// how to get the updated objectX?
}
Thanks in advance
Not fully sure if I can see where is the issue... but if you are searching for a way how to share access to updated reference, it should be easy. There is an example
Let's have these states
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view></div>',
resolve: {objectX : function() { return {x : 'x', y : 'y'};}},
controller: 'rootController',
})
.state('home', {
parent: "root",
url: '/home',
templateUrl: 'tpl.example.html',
})
.state('search', {
parent: "root",
url: '/search',
templateUrl: 'tpl.example.html',
})
.state('index', {
parent: "root",
url: '/index',
templateUrl: 'tpl.example.html',
})
Working with only one controller (for a root state):
.controller('rootController', function($scope, objectX){
$scope.data = { objectX: objectX };
})
And for this example, this is shared template:
<div>
<h3>{{state.current.name}}</3>
x <input ng-model="data.objectX.x"/>
y <input ng-model="data.objectX.y"/>
</div>
So, in this scenario, parent (root) has injected an object data into $scope. That reference is then inherit as described here:
Scope Inheritance by View Hierarchy Only
Check that example in action here. If you need more details (than in the link above, check this Q&A)
you could store it in a service.
.service("myService", function($q) {
// the actual data is stored in a closure variable
var data = undefined;
return {
getPromise: function() { // promise for some data
if (data === undefined) // nothing set yet, go fetch it
return $http.get('resourceurl').then(function(value) { data = value; return data; });
else
return $q.when(data); // already set, just wrap in a promise.
},
getData: function() { return data; }, // get current data (not wrapped)
setData: function(newDataVal) { data = newDataVal; } // reset current data
}
})
// `parent` wont' enter until getPromise() is resolved.
.state('parent', resolve:{objectX: function(myService) { return myService.getPromise(); } });
.controller('childstateCtrl', $scope, myService) {
$scope.data.objectX = myService.getData();
$scope.someEvent = function(someData){
myService.setData(someData);
}
}
.controller('otherChildCtrl', $scope, myService){
// how to get the updated objectX?
var data = myService.getData();
}
I am a bit confused. I am trying to get my resolve data in my controller. I read about these (and more) solutions, but can not get it working. It is all about the "spages".
http://jsfiddle.net/Avb4U/1/
http://www.jvandemo.com/how-to-resolve-angularjs-resources-with-ui-router/#
Angularjs resolve with controller as string
I hope this is not a duplicate question, because nothing did work for me from these solutions.
This is (part of all states) my state:
.state('root.search', {
url: '/search/:searchterm',
onEnter: ['$rootScope', function($rootScope) {
$rootScope.bodyclass = 'search';
}],
resolve : {
spages: ['$stateParams', 'SearchPages', function($stateParams, SearchPages) {
return SearchPages.get({'searchterm': $stateParams.searchterm});
}]
},
controller: 'searchCtrl',
views: {
'#': {
templateUrl: templateUrlFunction('search')
}
}
})
This is part of my controller:
app.controller('searchCtrl', ['$scope', 'spages', function($scope, spages) {
// spages should be already resolved and injected here
}
And this is my factory for the searchpages:
app.factory('SearchPages', ['$resource', function($resource) {
return $resource(null, {},
{
get: {
method: 'GET',
url: '/json/search/get/searchterm/:searchterm',
params: {searchterm: '#searchterm'}
}
});
}]);
Asfar as i do understand, spages should be resolved and injected in the controller now. But it is not.
The error i get is:
Error: [$injector:unpr] Unknown provider: spagesProvider <- spages
What do i do wrong? I am still learning...
Ok, what i did not realize is that i also have a controller in my template.
And i read: "You only need to specify controller once, in app.js. The second one, in the html code, is instantiated by the ngController directive, which does not know about `resolve', so it throws an exception."
Change your controller dependency from searchpages to SearchPages.
app.controller('searchCtrl', ['$scope', 'SearchPages', function($scope, searchpages) {
// searchpages should be already resolved and injected here
}