I have a parent controller and two tabs within it, each having its own controller as shown in image below:
Parent and Child View Model
I am broadcasting an event from Tab1/Tab2 controllers using $rootScope and catching it directive's controller scope using $scope. When I am switching between tabs, the parent controller and the directive's controller are getting initialized again and the event is caught twice in directive's controller even though it is broadcasted just once.
I tried to debug and noticed the directive's controller $id when it was being caught. When the event was caught first time, the directive's id was the one which got created with previous tab(tab from which i switched). And second time, directive's id was the one which got created with current tab (tab to which i switched).
Note: 1) Even after using reload: false option, parent controller and directive controller is getting re-initialized.
2) I am not initializing parent controller by using ng-controller in html. It is getting initialized only via $stateProvider routing.
This is how i configured my states:
$stateProvider
.state('app.parent', {
url: 'parent/:paramId',
params: {
paramId: 'sample'
},
views: {
'content-parent#app': {
templateUrl: 'parent.html',
controller: 'ParentController',
resolve: {
defaultParams: function(parentService, $stateParams) {
return parentService.getDefaultParams($stateParams.paramId);
}
}
}
}
})
.state('app.parent.tab1', {
url: '/tab1/',
views: {
'content-child#app.parent': {
templateUrl: 'tab1.html',
controller: 'Tab1Controller'
}
}
})
.state('app.parent.tab2', {
url: '/tab2/',
views: {
'content-child#app.parent': {
templateUrl: 'tab2.html',
controller: 'Tab2Controller'
}
}
});
No, the parent state (and thus controller/component) is not re-initialized when the child states are changed. This includes any resolve functions and state event handlers associated with the parent state.
Here's a Plunker that illustrates that the parent scope remains the same when you navigate between child states.
You can force it to reload the parent though, by setting the reload option of $state.go(..) to true. Like this:
$state.go('parent.childA', null, { reload: true });
The parent State remains unchanged as long as you do not push the changes to the parent controller using emit.
Here is a c-sharpcorner link that might help you!
Related
I am writing an angular app where a parent controller manages the global state, and it transitions between states to display information to users.
I followed this answer and used params to send objects to the child component/controller. It works fine, but if I update the values in the child object, these changes are not reflected in the parent.
My code is organised like this:
index.html
<bodyng-controller="main-loop">
...
<li ui-sref="supernova({data:data})" ui-sref-active="active">
data is an object, {"a":1,"b":2}
routes
.state("supernova", {
component: 'supernova',
params: { 'data': null }
component
component('supernova', {
templateUrl: 'views/supernova.html',
controller: ['$stateParams', supernova],
controllerAs: 'supernova'
});
controller
function supernova($stateParams) {
var ctrl = this;
ctrl.data= $stateParams.data;
I can read the value of data fine from both the view and the controller. However if I make a change like this:
ctrl.stuff = function () {
ctrl.data.a = 20;
The changes are visible in the component but not in the parent controller.
Is there any way to share the same object instance between both using ui-router constructs? That is, changes in one are reflected in the other (in a two-way binding style).
I created this small app where I have following states:
restricted.route.js
$stateProvider.state('restricted', {
url: '/restricted',
templateUrl: 'app/restricted/restricted.html',
abstract: true
});
pages.route.js
$stateProvider.state('restricted.pages', {
url: '/pages',
templateUrl: 'app/restricted/pages/pages.html',
controller: 'pagesController',
controllerAs: 'vmPages'
});
detail.route.js
$stateProvider.state('restricted.pages.detail', {
url: '/:id',
controller: 'pageDetailController',
controllerAs: 'vmDetail',
templateUrl: 'app/restricted/pages/detail/detail.html'
});
app.run.js
$urlRouterProvider.otherwise('/restricted/dashboard');
When I load the URL #/restricted/pages everything works fine. Controller is loaded and view is shown.
When I load the URL #/restricted/pages/1 the controller and view from the state 'restricted.pages' is loaded and executed.
The state is clearly recognized, because the $urlRouterProvider.otherwise is not executed.
Does anyone have an idea what I'm doing wrong here?
Thanks!
Is it intentional to have the details as a substate of the pages (list?) state? If so, you need to place an <div ui-view></div> into the template named app/restricted/pages/pages.html.
If that was not your intention, I recommend to rename the detail state to something like restricted.pages_detail as every dot introduces a nested level in the state definitions.
I think your issue lies in the HTML, the nested state should be loaded in a ui-view tag on the parent element.
It seems, by default, the controller of the previous state is reloaded when you press the back button in the browser to go to a previous state.
(this is not true in case of parent-child states)
How can I prevent that from happening?
Since I am not going to change any data in my current state which can affect the previous state, I don't want the previous state to reload again.
Here is a small plunker:
http://plnkr.co/edit/xkQcEywRZVFmavW6eRGq?p=preview
There are 2 states: home and about. If you go to about state and then press back button, you will see that the home state controller is called again.
.state('home', {
url: '/home',
templateUrl: 'partial-home.html',
controller: function($scope) {
console.log('i was called');
}
})
I believe this is the expected behavior, but I want to prevent it because my previous state (home in this case) is doing some visualizations which take some time to be created again.
Let's start with a global controller like GlobalCtrl which is added to the <body> or <html> tag like ng-controller="GlobalCtrl.
Doing this will enable us to keep the scope of this GlobalCtrl throughout your single page Angular app (as you are using ui-router).
Now, inside your GlobalCtrl define something like this:
$rootScope.globalData = {preventExecution: false};
// This callback will be called everytime you change a page using ui-router state
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
$scope.globalData.preventExecution = false;
// Just check for your states here
if (toState.name == "home" && fromState.name == "about") {
$scope.globalData.preventExecution = true;
}
});
Now, in your state configuration, you can use this $scope.globalData.preventExecution;
.state('home', {
url: '/home',
templateUrl: 'partial-home.html',
controller: function($scope) {
if ($scope.globalData.preventExecution) {
return;
}
console.log('i was called');
}
});
Answer to the question: The scope that we refer in the GlobalCtrl and the scope that we use in the State controller, how are they related?
Well, it is a very good question but it's simple. Every time a new scope is created in Angular, it always inherits its parent scope (unless isolated). So when your home state controller instantiated, its scope created using parent state i.e. $rootScope here in this case and we are instantiating the globalData in the $rootScope which is an Object (an Object in Javascript can be used to it's any nested object. Read this). So now when we are setting the globalData.preventExecution true/false, the same data can be used in the $scope of your home state controller. This is how both scopes are related or using the same data.
Answer to the question: is there some flag or setting in the ui-router which can accomplish this in general
If you want to achieve the above behaviour code for multiple states then you can write something like this:
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
$scope.globalData.preventExecution = false;
if (toState.name == "home" && fromState && fromState.preventHomeReExecution) {
$scope.globalData.preventExecution = true;
}
});
Now, your states can be written like this:
.state('about', {
url: '/about',
templateUrl: 'partial-about.html',
preventHomeReExecution: true
})
.state('foo', {
url: '/foo',
templateUrl: 'partial-foo.html',
})
.state('bar', {
url: '/bar',
templateUrl: 'partial-bar.html'
preventHomeReExecution: true
})
Basically, we are using preventHomeReExecution: true as a flag you wanted.
I'm using ui-router to display 2 ui-views, one within the other. They are organized like this:
.state('itemAbstract', {
url: '/items',
abstract: true,
templateUrl: 'client/views/item.ng.html',
controller: 'moveCtrl',
})
.state('item', {
url: "/:itemId",
parent: "itemsAbstract",
views: {
"otherpage":{
templateUrl: 'client/views/other-page.ng.html',
controller: 'otherPageCtrl'
}
}
})
I run the folloowwing in the otherpage controller when an item is clicked.
$rootScope.$broadcast("somethingClicked",obj)
I try to listen for the event in the item controller:
$scope.$on("somethingClicked",function(a,b){
console.log(a)
console.log(b);
})
Unfortunately, this function never gets called. I tried putting this listener function in the otherpage controller, and it was called correctly when the event happened. For some reason, though, this broadcast isn't getting transferred across scopes. That was the whole reason I was using this, to trigger an action in the parent when something in the parent is clicked. Any ideas on why this is not working?
Here is my controller for the item
angular.module('mm').controller('itemCtrl',
function($scope, $meteor, $rootScope, $state, $stateParams) {
var s = $scope;
s.rs = $rootScope;
$scope.$on("somethingClicked",function(a,b){
console.log("there as a click")
console.log(a)
console.log(b);
})
}
I used Batarang to debug and found that despite this code, $scope is not even registering an event listener. $scope.$$listeners does not have a listener for the somethingClicked event. Very strange, and it doesn't make sense why this isn't working.
Maybe you have independent controllers with no inheritance applied. Inheritance on $scope is declared simply by nesting the controllers on your view. In that case you may use $rootscope to broadcast or listen to event as:
//ctrl1
$rootScope.$broadcast("somethingClicked",obj);
//ctrl2
$rootScope.$on("somethingClicked",function(a,b){
console.log(a)
console.log(b);
});
Take a look at this simple demo as well as an older question on Stack Overflow.
EDITED
Based on your sample code I had no problem at all communicating using $rootscope.
The state declaration use an abstract parent controller and a fetched child controller mapped into the view as:
$stateProvider
.state('test', {
url: '/test',
controller: 'pctrl',
views: {
'main': {
template: '<div ng-controller="pctrl">{{test}}</div>' +
'<div ui-view="childview"></div>'
}
}
})
.state('test.child', {
url: '/test/child',
controller: 'cctrl',
views: {
'childview': {
template: '<div ng-controller="cctrl" />'
}
}
});
Here is a full working demo
Answer found - I had misconfigured my routes file, and had the wrong controller specified for the page I was loading. That's why none of my event listeners registered!
I have an Angular app using ui-router with the following states defined in my app.
$stateProvider
.state('classes', {
url: '/classes/:type',
templateUrl: 'classes.html',
controller: 'ClassesCtrl',
})
.state('classes.detail', {
url: '/:id',
views: {
"#": {
templateUrl: 'class.html',
controller: 'ClassCtrl'
}
}
});
and my controllers look like:
app.controller('ClassesCtrl', function($scope, $stateParams){
$scope.foo = "bar";
});
app.controller('ClassCtrl', function($scope, $stateParams){
console.log($scope.foo);
});
As you can see, class.detail hides the parent view by targeting the root level unnamed ui-view. The issue I'm having is that the child is not inheriting the parent's scope (I can't access $scope.foo in class.detail). It seems like the parent state gets destroyed when I go to the child state because if I click back, it has to reload all the data.
How do I effectively hide the parent view but still access the parent data?
From the ui-router documentation:
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).
Well, you're using # to define your view but your parent state is named, so you should name it #classes
Edit: I made this fiddle to explain about names.