angular JS $watch and communication between two directives
I Have a code in AngularJs where I'd like to call function in one directive when state of variable changes in other directive. I have a controller:
app.controller('TaskerCtrl', ['$scope', function($scope) {
$scope.tasksReload = false;
}
]);
Here as we can see is variable tasksReload. I'd like to call function in one of my directive when state of that variable changes on true in other directive.
Below I show code of my directives:
app.directive('newTaskWidget', function (TaskerForm, Consultants) {
return {
restrict: 'E',
scope: {
sortReverse: '=sortReverse',
tasksReload: '=tasksReload'
},
scope.test = function(){
scope.tasksReload = true;
}
app.directive('taskListWidget', function ($filter, $uibModal, Notification, TaskerForm, Consultants) {
return {
restrict: 'E',
scope: {
sortReverse: '=sortReverse',
departments: '=departmtens',
myDepartment: '=myDepartment',
tasksReload: '=tasksReload'
},
link: function (scope) {
scope.$watch('tasksReload', function (data) {
console.log("Musze przeladowac taski");
});
Below I show HTML code with my directives:
<new-task-widget sort-reverse="false" tasks-reload = 'tasksReload'>
</new-task-widget>
<task_list_widget sort-reverse="false" departmtens = "departments"
my-department="session.user.department" tasks-reload = 'tasksReload'>
</task_list_widget>
As we can see in newTaskWidget there is a function test. I'd like to call $watch action in taskListWidget when value scope.tasksReload = true; is been changed but it dosen't work correctly. I call that function with ng-click directive on button:
<button class="btn btn-primary validateButton" ng-click="test()">
</button>
There is no reaction. How could I do that properly? I would be grateful for help. Best regards ;)
One approach to communicating click events between sibling components is to use scope.$root.$broadcast:
scope.$root.$broadcast("test-event",args);
And in the sibling component, use scope.$on:
scope.$on("test-event", function(event,args) {
//Handle event here
});
The DEMO
angular.module("app",[])
.directive('newTaskWidget', function () {
return {
restrict: 'E',
scope: { },
template: `
<fieldset>
<button ng-click="test()">Click me</button>
</fieldset>
`,
link: function(scope,elem,attrs) {
scope.test = function(){
scope.$root.$broadcast("test-event");
};
}
};
})
.directive('taskListWidget', function () {
return {
restrict: 'E',
scope: {},
template: `<fieldset>clicks={{count}}</fieldset>`,
link: function (scope) {
scope.count=0;
scope.$on('test-event', function (event) {
scope.count++;
console.log("Musze przeladowac taski");
});
}
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<new-task-widget>
</new-task-widget>
<task_list_widget>
</task_list_widget>
</body>
Related
Currently I have a list of contacts on controller A. When I click on one of the contacts, it is broadcasting the contact info to controller B and to the datepicker directive in controller B. This is working but is there a better way to update the input on the datepicker directive?
app.directive('datePickerDirective', [function () {
return {
restrict: 'AE',
require: 'ngModel',
scope: {
datepickerNgModel: '=',
datepickerId: '#'
},
templateUrl: 'Content/app/directives/templates/DatePicker.html',
link: function ($scope, element, attrs, ngModel) {
$scope.$watch(function () {
ngModel.$setViewValue($scope.datepickerNgModel);
return ngModel.$modelValue;
});
$scope.$on('data-from-component-a', function (event, data) {
$('#' + $scope.datepickerId).val(data.date);
})
}
}
}]);
I would avoid using events ($broadcast) here. You can do it by using a nice factory which handles the data for your components. You did not gave any information about your datepicker and controllers, so I created an abstract example which delivers you the basic handling.
> Share data via factory between controllers - demo fiddle
View
<div ng-controller="MyCtrl">
<button ng-click="publishData()">
Publish data
</button>
<button ng-click="resetData()">
Reset data
</button>
</div>
<div ng-controller="MyOtherCtrl">
<my-directive my-model="data.getData()"></my-directive>
</div>
AngularJS application
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope, myFactory) {
$scope.publishData = function() {
myFactory.publishData();
}
$scope.resetData = function() {
myFactory.resetData();
}
});
myApp.controller('MyOtherCtrl', function($scope, myFactory) {
$scope.data = myFactory;
});
myApp.directive('myDirective', function () {
return {
restrict: 'E',
template: '{{myModel}}',
scope: {
myModel: '='
},
link: function (scope, element, attrs) {
scope.$watch('myModel', function (newValue, oldValue) {
console.log(newValue);
// $('#' + $scope.datepickerId).val(newValue);
});
}
}
});
myApp.factory('myFactory', function() {
return {
contactInfo: '',
publishData: function() {
this.contactInfo = 'Sdfsdfsdf';
},
resetData: function() {
this.contactInfo = null;
},
getData: function () {
return this.contactInfo;
}
}
});
I have several hierarchical directives and in one, I need to have some functions in its controller, so that the child elements can interact with it. But this one directive also needs to reference its parent directive's controller, but I don't know how to do that in controller (I know how in the "link()" but this time I need controller for the child interaction). It should be possible to do it with scope:
controller: function($scope){},
link: function (scope, ..., parentCtrl){
scope.parentCtrl = parentCtrl;
}
but it seems weird, because the link function is executed after the controller is, or it it OK? I'm confused and I think it might be a bad design?
diagram:
ParentParentDirective
controller: function(service){
this.service = service;
}
ParentDirective
controller: function(){
this.callOnService = function(id){
???ParentParentDirective???.service.callSth(id);
}
}
ChildDirective
link(scope, ...,ParentDirectiveCtrl){
scope.makeChanges = ParentDirectiveCtrl.callOnService;
}
You can use $element.controller method for that, as in example below.
angular.module('app', []);
angular.module('app').directive('grandparent', function () {
return {
restrict: 'E',
controller: function () {
this.go = function () {
console.log('Grandparent directive');
};
}
};
});
angular.module('app').directive('parent', function () {
return {
restrict: 'E',
controller: function () {
this.go = function () {
console.log('Parent directive');
};
}
};
});
angular.module('app').directive('child', function () {
return {
restrict: 'E',
require: ['^parent', '^grandparent'],
controller: function ($element) {
var parentCtrl = $element.controller('parent');
var grandparentCtrl = $element.controller('grandparent');
parentCtrl.go();
grandparentCtrl.go();
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<div ng-app="app">
<grandparent>
<parent>
<child></child>
</parent>
</grandparent>
</div>
I want to call the function in child directive , instead of using the $scope.$broadcast to notify the child directive that something happens.
The outer directive will call the different functions in different child directive at different time.
You can use controllers in directive for this.
For example: in link function of child directive, you can save what you want in parent controller, and call then when you want.
Sample
angular.module('app', [])
.directive('parent', function() {
return {
controller: function() {
var parent = this;
parent.childs = [];
parent.click = function() {
parent.childs.forEach(function(child) {
child.addOne();
});
}
},
controllerAs: 'vm'
}
})
.directive('child', function() {
return {
require: ['child', '^parent'],
template: '<div>Child value: {{vm.val}} <input type="button" ng-click="vm.addOne()" value="add one" /></div>',
controller: function() {
var child = this;
child.addOne = function() {
child.val += 1;
}
},
controllerAs: 'vm',
scope: true,
link: function(scope, elem, attrs, ctrls) {
var childCtrl = ctrls[0],
parentCtrl = ctrls[1];
parentCtrl.childs.push(childCtrl);
childCtrl.val = +attrs.child;
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app="app">
<div parent>
<input type="button" ng-click="vm.click()" value="add one to all" />
<div child="1"></div>
<div child="2"></div>
<div child="3"></div>
</div>
</div>
We will need to create an empty object in parent directive scope and pass it to the child directive as a parameter.
While receiving it in child directive – we can inject a method to it
app.directive('childDirective', function () {
return {
restrict: 'E',
scope: {
objectToInject: '=',
},
templateUrl: 'templates/myTemplate.html',
link: function ($scope, element, attrs) {
var killwatch = $scope.$watch('objectToInject', function (value) {
if(value){
$scope.Obj = value;
/*Injecting the Method*/
$scope.Obj.invoke = function(){
//Do something
}
killwatch();
}
});
}
};
});
And use the object to call the child directive function from parent.
Please consider this tryout on Plunkr.
I have a simple set up:
<body ng-app="myApp">
<div ng-controller="myController">
<parent-directive></parent-directive>
<child-directive></child-directive>
</div>
</body>
With the parent directive defined like:
app.directive("parentDirective", [
"$compile",
function (
$compile) {
return {
scope: {
person: "="
},
restrict: "E",
template: "<h3>I'm a parent</h3>",
controller: [
"$scope",
function ($scope) {
// --- PRIVATE --- //
var self = {};
$scope.ClickMe = function() {
alert('Parent clicked');
};
}],
link: function ($scope, $elem, $attrs) {
}
};
}]);
And child directive defined like:
app.directive("childDirective", [
"$compile",
function (
$compile) {
return {
scope: {
person: "="
},
restrict: "E",
require: "^?parentDirective",
template: "<h3>I'm a child, click <button ng-click='ClickMe()'>here</button></h3>",
controller: [
"$scope",
function ($scope) {
// --- PRIVATE --- //
var self = {};
$scope.ClickMe = function() {
alert('child clicked');
$scope.parentDirective.ClickMe();
};
}],
link: function ($scope, $elem, $attrs) {
}
};
}]);
The child click is handled, but the click defined on the `parent', returns undefined:
TypeError: Cannot read property 'ClickMe' of undefined
looking at the console.
Any idea what's going wrong?
Any idea what's going wrong?
You cannot require a sibling directive.
The required directives controller methods dont get exposed automagically onto your scope.
You should expose methods on the controller itself, not on the assigned $scope.
You can require a directive that is defined on the same element as the requiring directive, or on a parent element.
<child-directive parent-directive></child-directive>
<parent-directive>
<child-directive></child-directive>
</parent-directive>
When you require the controller (aka. exposed API) of another directive, it doesn't magically end up on the $scope of the requiring directive.
It does however end up in your link function as the fourth argument.
Like so:
.directive('child', function () {
return {
require: '?^parentDirective',
link: function (scope, el, attrs, parentDirectiveController) {
scope.clickMe = function () {
parentDirectiveController.clickMe();
};
}
};
});
Expose the methods you want available in other directives onto this instead of $scope, as the $scope way of doing it won't work the way you intend it to when you have isolated scopes.
.directive('parent',
controller: function () {
this.clickMe = function () {};
}
}
To get your example working;
<parent>
<child></child>
</parent>
.directive('parent', function () {
return {
controller: function () {
this.clickMe = function () {};
}
};
}
.directive('child', function () {
return {
require: '^?parent',
link: function (scope, el, attrs, parentCtrl) {
scope.clickMe = function () {
parentCtrl.clickMe();
};
}
};
});
Simplified (& working) version of your plunker: http://plnkr.co/edit/nao4EvbptQm7gDKkmZS2?p=preview
Put your child directive in your parent directive template. Then use $scope.$parent.ClickMe(). Here's what it would look like.
Simple setup:
<body ng-app="myApp">
<div ng-controller="myController">
<parent-directive></parent-directive>
</div>
</body>
Parent directive:
app.directive("parentDirective", [
function () {
return {
scope: {},
restrict: "E",
template: "<h3>I'm a parent</h3><child-directive></child-directive>",
controller: [
"$scope",
function ($scope) {
$scope.ClickMe = function() {
alert('Parent clicked');
};
}
]
};
}
]);
Child directive:
app.directive("childDirective", [
function () {
return {
restrict: "E",
template: "<h3>I'm a child, click <button ng-click='ClickMe()'>here</button></h3>",
controller: [
"$scope",
function ($scope) {
$scope.ClickMe = function() {
alert('child clicked');
$scope.$parent.ClickMe();
};
}
]
};
}
]);
I might be thinking about your problem a little differently but I would take a look at $broadcast. The idea is you can broadcast an event and have "n" number of directives in your case listening for that event.
http://plnkr.co/edit/wBmX2TvC3yMXwItfxkgl
brodcast:
$scope.ClickMe = function() {
alert('child clicked');
$rootScope.$broadcast('child-click');;
};
listen:
$scope.$on('child-click', function (event, args) {
alert('Parent clicked');
});
I have a grid with button that has k-grid-cancel-changes class. I would like to create a directive that will attach a click event to that button and call method on the page scope
.directive('kGridCancelChanges', function () {
return {
restrict: 'C',
scope: {
onCancelChanges: "&"
},
controller: function ($scope, $element, $attrs, $location) {
$element.click(function () {
$scope.onCancelChanges();
});
}
}
});
When I press button I can see $scope.onCancelChanges() fired from my directive but it never reaches function on the page scope.
$scope.onCancelChanges = function () {
alert('test');
}
I would appreciate any suggestions
If you want to call a function in the scope it has to be provided like this:
<button class="k-grid-cancel-changes" on-cancel-changes="onCancelChanges()">test</button>
Demo: http://plnkr.co/edit/8vQ1wmdriGrDFGZwhqW2?p=preview
If for some reason you can't modify HTML code (say, it's rendered dynamically by Kendo) and can't add attribute, then you can only access the function to call via $parent scope reference:
$scope.$parent.onCancelChanges();
Demo: http://plnkr.co/edit/tpEEZs9VQunKXABud9yN?p=preview
And finally, if it's not principal to have isolated scope for your directive then you can simply call the function as it's the same scope:
.directive('kGridCancelChanges', function() {
return {
restrict: 'C',
controller: function($scope, $element, $attrs, $location) {
$element.click(function() {
$scope.onCancelChanges();
});
}
}
});
Demo: http://plnkr.co/edit/0OmlCJ6SgYU2GQRyBgYj?p=preview
You can create you directive like this:
app.directive('myDir', function () {
return {
restrict: 'C',
scope: {
foo: '&'
},
link: function(scope,elem){
elem.on('click',function(){
scope.foo();
});
}};
});
or use controller function instead of link if you need:
controller: function($scope,$element){
$element.on('click',function(){
$scope.foo();
});
}};
Note that angular's jqLite has no element.click function.
Here is fiddle:
http://jsfiddle.net/cxo77xb4/2/