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!
Related
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!
For some reason, the second Controller isn't receiving the data from the Service.
I'm trying to make the communication between two Controllers using one Service for it.
The View:
<div class="btn-wrapper" ng-controller="FirstController">
<a ng-href="" ng-click="doPath()" id="go" class="button">GO!</a>
</div>
The FirstController.js:
angular.module('app')
.controller('FirstController', function($scope, sharedService) {
$scope.doPath = sharedService.searchPath;
});
The sharedService:
angular.module('myServices', [])
.service('sharedService', function($rootScope) {
this.searchPath = function() {
console.log("I got the service!");
$rootScope.$broadcast('Search', {
data: 'something'
});
}
});
And the SecondController.js
angular.module('app')
.controller('SecondController', function(sharedService, $scope) {
$scope.$on('Search', function(event, data){
console.log(data);
//this.search(); => I intent to run this function after this
});
});
The event is dispatched by a button in the View, that calls the doPath() function. This function does communication with the Service sharedService and the message "I got the service" is displayed.
However, the app stops here. And the communication between Service and the second Controller, using $rootScope.$broadcast, seems that not happening (the data isn't showing on console, neither any error).
I found some solutions here. I have tried already all of answers, so the problem is not the same, cause still not working.
EDIT
The ngRoute is here (app.js):
angular.module('app', ['ngRoute', 'myServices'])
.config(function($routeProvider){
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'FirstController'
})
.otherwise({
redirectTo: '/'
});
});
As everyone suggested 'Instantiation' of the Second controller is needed.
<div ng-controller="firstController">
//code
</div>
<div ng-controller="secondController">
//code
</div>
Like above.
I know you have used 'ngRoute'. Until you change your view the second controller will not be loaded in 'ngRoute' whereas in above code both the controllers are in the same view. That is why above code works and 'ngRoute' does not.
SecondController is not instantiated by Angular because you are referring only FirstController from html. You need to instantiate the SecondController on the same html using parent child or sibling relationship depending on your application.
(function(app) {
app.config(function($stateProvider, $httpProvider, $urlRouterProvider) {
//region Registering States
$urlRouterProvider.otherwise('/home');
var dashboard = {
name: 'home',
url: '/home',
templateUrl: 'home.html',
controller: 'homeCtrl'
};
var home = {
name: 'profile',
url: '/profile',
templateUrl: 'profile.html',
controller: 'profileCtrl'
};
$stateProvider.state(dashboard);
$stateProvider.state(home);
//endregion
});
app.controller('homeCtrl', ['$rootScope','$scope', function($rootScope,$scope) {
$rootScope.pageTitle = "home";
$scope.callEvent = function(){
$rootScope.$broadcast('newEvent',"data");
}
}]);
console.log('called');
}(angular.module('app', ['ui.router'])));
2'nd controller:
(function(app) {
app.controller('profileCtrl', ['$rootScope','$scope', function($rootScope,$scope) {
$rootScope.pageTitle = "profile";
$rootScope.$on('newEvent',function(ev,data){
alert('Event fired from home');
});
$scope.$on('newEvent',function(ev,data){
alert('Event fired from home');
});
}]);
}(angular.module('app')));
App with 2 controller:
1 controller: fired a event using $broadcast
2 controller: capturing event fired from 1'st controller.
But unable to accomplish that.
NOTE: If html page load once( 2'nd controller), $on event is fired else it's doesn't listen to any event.
For better understanding i had created a plunkr
Please first review the code
#vojta
Thanks
The problem is you are loading the controllers via states. This means they only initialize once the state becomes active (i.e you navigate to the html).
If you add a console log to profileCtrl you see that it does not get fired until the page is active.
Angular doesn't load the controller if it wasn't called.
Think of it as a singleton object which won't init until you will try to run it.
In your case, you set a controller for a specific route which means it will run only after you go to its route address.
What you actually will want to do is to have some directive Angular Directive
That will control on your menu bar that you will load on the main page and maybe every other page.
And for the home page, you should have a different controller that will be active only when the user is on the page.
As a side note, don't assume the "home" controller will be available once you leave the home page (Angular destroy).
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 have different views each created by a different controller. At a particular time only one of the views is visible.
I want to switch from one view to another view through a function of the controller of the first view and after that I want to call a method of the second view controller.
My problem is how should I call this method in an angular way?
I know the possiblity using $broadcast and $on but that smells a little bit.
The other choice ist to find the scope in the dom and calling the method via scope. But that is even more ugly.
What is the best solution?
You can use services to communicate between controllers. While you could create a generic shared service to have a central point to subscribe to and broadcast events, services are easier to maintain over time.
You can use Angular Routing
Check out the documentation. This is an excerpt from the documentation. You can make links like
Link
For the first route and so on.
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
Okay it is done and simpler as expected.
The idea is to use a service used in both views (controllers), that contains a 'execution boolean'.
The first view set the boolean to true, the second set a watch on this boolean and therefore is called and can call the desired method.
In the service:
trigger: function(name) { model[name] = true; },
setTriggerWatch: function(scope, name, callback) {
scope.$watch(function value() {
return model[name];
}, function listener(newValue, oldValue) {
if (newValue) {
callback();
}
});
},
In the destination controller:
sessionData.setTriggerWatch($scope, 'createSession', function callCreateSession() {
_createSession();
});
In the source controller:
sessionData.trigger('createSession');