Calling a Directive from another Directive - javascript

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');
});

Related

Better approaches than using $broadcast to update input in directive?

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;
}
}
});

How to communicate events between sibling component directives

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>

Pass parent directive's controller to a directive controller (like it is done in the "link" function)

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>

In AngularJs , how does the parent directive call the function which is defined in its child directive?

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.

How do I access a directive scope from a parent directive/controller?

I have a template that goes something like this:
<parent-directive>
<child-directive binding="varFromParent"></child-directive>
<button ng-click="parentDirective.save()"></button>
</parent-directive>
When executing a function in the parentDirective controller, is it possible to access and manipulate the scope variables of the childDirective for e.g. if I have them set up as so
angular.module('app').directive('parentDirective', function() {
return {
restrict: 'E',
templateUrl: '...',
controllerAs: 'parentDirective',
controller: function($rootScope, $scope) {
//...
this.save = () => {
//Need to manipulate childDirective so that its
//scope.defaultValue == 'NEW DEFAULT'
}
}
}
});
and
angular.module('app').directive('childDirective', function() {
return {
restrict: 'E',
templateUrl: '...',
scope: {
binding: '='
},
controllerAs: 'childDirective',
link: function(scope, elm, attrs) {
scope.defaultValue = 'DEFAULT';
}
}
});
How would I go about doing this? Is there any way to do this without setting up a bidirectional binding? I would like to avoid a mess of attributes on the <child-directive> element if possible.
There are many way to set up a communication between your children and your parent directive:
Bidirectional binding (like you said)
Registration of your children in your parent.
You can use the directive require property and the last parameter of the link function controllers to register a children in his parent.
Events, see $scope.on/broadcast
Angular services (as they are "singletons", it's very easy to use it to share data between your directives)
etc.
Example for 2:
angular.module('Example', [])
.directive('parent', [function () {
return {
controller: function (){
// registerChildren etc
}
// ...
};
}])
.directive('children', [function () {
return {
require: ['^^parent', 'children'],
controller: function (){
// ...
}
link: function ($scope, element, attributs, controllers) {
ParentController = controllers[0];
OwnController = controllers[1];
ParentController.registerChildren(OwnController);
// ...
}
// ...
};
}])
In this case you probably don't need to isolate child's directive scope. Define a variable you need to change on parent's scope and then child's directive scope would inherit this value so you can change it value in child's directive and it would be accessible from parent.
angular.module('app').directive('parentDirective', function() {
return {
restrict: 'E',
controllerAs: 'parentCtrl',
controller: function($scope) {
$scope.value = 'Value from parent';
this.value = $scope.value
this.save = function() {
this.value = $scope.value;
}
}
}
});
angular.module('app').directive('childDirective', function() {
return {
restrict: 'E',
controllerAs: 'childCtrl',
controller: function($scope) {
$scope.value = 'Value from child';
this.setValue = function() {
$scope.value = 'New value from child';
}
}
}
});
Here is the fiddle
http://jsfiddle.net/dmitriy_nevzorov/fy31qobe/3/

Categories