I have an abstract parent view that is meant to share a controller with its nested views.
.state('edit', {
abstract: true,
url: '/home/edit/:id',
templateUrl: 'app/templates/editView.html',
controller: 'editController'
})
.state('edit.details', {
url: '/details',
templateUrl: 'app/templates/editDetailsView.html'
})
.state('edit.info', {
url: '/info',
templateUrl: 'app/templates/editInfoView.html'
})
The routing works as expected.
The problem is that when I update a $scope variable from one of the nested views, the change is not reflected in the view. When I do the same from the parent view, it works fine. This is not situation that requires an $apply.
My guess is that a new instance of editController is being created for each view, but I'm not sure why or how to fix it.
The issue here would be related to this Q & A: How do I share $scope data between states in angularjs ui-router?.
The way how to solve it is hidden in the:
Understanding Scopes
In AngularJS, a child scope normally prototypically inherits from its parent scope.
...
Having a '.' in your models will ensure that prototypal inheritance is in play.
// So, use
<input type="text" ng-model="someObj.prop1">
// rather than
<input type="text" ng-model="prop1">.
And also this
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Having that we should do this in edit Controller
controller('editController', function ($scope) {
$scope.Model = $scope.Model || {SomeProperty : "xxx"};
})
And we can even reuse that controller: 'editController' (we can do not have to, because the $scope.Model will be there - thanks to inheritance)
.state('edit', {
abstract: true,
url: '/home/edit/:id',
templateUrl: 'app/templates/editView.html',
controller: 'editController'
})
.state('edit.details', {
url: '/details',
templateUrl: 'app/templates/editDetailsView.html',
controller: 'editController'
})
.state('edit.info', {
url: '/info',
templateUrl: 'app/templates/editInfoView.html',
controller: 'editController'
})
Now, the same controller will be instantiated many times (parent all the children) but the $scope.Model will be initiated only once (inside of parent) and available everywhere
Check this similar working example here
Based on a comment by PilotBob
Is it possible to do this when using the controllerAs pattern giving the child state it's own controller?
I decided to append another solution, using controllerAs while keeping the above/original concept
There is a working plunker
The states would be now having different controllers and parent state will name it "parentCtrl" (to be NOT overwritten in a child scope with child controller)
.state("main", {
controller:'mainController',
controllerAs: "parentCtrl",
...
.state("main.1", {
parent: 'main',
controller:'child1Controller',
controllerAs: "ctrl",
...
.state("main.2", {
parent: 'main',
controller:'child2Controller',
controllerAs: "ctrl",
...
And these are controllers:
.controller('mainController', function ($scope) {
this.Model = {Name : "yyy"};
})
.controller('child1Controller', function ($scope) {
$scope.Model = $scope.parentCtrl.Model;
})
.controller('child2Controller', function ($scope) {
$scope.Model = $scope.parentCtrl.Model;
})
Check it in action here
Another alternative using resolve
.state('edit', {
abstract: true,
url: '/home/edit/:id',
templateUrl: 'app/templates/editView.html',
controller: 'editController',
resolve: {
baseData: function() {
return {};
}
}
})
.state('edit.details', {
url: '/details',
templateUrl: 'app/templates/editDetailsView.html',
controller: 'editController'
})
.state('edit.info', {
url: '/info',
templateUrl: 'app/templates/editInfoView.html',
controller: 'editController'
})
.controller('editController', function (baseData) {
baseData.foo = baseData.foo || 'bar';
});
In the child controller you can do:
angular.extend($scope, $scope.$parent)
In case the controller is used with alias e.g. 'vm' you can do:
let vm = angular.extend(this, $scope.$parent.vm);
Related
app.js
I have created multiple views having the same controller named as vendorCtrl, as we know that $scope can be used withing a specific controller, my question is without the help of $rootscope how can i share data between multiple views named as basic, areas, identity
$stateProvider
.state('dashboard.vendor.add',{
views:{
'#': {
templateUrl: 'templates/vendor/forms/basic.html',
controller : 'vendorCtrl'
}
}
})
.state('dashboard.vendor.add.areas',{
views:{
'#': {
templateUrl: 'templates/vendor/forms/areas.html',
controller : 'vendorCtrl'
}
}
})
.state('dashboard.vendor.add.identity',{
views:{
'#': {
templateUrl: 'templates/vendor/forms/identity.html',
controller : 'vendorCtrl'
}
}
})
Issue regarding scope inheritance
app.js
$stateProvider
.state('dashboard.vendor.add',{
views:{
'#': {
templateUrl: 'templates/vendor/forms/basic.html',
controller : 'vendorCtrl'
}
}
})
.state('dashboard.vendor.add.areas',{
views:{
'#': {
templateUrl: 'templates/vendor/forms/areas.html',
controller : 'vendorCtrl'
}
}
})
controller
function vendorCtrl($scope) {
$scope.data= 'hello world';
}
areas.html
<input type-"text" ng-modal="data"/>
<label name="name">{{data}}</label>
basic.html
<label name="name">{{data}}</label>
In this situation value of $scope.data would be display in areas.html but failed to display value in basic.html despite having same controller.
Because scope properties only inherit down the state chain if the views of our states are nested. Inheritance of scope properties has nothing to do with the nesting of our states and everything to do with the nesting of your views.
On the one hand, With ui-router you can use the params property.
On the other hand, you can use a simple (getter/setter ?) service that can hold your data.
I want to inherit states/URLs using parent.child notation in UI router. I don't want to inherit views (or nest views inside other views), or inherit controllers, I just want to inherit URLs. I'm using 'ui.router' as a dependency.
This is an example of a router:
$stateProvider
.state('products', {
url: '/products',
templateUrl: 'view1.html',
controller: 'products#index'
})
.state('products.show', {
url: '/:id',
templateUrl: 'view2.html',
controller: 'products#show'
})
.state('products.buy', {
url: '/:id/:moreParams',
templateUrl: 'view3.html',
controller: 'products#buy'
})
.state('products.sell', {
url: '/:id/:moreParams/:moreParams',
templateUrl: 'view4.html',
controller: 'products#sell'
});
And the controllers are:
angular.module('productsModule', [])
.controller('products#index', function($scope){
})
.controller('products#show', function($scope){
})
.controller('products#buy', function($scope){
})
.controller('products#sell', function($scope){
})
Here, all views are completely different, and I don't want to nest any view inside any other view. Also, all controllers are different too, and I don't want to inherit controllers, they are all separate with different functions.
Here's my expected result. What I want to achieve is Angular to allow me to only inherit URLs, so the URLs become:
/products
/products/:id
/products/:id/:moreParams
/products/:id/:moreParams/:moreParams
(and then have each URL its own view and controller, as specified above)
So far it's not working, and my research is beginning to tell me that this kind of inheritance using parentState.childState is only for when you want to have nested views (which is what I don't want. I only want to re-use URLs).
My workaround is to create router URLs like products_show, that is, using an underscore instead of a dot, so they are treated as new independent URLs rather than inheritance ones. I'm not sure if this is the best idea, mostly because it looks ugly (though it works perfectly).
Perhaps I should just use products_show in case it can't be done with a dot? Ideas?
You can have a parent child relationship between the routes without nesting their respective views. You achieve that by specifying an absolute target for your view. Like this:
$stateProvider
.state('products', {
url: '/products',
templateUrl: 'view1.html',
controller: 'products#index'
})
.state('products.show', {
url: '/:id',
views: {'#': {
templateUrl: 'view2.html',
controller: 'products#show'
}}
})
.state('products.buy', {
url: '/:id/:moreParams',
views: {'#': {
templateUrl: 'view3.html',
controller: 'products#buy'
}}
})
.state('products.sell', {
url: '/:id/:moreParams/:moreParams',
views: {'#': {
templateUrl: 'view4.html',
controller: 'products#sell'
}}
});
By doing that you're basically telling ui-router to render your view inside the unnamed ui-view in your main index.html, thus, overriding the parent view. Instead of looking for a ui-view in the parent view's template.
To understand why this works, you'll have to understand how ui-router decides where to render the view for your route. So, for example, when you do:
.state('parent.child', {
url: '/:id',
templateUrl: 'child-view.html',
controller: 'ChildCtrl'
})
ui-router will by default translate this to something like:
.state('parent.child', {
url: '/:id',
views: {'#parent': {
templateUrl: 'child-view.html',
controller: 'ChildCtrl'
}}
})
which will cause it to look for an unnamed ui-view inside the parent's view template and render the child view's template inside.
You could also specify more than 1 view for a route, like:
.state('parent.child', {
url: '/:id',
views: {
'#parent': {
templateUrl: 'child-view.html',
controller: 'ChildCtrl'
},
'sidebar#parent': {
templateUrl: 'child-view-sidebar.html',
controller: 'ChildViewSidebarCtrl'
}
}
})
In this case, child-view.html will be rendered inside the parent view's unnamed ui-view as before. In addition it will also look for a ui-view="sidebar" in the parent view's template and render child-view-sidebar.html inside.
For more info on this powerful views option and how to specify targets for your view, see the ui-router docs
This is how to write nested states:
$stateProvider
.state('products', {
url: '/products',
templateUrl: 'view1.html',
controller: 'products#index'
})
.state('products.show', {
parent:'products',
url: '/:id',
templateUrl: 'view2.html',
controller: 'products#show'
})
I have a nested view like so:
.state('xyz',{
url:'/xyz',
templateUrl: 'tpl/xyz.html'
controller : 'xyzCtrl'
})
.state('xyz.abc',{
url:'/abc',
templateUrl: 'tpl/abc.html'
})
and controller:
app.controller('xyzCtrl', ['$scope','$htpp', function($scope,$http){
$scope.data = [];
$http.get('../js/data.json').then(function(res){
$scope.data = res.data;
});
}]);
The problem is I get the value for data in xyz's view but I am not able to get it in abc's view. Normally the child inherits the parents scope data right?
abstract - {boolean=} - An abstract state will never be directly
activated, but can provide inherited properties to its common children
states.
Try this make abstract:true
.state('app',{
abstract: true,
url: '/app',
})
.state('app.xyz',{
url:'/xyz',
templateUrl: 'tpl/xyz.html'
controller : 'xyzCtrl'
})
.state('app.abc',{
url:'/abc',
templateUrl: 'tpl/abc.html'
})
If I have a parent route with a controller, and a child route with no controller, shouldn't the children see the $scope data from the parent.
.state('parent', {
url: '/parent/{idParentRecord}?extraInfo',
templateUrl: 'app/parent/parent.html',
controller: 'ParentController',
controllerAs: 'aparent'
})
.state('parent.child', {
url: '/child/{idChild}',
templateUrl: 'app/parent/child.html'
})
Can I then just access properties in the parent $scope, as if they were in the child scope, so in the child view could I say:
<h1>{{name}}</h1>
Assuming $scope.name has a value in ParentController's $scope?
If that's the case, and the controller is loading some slow data, when the parent $scope.data.subdata.mydata finally loads, if I have
<p>{{data.subdata.mydata}}</p>
in my Controller-less parent.child state view, it should just show up when that data loads... right? Even if maybe I'm asking for something based on my childstate like calling a $scope.myfunc(idChild) that is filtering an array for me like
<div ng-repeat="myparticle in getMyParticles($stateParams.idChild)">
<p>{{myparticle.aproperty}}</p>
Thanks for the help!
Yes your thinking is correct as long as your child view is nested within your parent view. Scope properties will only inherit down the state chain if the views of your states are nested.
See the ui-router wiki section Scope Inheritance by View Hierarchy Only for more information.
I made a quick demo in plunker so you can see this:
http://plnkr.co/edit/0EOdaXIiJhhoKElQnHfo?p=preview
States:
angular
.module('app', ['ui.router'])
.config(function ($stateProvider) {
$stateProvider
.state('parent', {
url: '/parent',
templateUrl: 'parent.html',
controller: 'ParentController',
controllerAs: 'aparent'
})
.state('parent.child', {
url: '/child',
templateUrl: 'child.html'
});
})
.controller('ParentController', function () {
var vm = this;
vm.name = 'Test Name';
});
parent.html:
<!-- Parent View -->
<h2>Parent View</h2>
<!-- This is where the child view will show -->
<ui-view></ui-view>
child.html
<!-- Child View -->
<h2>Child View</h2>
{{aparent.name}}
I have my routes set up as below. Its too frustrating that the view in view.tab is loaded but its controller isn't called. I tried without the paramaters, but it still doesn't work as expected. Does anyone have any idea on how to solve this?
$stateProvider
.state('index', {
url: '/',
templateUrl: viewsRoot + 'restaurants/index.htm',
controller: 'RestaurantsCtrl'
})
.state('view', {
url: '/view',
controller: 'RestaurantsViewCtrl',
templateUrl: viewsRoot + '/restaurants/view.htm'
})
.state('view.tab', {
url: '/orders',
// controller: 'OrdersIndexCtrl',
controller: function ($scope) {
alert('This does not run');
},
views: {
"view": {
templateUrl: viewsRoot + '/restaurants/orders.htm'
}
}
});
$urlRouterProvider.otherwise("/");
You need to declare the controller along side the template:
views: {
"view": {
templateUrl: viewsRoot + '/restaurants/orders.htm',
controller: 'MyController' // (or a function, etc.)
}
The UI-Router wiki sort of alludes to this:
If you define a views object, your state's templateUrl, template and templateProvider will be ignored. So in the case that you need a parent layout of these views, you can define an abstract state that contains a template, and a child state under the layout state that contains the 'views' object.
Controllers are paired with a view. So if it ignores the "template" properties defined on the state, it seems to imply that it will ignore the controller too.
If you want all of your named views to share a controller, define an abstract parent state as the wiki suggests.