I have the following setup in my AngularJS app:
.state('people.view',
{
abstract: true,
parent: 'people',
views: {
'header#maincontent': {
templateUrl: 'partials/people/_header_view.html'
},
'footer#maincontent': {
templateUrl: 'partials/people/_footer_view.html'
}
}
})
.state('people.details',
{
parent: 'people.view',
url: '/:personId/details',
views: {
'content#maincontent': {
templateUrl: 'partials/people/details.html',
controller: 'PeopleDetailCtrl'
}
}
})
.state('people.addressbook',
{
parent: 'people.view',
url: '/:personId/addressbook',
views: {
'content#maincontent': {
templateUrl: 'partials/people/addressbook.html',
controller: 'PeopleAddressBookCtrl'
}
}
})
So I have a Detail and an Address Book view that are children of a Person View state that has a header and footer (that don't change).
But because the personId is only passed in the child states and only they have a controller specified, then it means I can't access the person information.
For example in the header I want to show the person name.
How can I do this?
Based on the answer below from #mohamedrias I have tried:
.controller('PeopleDetailCtrl', ['$scope', '$rootScope', '$stateParams', 'Person',
function($scope, $rootScope, $stateParams, Person) {
$scope.person = Person.get({personId: $stateParams.personId}, function(person) {
console.log(person);
$rootScope.person.firstname = person.firstname;
$rootScope.person.lastname = person.lastname;
});
}]);
So that I can access {{person.firstname}} in the header, but I get the error:
TypeError: Cannot set property 'firstname' of undefined
The console shows:
Resource {firstname: "Cameron", lastname: "Drysdale", $promise: Object, $resolved: true, $get: function…}
In your child controller, emit the event along with personId.
$scope.$emit('personId', personId);
In your parent controller:
$scope.$on('personId', function(event, personId) {
// check for personId and process the information
});
The above is nice way of doing it.
Else you can do in normal way too, but it's not recommended.
Inject $rootScope in your child controller:
app.controller("ChildController", function($scope, $rootScope, $routeParams) {
$rootScope.personId = $routeParams.personId;
});
In your template header, you can just refer to personId and it will pick the value from $rootScope.
Update based on your code:
$rootScope.person = $rootScope.person || {};
$rootScope.person.firstname = person.firstname;
This is just to make sure that $rootScope.person is available.
Related
I'm using AngularJS's UI-Router to manage routes for my web application.
I have two states: parent_state and child_state arranged as shown below.
$stateProvider
.state('parent_state', {
abstract: true,
views: {
'#' : {
templateUrl: 'http://example.com/parent.html',
controller: 'ParentCtrl'
}
}
})
.state('child_state', {
parent: 'parent_state',
url: '/child',
params: {
myArg: {value: null}
},
views: {
'mainarea#parent_state': {
templateUrl: 'http://example.com/child.html',
controller: 'ChildCtrl'
}
}
})
From within ChildCtrl, I can access myArg like this:
app.controller("ChildCtrl", function($stateParams) {
console.log('myArg = ', $stateParams.myArg);
});
Is it possible for me to access myArg and have it displayed in the html page parent.html? If so, how can it be done? I see that the ParentCtrl controller for the abstract state is never even called.
This question addresses a related topic. But it doesn't show me how to display a parameter to the child state in a template of the parent state.
The first thing that comes to my mind is to use events for notifying parent after child param change. See the following (you can even run it here).
Child, after rendering, emits an event to the parent with the changed value of the parameter. Parent grabs and displays it in its own template.
angular.module('myApp', ['ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('parent_state', {
abstract: true,
template: "<h1>Parent! Value from child: {{ paramFromChild }}</h1><div ui-view></div>",
controller: function ($scope) {
$scope.$on('childLoaded', function (e, param) {
$scope.paramFromChild = param;
});
}
})
.state('child_state', {
parent: 'parent_state',
url: '/child',
params: {
myArg: {value: null}
},
template: '<h2>Child! Value: {{ param }}</h2>',
controller: function($stateParams, $scope){
$scope.param = $stateParams.myArg;
$scope.$emit('childLoaded', $stateParams.myArg);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.10/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.20/angular-ui-router.js"></script>
<div ng-app="myApp">
<a ui-sref="child_state({myArg: 'first'})">First link</a>
<a ui-sref="child_state({myArg: 'second'})">First second</a>
<div ui-view></div>
</div>
Is it possible for me to access myArg and have it displayed in the
html page parent.html?
That is against the principle of the UI-Router. Parent params can be consumed in children, but not vice versa. How would parent view know about changes WITHOUT re-initializing the controller? You need something like watching.
The true way is to employ Multiple Named Views. Look at this working plunkr.
Yes, this is possible.
Using $stateChangeSuccess:
You can use $stateChangeSuccess to achieve this.
For example:
.state('main.parent', {
url: '/parent',
controller: 'ParentController',
controllerAs: 'vm',
templateUrl: 'app/parent.html',
data: {
title: 'Parent'
}
})
.state('main.parent.child', {
url: '/child',
controller: 'ChildController',
controllerAs: 'vm',
templateUrl: 'app/child.html'
})
And in the runblock call it as follows:
$rootScope.$on('$stateChangeSuccess', function (event, toState, fromState) {
var current = $state.$current;
if (current.data.hasOwnProperty('title')) {
$rootScope.title = current.data.title;
} else if(current.parent && current.parent.data.hasOwnProperty('title')) {
$rootScope.title = current.parent.data.title;
} else {
$rootScope.title = null;
}
});
Then you can access the $rootScope.title from the child controller since it is globally available.
Using a Factory or Service:
By writing setters and getters you can pass data between controllers. So, you can set the data from the child controller and get the data from the parent controller.
'use strict';
(function () {
var storeService = function () {
//Getters and Setters to keep the values in session
var headInfo = [];
return {
setData: function (key, data) {
headInfo[key] = data;
},
getData: function (key) {
return headInfo[key];
}
};
};
angular.module('MyApp')
.factory('StoreService', storeService);
})(angular);
Set data from child controller
StoreService.setData('title', $scope.title)
Get data
StoreService.getData('title');
Using events $emit, $on:
You can emit the scope value from the child controller and listen for it in the parent scope.
I need to get some content data to my controller:
state('admin.businesses.employees.all', {
resolve: {
executorsListTitle: 'All employees',
executorsEmptyListMessage: 'Add the first employee'
},
url: '/all',
controller: 'ExecutorsController',
templateUrl: 'templates/admin/executors/index.html'
})
And a controller code:
module.controller(
'ExecutorsController',
[
'$scope', '$rootScope', '$state',
'$stateParams', '$modal', 'executorsListTitle',
'executorsEmptyListMessage', 'Executor',
function($scope, $rootScope, $state, $stateParams, $modal, executorsListTitle, executorsEmptyListMessage, Executor) {
// Some code
}
)
But when I try to get into this state I can't do it - click by button makes nothing; if I remove resolve from state description it works good. What I do wrong? Thanks!
The resolve of the state machine expects a key and a factory. The doc states:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}
And when you provide a string to the factory:
If string, then it is an alias for a service.
If you want to return a string, you can do the following:
state('admin.businesses.employees.all', {
resolve: {
executorsListTitle: function() {
return 'All employees';
},
executorsEmptyListMessage: function() {
return 'Add the first employee';
},
},
url: '/all',
controller: 'ExecutorsController',
templateUrl: 'templates/admin/executors/index.html'
})
If you are using static data (the strings you are resolving to), you can also use the custom data property:
state('admin.businesses.employees.all', {
data: {
executorsListTitle: 'All employees',
executorsEmptyListMessage: 'Add the first employee'
},
url: '/all',
controller: 'ExecutorsController',
templateUrl: 'templates/admin/executors/index.html'
})
If you use this method, in the controller you can access the data like this:
$state.current.data.executorsListTitle
You can use strings directly with this method. Here is the doc for the custom data property.
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..
So consider the following fragment from my angularUI routing setup. I am navigating to the route /category/manage/4/details (for example). I expect 'category' to be resolved before the relevant controller loads, and indeed it is to the extent that I can put a breakpoint inside the resolve function that returns the category from the category service and see that the category has been returned. Now putting another breakpoint inside the controller itself I can see that 'category' is always undefined. It is not injected by UI router.
Can anyone see the problem? It may be somewhere other than in the code I've provided but as I have no errors when I run the code, it's impossible to tell where the source of the issue might lie. Typical js silent failures!
.state('category.manage', {
url: '/manage',
templateUrl: '/main/category/tree',
controller: 'CategoryCtrl'
})
.state('category.manage.view', {
abstract: true,
url: '/{categoryId:[0-9]*}',
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId).then(returnData); //this line runs before the controller is instantiated
}]
},
views: {
'category-content': {
templateUrl: '/main/category/ribbon',
controller: ['$scope', 'category', function ($scope, category) {
$scope.category = category; //category is always undefined, i.e., UI router is not injecting it
}]
}
},
})
.state('category.manage.view.details', {
url: '/details',
data: { mode: 'view' },
templateUrl: '/main/category/details',
controller: 'CategoryDetailsCtrl as details'
})
The concept is working. I created working plunker here. The changes is here
instead of this
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
//this line runs before the controller is instantiated
return CategoryService.getCategory($stateParams.categoryId).then(returnData);
}]
},
I just returned the result of the getCategory...
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId); // not then
}]
},
with naive service implementation:
.factory('CategoryService', function() {return {
getCategory : function(id){
return { category : 'SuperClass', categoryId: id };
}
}});
even if that would be a promise... resolve will wait until it is processed...
.factory('CategoryService', function($timeout) {return {
getCategory : function(id){
return $timeout(function() {
return { category : 'SuperClass', categoryId: id };
}, 500);
}
}});
I have the following:
var admin = {
name: 'admin',
url: '/admin',
views: {
'nav-sub': {
templateUrl: '/Content/app/admin/partials/nav-sub.html',
controller: function ($scope) { $scope.message = "hello"; }
}
},
controller: ['$scope', function ($scope) {
$scope.message = "hello";
}]
}
var subject = {
name: 'subject',
parent: admin,
url: '/subject',
views: {
'grid#': {
templateUrl: '/Content/app/admin/partials/grid-subject.html',
controller: 'AdminGridSubjectController',
}
}
};
I would like the AdminGridSubjectController to know what the $scope.message value is but it seems not to know anything about it. Is there something I am doing wrong?
stApp.controller('AdminGridSubjectController', ['$scope', function ( $scope ) {
var a = $scope.message;
}]);
In order to access the scope of a parent controller in Angular UI Router use:
$scope.$parent
Then the parent scope is then freely available to you.
Your problem might be that the name should reflect the parent in it:
var subject = {
name: 'admin.subject',
parent: admin,
url: '/subject',
...
Here's a complete illustration of how to inherit $scope with ui-router: plunker ex
There are several ways (and workarounds) to access parent scope data ... but controller inheritance itself, is not possible: https://github.com/angular-ui/ui-router/wiki/nested-states-&-nested-views#what-do-child-states-inherit-from-parent-states