Have two custom directives interact with each other in AngularJS - javascript

I have a two directives
angular.module('myApp.directives', []).
directive('exampleDirective', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('click',function() {
//access exampleDirective2 behaviour
});
}
}
}]).
directive('exampleDirective2', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('change',function() {
//access exampleDirective behaviour
});
}
}
}]);
As you can see on exampleDirective elm.on(click) function I want to get the exampleDirective2 function and vice versa.
Is there a way in AngularJS to achieve this?

There are three solutions to this problem:
First solution: use a service
Share a service between directives, that can contain data and functions.
.service('myService', function(){
this.data = //....
this.function = function() //...
})
.directive('dir1', ['myService', function(myService) {
//...
link: function(scope, elem, attrs) {
scope.data = myService.data;
}
}])
The same for the other directive.
Second solution: directives' controllers
If your directives have a parent/child/sibling relationship:
.directive('dir1', function(){
return {
controller: function(scope, elem, attrs) {
this.sayHello = function() {
alert('hello')
}
}
}
})
.directive('dir2', function(){
return {
require: '^dir1',
link: function(scope, elem, attrs, dir1Ctrl) {
dir1Ctrl.sayHello(); //will alert hello
}
}
})
However, this won't work if you directives have isolated scope. Also, depending on the relationship of the directive (parent/child or siblings) the sintax for the require attribute changes slightly; you can find more info on the AngularJS docs for directives.
Third solution: use events
You can also emit/broadcast events from the directive scopes, or inject $rootScope and use it as an event bus:
.directive('dir1', function($rootScope){
return {
link: function(scope, elem, attrs) {
var emitEvent = function(){
$rootScope.$emit('somethingHappenedEvent', { /*you can pass optional data*/ });
}
emitEvent();
}
}
})
.directive('dir2', function($rootScope) {
return {
link: function(scope, elem, attrs) {
$rootScope.$on('somethingHappenedEvent', function(event) {
if(!event.defaultPrevented) {
//react to event here
}
})
}
}
})
You could also to the same with the normal scope instead of $rootScope, but in that case you will have to keep in mind that the event will bubble up/down all the scopes (depending on the use of $emit or $broadcast). I prefer to $emit from $rootScope, to that it will be the only scope able to catch the event, and will also be quite fast.

One way to do this is to have a wrapper directive with a controller, which can then be shared between the directives if you use require. A simpler, and perhaps better solution (as it doesn't depend on the DOM) is to have a common service that enables communication between the directives.

If you want to do this in directive way only.
Here is answer
angular.module('myApp.directives', []).
directive('exampleDirective', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('click',function() {
//access exampleDirective2 behaviour
scope.exampleDirective2Function();
});
scope.exampleDirectiveFunction = function (){
//write your code here
}
}
}
}]).
directive('exampleDirective2', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('change',function() {
//access exampleDirective behaviour
scope.exampleDirectiveFunction();
});
scope.exampleDirective2Function= function (){
//write your code here
}
}
}
}]);
Another way is, you can write a service, write a function in that, and use that service functions by injecting service into the directive.

Related

Query selector in nested AngularJS Directives

I'm using two directives in this HTML code:
<div animations>
<h2>Title</h2>
<span class="animateBtn">Animate!</span>
<info-section></info-section>
</div>
The first, is an Attribute Directive:
angular.module('app').directive('animations', ['$window', ($window: any) => {
return {
restrict: "A",
link: function ($scope: any, element: any, attrs: any) {
angular.element(document).ready(() => {
let animateBtns = angular.element(element[0].querySelector('.animateBtn'));
if (animateBtns && animateBtns.length) {
for (let i = 0, animateBtnsLength = animateBtns.length; i < animateBtnsLength; i++) {
let currentBtn = animateBtns[i];
currentBtn.addEventListener("click", function () {
.... other code....
});
}
}
..... other code .....
});
}
};
}])
So, it simply does a querySelector to select all buttons that, at the click, have to start a certain function.
And it works. The problem is that the second directive also contains an "animateBtn":
.directive('infoSection', function() {
return {
replace: true,
restrict: 'E',
template: '<div><span class="animateBtn">Animate with this too</span></div>'
}
});
The problem is that in the first directive (even if I user (document).ready()), the selector returns just one element (the span under the title), and it doesn't include the "animateBtn" of the second directive.
Here you can find the full code: PLNKR
With AngularJS one uses directives to attach code to elements instead of query selectors:
app.directive("animateBtn", function($window) {
return {
restrict: 'C',
link: postLink
};
function postLink (scope, elem, attrs) {
elem.on('click', function() {
.... other code....
});
.... other code....
}
})
The above directive will attach the click handler and associated code to each element with the class animateBtn when the element is added to the DOM by the AngularJS framework.
if a write a function inside "animations" directive, how can I trigger it inside "animatBtn" directive? I mean, in your code, inside the first line of "..... other code...." how can I call a function written in the "animations" directive?
Use the require property of the DDO to access the controller of the animations directive:
app.directive("animateBtn", function($window) {
return {
restrict: 'C',
link: postLink,
require: '^animations'
};
function postLink (scope, elem, attrs, animations) {
elem.on('click', function() {
.... other code....
});
//call animations API
animations.someMethod(args);
}
})
In the animations directive:
app.directive("animations", function() {
return {
controller: ctrl
}
function ctrl($element, $scope) {
this.someMethod = function(args) {
//code here
};
}
})
For more information, see AngularJS Comprehensive Directive API Reference - require

AngularJS: Bind a directive to a Controller via a Service update

How to bind a directive to a controller via a service update ?
I want to create the possibility to update a cart(the service) via a directive(add to cart button) and then the controller (that display the cart) will update its view.
Despite the fact that I added a watch on the service itself my controller is not been updated.
Of course it will be good if the controller and the directive doesn't share the same scope (transclude: true in the directive)
The service:
angular.module('stamModule', [])
.factory('valueService', function () {
var factory = {
data: {value: 1000},
inc: inc,
getData: getData
};
function inc() {
this.data.value++;
}
function getData() {
return this.data;
}
return factory;
})
the directive:
.directive('buttonDirective', function (valueService) {
var directive = {
restrict: 'E',
template: '<button>Inc</button>',
link: linkFnc
};
function linkFnc(scope, el) {
el.on('click', function () {
valueService.inc();
});
}
return directive;
})
The controller:
.controller('FirstController', function ($scope, valueService) {
var vm = this;
vm.serv = valueService;
$scope.$watch('vm.serv.getData()', function (newValue) {
console.log("watch");
console.log(newValue);
});
})
The html:
<body ng-app="stamModule">
<hr>
<div ng-controller="FirstController as vm">
<p>{{vm.serv.data}}</p>
<button-directive ></button-directive>
</div>
here's a demo:
https://jsfiddle.net/07tp4d03/1/
Thanks
All your code needed was a little push. No need for event broadcasting or anything like that.
The problem was, that the click event listener was working outside Angular's digest loop, and thus Angular watch wasn't working for you.
If you change your directive's code to the following, it will work.
.directive('buttonDirective', function (valueService) {
var directive = {
restrict: 'E',
template: '<button ng-click="inc()">Inc</button>',
link: linkFnc
};
function linkFnc(scope) {
scope.inc = function() {
valueService.inc();
};
}
return directive;
})
Here is a fork of your fiddle that works

Passing a controller to a directive in angularJS. Gives undefined error

I am working to add modals to my directives using ui-bootstrap and did so fine on the previous directive. I don't believe I am doing anything differently in this one but I get the ReferenceError: milestoneController is not defined when I run the edit() function from within the directive.
milestone.html (this is the template HTML for the directive below):
<div ng-controller = "milestoneController"></div>
milestone directive:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
milestoneController.editMilestoneDialog(data);
};
}
}
});
angular.module('ireg').controller('milestoneController', function ($scope, $modal){
$scope.editMilestonesDialog = function (objectid) {
//fun
}
});
EDIT: I allso felt I should mention that the milestone directive is repeated in a ng-repeat loop. Thanks!
ok you're going to want to use a transcluded scope in your directive to pass a controller function to the directive. Your directive now becomes:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid',
editMilestoneDialog:'&'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
$scope.editMilestoneDialog(data);
};
}
}
and your markup becomes:
<milestone edit-milestone-dialog="editMilestoneDialog"></milestone>

How to set a value on a directive's scope

I have multiple AngularJS directives that are nearly identical - there are only two differences: the template URL and one single element in the linking function. Both are constant for each directive. So, for simplicity's sake, this is how it looks like:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
link: function(scope, element, attrs) {
var veryImportantString = "this_string_changes";
// a few dozen lines of code identical to all directives
}
};
}]);
Now, moving the linking function to a commonly available place is obvious. What is not so obvious to me is how to set that "very important string" on the scope (or otherwise pass it to the directive) without declaring it in the HTML.
Here's what I've tried.
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: {
veryImportantString: "this_string_changes"
},
link: someCommonFunction
};
}]);
Nope, apparently the scope config doesn't take values from nobody. I can bind a value coming from the HTML attribute, but this is precisely what I don't want to do.
Also tried this:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
veryImportantString: "this_string_changes",
scope: true,
link: function(scope, element, attrs) {
var veryImportantString = this.veryImportantString;
}
};
}]);
But alas, the linking function is then called with this set to something else.
I assume this might work:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
compile: function(element, attrs) {
// no access to the scope...
attrs.veryImportantString = "this_string_changes";
return someCommonFunction;
}
};
}]);
However, I am not 100% sure this is what I want either, as it reeks of being a dirty workaround.
What are my other options?
I have devised a completely different approach: using a factory-like function to spawn directives.
var factory = function(name, template, importantString) {
app.directive(name, [function() {
return {
scope: true,
templateUrl: template,
link: function(scope, element, attrs) {
var veryImportantString = importantString;
// directive logic...
}
};
}]);
};
Then, in order to create individual directives, I simply call:
factory("myDirective", "/path/to/template.html", "important");
factory("myDirective2", "/path/to/template2.html", "important2");
What about the following:
Before wherever you define someCommonFunction, add the line
var veryImportantString = "someOptionalDefault"
This then puts veryImportantString in scope of both your someCommonFunction and .directive()
Then you can change your directive code to:
app.directive("myDirective", [function() {
return {
templateUrl: "this/path/changes.html",
scope: true,
link: function(args){
veryImportantString = "thatUberImportantValue";
someCommonFunction(args);
}
};
}]);
Proof of concept fiddle

How to require a controller in an angularjs directive

Can anyone tell me how to include a controller from one directive in another angularJS directive.
for example I have the following code
var app = angular.module('shop', []).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/js/partials/home.html'
})
.when('/products', {
controller: 'ProductsController',
templateUrl: '/js/partials/products.html'
})
.when('/products/:productId', {
controller: 'ProductController',
templateUrl: '/js/partials/product.html'
});
}]);
app.directive('mainCtrl', function () {
return {
controller: function ($scope) {}
};
});
app.directive('addProduct', function () {
return {
restrict: 'C',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
//console.log(cartController);
}
};
});
By all account I should be able to access the controller in the addProduct directive but I am not. Is there a better way of doing this?
I got lucky and answered this in a comment to the question, but I'm posting a full answer for the sake of completeness and so we can mark this question as "Answered".
It depends on what you want to accomplish by sharing a controller; you can either share the same controller (though have different instances), or you can share the same controller instance.
Share a Controller
Two directives can use the same controller by passing the same method to two directives, like so:
app.controller( 'MyCtrl', function ( $scope ) {
// do stuff...
});
app.directive( 'directiveOne', function () {
return {
controller: 'MyCtrl'
};
});
app.directive( 'directiveTwo', function () {
return {
controller: 'MyCtrl'
};
});
Each directive will get its own instance of the controller, but this allows you to share the logic between as many components as you want.
Require a Controller
If you want to share the same instance of a controller, then you use require.
require ensures the presence of another directive and then includes its controller as a parameter to the link function. So if you have two directives on one element, your directive can require the presence of the other directive and gain access to its controller methods. A common use case for this is to require ngModel.
^require, with the addition of the caret, checks elements above directive in addition to the current element to try to find the other directive. This allows you to create complex components where "sub-components" can communicate with the parent component through its controller to great effect. Examples could include tabs, where each pane can communicate with the overall tabs to handle switching; an accordion set could ensure only one is open at a time; etc.
In either event, you have to use the two directives together for this to work. require is a way of communicating between components.
Check out the Guide page of directives for more info: http://docs.angularjs.org/guide/directive
There is a good stackoverflow answer here by Mark Rajcok:
AngularJS directive controllers requiring parent directive controllers?
with a link to this very clear jsFiddle: http://jsfiddle.net/mrajcok/StXFK/
<div ng-controller="MyCtrl">
<div screen>
<div component>
<div widget>
<button ng-click="widgetIt()">Woo Hoo</button>
</div>
</div>
</div>
</div>
JavaScript
var myApp = angular.module('myApp',[])
.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomethingScreeny = function() {
alert("screeny!");
}
}
}
})
.directive('component', function() {
return {
scope: true,
require: '^screen',
controller: function($scope) {
this.componentFunction = function() {
$scope.screenCtrl.doSomethingScreeny();
}
},
link: function(scope, element, attrs, screenCtrl) {
scope.screenCtrl = screenCtrl
}
}
})
.directive('widget', function() {
return {
scope: true,
require: "^component",
link: function(scope, element, attrs, componentCtrl) {
scope.widgetIt = function() {
componentCtrl.componentFunction();
};
}
}
})
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
}

Categories