I have under one controller two directives :
<div ng-controller="ctrl">
<div data-dy-items-list items="items"></div> // FIRST DIRECTIVE
<div data-dy-subitems-list subitems="subitems"></div> //SECON DIRECTIVE
</div>
In the Second directive template, I have one button and in the directive.js file in the controller section I did this :
$scope.clickButton= function () {
......
$scope.$emit("UPDATE_PARENT","updated");
}
In the first directive, I would like to do this in the controller section:
$scope.update = false;
$scope.$on("UPDATE_PARENT", function (event,message){
$scope.update = true;
console.log('update: ' + $scope.update);
});
But it doesn't work!!!
$emit dispatches an event upwards through the scope hierarchy. Your directives are siblings and thus $emit won't work.
The solution might be to $broadcast an event from a parent scope. Do it from ctrl if that's an option for you, or inject $rootScope to the directive and do $rootScope.$broadcast from there:
$rootScope.$brodcast("UPDATE_PARENT","updated");
Mind that $broadcasting events from $rootScope might seem to be an anti-pattern for AngularJS. It strongly depends on the usecase. There are other solutions to your problem:
One of them is to create a parent directive for both of your directives.
Another one is to use an intermediatory service which will hold values. Then you can do $watch on the service data and react accordingly.
You can $emit the event to the ctrl and then ctrl will $broadcast it down to the other directive.
Choose whatever fits your needs best.
I just got same problem.
Event between two directives was now passed, not with scope.$emit, not with scope.$broadcast.
After looking around, I did this trick,
Use scope.$parent.$broadcast with your $parent scope:
Directive 1:
scope.$parent.$broadcast('NO_LIVE_CONNECTION', {});
Directive 2:
scope.$on('NO_LIVE_CONNECTION', function (event, params) {
console.log("No live DB connections ...");
scope.state.showErrorMessage = true;
});
Related
I have 2 controllers and one service.From one controller calling service and result $emit to another controller in another page .but its not working.Please help.Thanks in advance.
In first controller
routerApp.controller('DetailCtrl',['$scope''ChefService','$rootScope',function($scope,ChefService,$rootScope){
$scope.Fn_ClickChefProfile = function(chefid){
ChefService.GetChefProfile(chefid).then(function(d){
$rootScope.$emit("CallToChef",d);
});};]});
In second controller
routerApp.controller('ChefCtrl',['$scope','$rootScope','ChefService',function($scope,$rootScope,ChefService){
$rootScope.$on("CallToChef",function(event,data){
$scope.Fn_GetChefProfile(data);
});
$scope.Fn_GetChefProfile = function(data){
console.log(data);
};}]);
Take a look at this answer
$emit dispatches an event upwards (to parent controller) ... $broadcast dispatches an event downwards (to child controller)
http://plnkr.co/edit/UfQJU661pQR0DMY3c61t?p=preview
I got above code from AngularJs site and only thing I have added a button to delete a Div where we have controller but after delete no destroy method called as I have put alert in Directive and Controller.
element.on('$destroy', function() {
alert('destroy directive interval');
$interval.cancel(stopTime);
});
and
$scope.$on('$destroy', function() {
alert('destroy controller interval');
// Make sure that the interval is destroyed too
$scope.stopFight();
});
please suggest.
Thanks
The main thing to be noticed
When element.remove() is executed that element and all of its children will be removed from the DOM together will all event handlers attached via for example element.on.
It will not destroy the $scope associated with the element.
So you need to manually trigger scope.$destroy();
First get the scope of element:-
var scope = angular.element(document.getElementById("mainDiv")).scope();
Second remove the element from dom:-
$('#mainDiv').remove();
Third destroy scope manually:-
scope.$destroy();
Plunker
You're doing it outside of angular's context.
<button id="btn" onclick="DeleteMainDiv()">DeleteDiv</button>
So in your DeleteMainDiv() function
function DeleteMainDiv() {
alert('Controller div going to remove');
//debugger;
var scope = angular.element(document.getElementById("mainDiv")).scope();
$('#mainDiv').remove();
scope.$destroy();
}
This will trigger the destroy functionality.
But I don't see a need of it. Angular will automatically run the $destroy event handler when the route changes or directive no longer required.
DEMO
I got this code $rootScope.$on('abc',function(event, next, current){ }); in a tutorial.
My question is what is .$on()? If it is a function, then why is it preceded by $?
$on is related to $broadcast and $emit - which is a way to trigger code from other places.
The first thing about $on you should know is that it's a method of $scope
The second thing you should know is $ prefix refers to an Angular Method, $$ prefixes refers to angular methods that you should avoid using.
Now lets get into detail about what $on is.
Below is an example template and its controllers, we'll explore how $broadcast/$on can help us achieve what we want.
<div ng-controller="FirstCtrl">
<input ng-model="name"/>
<button ng-click="register()">Register </button>
</div>
<div ng-controller="SecondCtrl">
Registered Name: <input ng-model="name"/>
</div>
The controllers are
app.controller('FirstCtrl', function($scope){
$scope.register = function(){
}
});
app.controller('SecondCtrl', function($scope){
});
My question to you is how do you pass the name to the second controller when a user clicks register? You may come up with multiple solutions but the one we're going to use is using $broadcast and $on.
$broadcast vs $emit
Which should we use? $broadcast will channel down to all the children dom elements and $emit will channel the opposite direction to all the ancestor dom elements.
The best way to avoid deciding between $emit or $broadcast is to channel from the $rootScope and use $broadcast to all its children. Which makes our case much easier since our dom elements are siblings.
Adding $rootScope and lets $broadcast
app.controller('FirstCtrl', function($rootScope, $scope){
$scope.register = function(){
$rootScope.$broadcast('BOOM!', $scope.name)
}
});
Note we added $rootScope and now we're using $broadcast(broadcastName, arguments). For broadcastName, we want to give it a unique name so we can catch that name in our secondCtrl. I've chosen BOOM! just for fun. The second arguments 'arguments' allows us to pass values to the listeners.
Receiving our broadcast
In our second controller, we need to set up code to listen to our broadcast
app.controller('SecondCtrl', function($scope){
$scope.$on('BOOM!', function(events, args){
console.log(args);
$scope.name = args; //now we've registered!
})
});
It's really that simple. Live Example
Other ways to achieve similar results
Try to avoid using this suite of methods as it is neither efficient nor easy to maintain but it's a simple way to fix issues you might have.
You can usually do the same thing by using a service or by simplifying your controllers. We won't discuss this in detail but I thought I'd just mention it for completeness.
Lastly, keep in mind a really useful broadcast to listen to is '$destroy' again you can see the $ means it's a method or object created by the vendor codes. Anyways $destroy is broadcasted when a controller gets destroyed, you may want to listen to this to know when your controller is removed.
The previous answer is a very good one. I'd like only to add a short remark: this $on kind of listener has a very important property: can be canceled (stoped).
I'll explain what do I mean:
The html:
<div ng-controller="FirstCtrl">
<input ng-model="name"/>
<button ng-click="register()">Register </button>
</div>
<div ng-controller="SecondCtrl">
Registered Name: <input ng-model="name"/>
</div>
<hr/><br/>
<button ng-click="disableEvents()">Disable events</button>
And the controller:
app.controller('SecondCtrl', function($scope){
$scope.cancelOn = $scope.$on('BOOM!', function(events, args){
console.log(args);
$scope.name = args; //now we've registered!
});
// this will cancel the event listening....
$scope.disableEvents = function(){
console.log('Canceling the event listener: ', $scope.cancelOn());
}
});
If you press the "Register" button, you can see it communicate with the second controller. Now press the "Disable events" button. This will cancel the listener, the $on(...). Now, if you press again the "Register", you'll notice that the listener is no longer listen for this kind of event.
I created a directive called dt-modal under the dt module. In my main app's module called demo, I use this dt-modal which has an isolated scope. I created this directive such that the HTML form written within the directive is transcluded since I want to reuse this modal for many different forms.
<dt-modal>
<form ng-controller="ReviewFormController"
name="reviewForm"
novalidate
ng-submit="reviewForm.$valid && submitReview(review)">
<!-- form contents here -->
</form>
</dt-modal>
This transcluded form has a custom controller called ReviewFormController that listens for the submit event. How can I call the close() method on the dt-modal's scope from within submitReview() in ReviewFormController?
Here is a JSBin. If you hit ESC, you can see close() in the directive run.
http://jsbin.com/cukanole/1/edit
If this isn't possible, is there a better design for this directive?
Thanks in advance!
Since you are using an isolated scope, you could pass a control object to the directive...
<dt-modal id="review-form-modal" api="modal.api">
and add the close method to it via two-way binding:
scope: {
api: '='
},
link: function($scope, $el, attrs) {
$scope.api = {
close: function() {
$el.css({
display: 'none'
})
}
}
...
Then ng-click can use the control object to call close:
<button type="submit" ng-click="modal.api.close()">Submit</button>
If you want to try this code, here it is on Plunker.
My recommendation is to use $emit to trigger the event from the controller and use $on on the directly.
Controller
scope.$emit("ValueChanged", value);
In the directive the event will be captured using $on like:
$scope.$on("ValueChanged", function(event, ars){
... //your event has been triggered.
});
Important:
Directives should be always independent components, if inside the directive there is a call to a method from a controller(outside the directive) this will create a dependency between my directive and the controller and of course this will force one not being able to exist without the other.
If I would have to apply a design principle to a directive it will be the S in SOLID, Single responsibility principle. Directives should be able to encapsulate and work independently.
I have this piece of code which allows a user to leave comments on a list of items.
I created a directive and listen to keydown in order to let the user submit a comment if keyCode == 13.
Not sure if I should include the code to post a comment within the directive. What is the best way to communicate between controllers and directives?
I also check whether or not the input is empty before submitting the comment. It works but not sure this is Angular best practice?
Here is my plunker.
you don't need to write a directive, if you want to use ng-keydown..
example:
template:
<input type="text" ng-model="myText" ng-keydown="checkKeyCode($event)">
controller: -- written in coffeescript
$scope.checkKeyCode = ($event)->
if $event.keyCode == 13 and $scope.myText?
$scope.doSomething()
You generally don't want your directives knowing anything about your controller, so the best(Angular) way of communicating between controllers and directives is through bi-directional bindings.
In your situation, I think best practice, again IMO, would be to create a directive for the button -- not the input. You'd tell the button which "input" (by id) to monitor. Something like:
<input id="input-{{item.id}}" type="text" ng-model="currMessage" />
<button class="btnMessage" ng-click="addMessage(currMessage, item)" default-input="input-{{item.id}}">Add</button>
ETA: Here's what the directive would end up looking like
http://plnkr.co/edit/HhEAUUq0IZvzblbRksBH?p=preview
myApp.directive('defaultInput', function () {
return {
restrict:'A',
link: function(scope, element, attrs) {
attrs.$observe('defaultInput', function(value) {
var inputElement = angular.element(document).find('#' + value);
inputElement.bind('keydown', function(e) {
if (e.keyCode == 13) {
element.click();
}
});
});
}
};
});
It could get tricky because the $observe callback will fire every time your controller's scope.items changes, so you'd need to somehow unbind and rebind (I know you're using jQuery, but I'm not seeing angular.unbind in the docs).
Another option, if you wanted to stick closer to your original approach:
http://plnkr.co/edit/3X3usJJpaCccRTtJeYPF?p=preview
HTML
<input id="input-{{item.id}}" type="text" ng-model="currMessage" enter-fires-next-button />
JavaScript
myApp.directive('enterFiresNextButton', function() {
return function(scope, element, attrs){
element.on('keydown', function(e){
if(e.keyCode == 13) {
element.next('button').click();
}
});
}
});
What is the best way to communicate between controllers and directives?
It depends... I like to first determine which type of scope is appropriate for a directive: no new scope, new scope, or new isolate scope. See When writing a directive in AngularJS, how do I decide if I need no new scope, a new child scope, or a new isolated scope?
Once that has been decided, the next decision is to determine if the communication should really be going to a service. If so, the controller and directive would both inject the service and interact with it, rather than each other.
If a service is not required, attributes are used to facilitate the communication between the controller and the directive. How that is done is determined by the type of scope the directive creates. Tip: if an isolate scope is not used, use $parse to get and set properties inside the directive, or to call methods on the controller from inside the directive -- see
How to set angular controller object property value from directive in child scope
https://stackoverflow.com/a/12932075/215945 - an example of calling a controller function with arguments