angular add function to $element in directive - javascript

I have a page with two directives. I need to invoke a function in one directive from the other. I have added the function to the $element object of the first directive and used jQuery to invoke it from the other. Is this the right approach or should I be using a context object shared by both directives?
//inside directive 1 link fn
$element[0].foo = function(){
console.log("test");
}
...
//inside directive 2 link fn
$('.className').foo()
The two directives are elements on a page with a shared controller. Each has an isolated scope. This seems to work well. Are there any reasons why I should not do this?

are the 2 directives sharing the same controller? If so, you could call a function on the controller from one directive which would "notify" the other. Or, you could use events, check this answer here

This is not the way you should do it. Try to avoid using $element, it does DOM manipulations which are slow and Angular takes care of by itself. To invoke a function in directiveB, triggered from directiveA, you better make a service.
angular.service('Communication', function () {
var _listeners = [];
return {
addListener: function (callback) {
_listeners.push(callback);
},
removeListener: function () {/* Clean up */},
invokeListeners: function (data) {
_listeners.forEach(function (listener) {
listener(data);
});
}
}
});
angular.directive('directiveB', ['Communication', function (Communication) {
return {
restrict: 'AE',
scope: {},
controller: directiveB
};
function directiveB (scope, element, attrs) {
Communication.addEventListener(function (data) { /* Do stuff with data */ });
}
}]);
angular.directive('directiveA', ['Communication', function (Communication) {
return {
restrict: 'AE',
scope: {},
controller: directiveA
};
function directiveA (scope, element, attrs) {
// trigger event in directive B
Communication.invokeListeners('test');
}
}]);

Related

Angular directive: whats the difference between scope in controller vs scope in link function?

Im learning about Angular directives and I can't wrap my head around the scope topic. Suppose I have this custom directive which is named parentDirective. It has a controller property and a link property, as follows:
angular.module("app").directive("parentDirective", function () {
return {
restrict: "E",
templateUrl: "dirs/parent.html",
scope:{
character: "="
},
controller: function ($scope) {
$scope.getData = function (data) {
console.log(data);
}
},
link: function (scope,elem, attrs) {
elem.bind("click", function (e) {
//get object here?
});
scope.getData = function (data) {
console.log(data);
}
}
}
});
Its template is defined as follows:
<p ng-click="getData(character)">
{{character.name}}
</p>
I can get the character object in the controller function through the $scope variable and I have access to the same data in the link function through scope. Whats the difference between the two methods in this regard? Second question, Is it possible to bind a click to the directive and get the object like this:
elem.bind("click", function (e) {
//get object here?
});
Scope is specific to current directive instance and is the same object in both functions.
For defining methods on the scope, there's no difference if they are defined in controller or link function, unless there is race condition that requires the method to be defined as early as possible. For this reason it makes sense to define scope methods in controller.
Event handler doesn't differ from any other function, it is
elem.on("click", function (e) {
scope.$apply(function () {
scope.character...
});
});
scope.$apply(...) wrapper doesn't hurt anyway, but the necessity of it depends on what happens with scope.character.
The directive can have only controller and no link. Current Angular versions (1.5+) suggest the style where bindToController + controllerAs are used instead of scope bindings as common ground for directives and components.
Then the directive may look like
restrict: "E",
template: '<p>{{$ctrl.character.name}}</p>',
controllerAs: '$ctrl',
bindToController: { character: "=" },
controller: function ($element, $scope) {
var self = this;
self.getData = function (data) { ... };
$element.on("click", function (e) {
scope.$apply(function () {
self.character...
});
});
}
link function may appear as $postLink controller hook, but here it is not needed.

call a global function in directives in ng

i have a function which i call more than once ( in different directives).
So is there a way to call a function in every directive?
One solution would be to make the function as a service.
But the service does need a return value, doesn´t it?
this.changeDesign = function (currentstep) {
//do something
};
this method I call many times.
You should consider to avoid using global variables or put something on the global scope. Maybe it works now, but if your project gets bigger, you will probably get a lot of problems. More infos for example: https://gist.github.com/hallettj/64478
Here is an example, how you can use a factory, which you can inject into your directives (coding style inspired by John Papa https://github.com/johnpapa/angular-styleguide#factories):
(function () {
"use strict";
angular.module('my-app').provider('MyFactory', MyFactory);
MyFactory.$inject = [];
function MyFactory() {
var service = {
changeDesign: myChangeDesignImpl
};
function myChangeDesignImpl() { }
this.$get = function() {
return service ;
};
}
})();
You can now inject your service into a directive like this:
(function () {
"use strict";
angular.module('my-app').directive('MyDirective', MyDirective);
MyDirective.$inject = ["MyFactory"];
function MyDirective(MyFactory) {
var directive = {
restrict: 'E',
template: "/template.html",
link: link
};
return directive;
function link(scope, el, attr) {
MyFactory.changeDesign();
}
}
})();
you can do it in a directive that you will delcare on your elements.
angular.module('myDesignModule', [])
.directive('myDesignDirective', function() {
return {
link: function(scope, element, attrs, ctrl){
element.addClass("myClass");
}
};)

How to pass the angular's directive's link and controller to it

Say that I have a straight forward directive:
angular
.module('myApp')
.directive('myDiv', ['MyService1', 'MyService2',
function(MyService1, MyService2) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
scope.myVars = MyService1.generateSomeList();
MyService2.runSomeCommands();
// Do a lot more codes here
// Lines and Lines
// Now run some embedded function here
someEmbeddedFunction();
function someEmbeddedFunction()
{
// More embedding
// This code is really getting nasty
}
}
}
}
]);
The code above has so much indentation and crowded that at least to me, it is very hard to read and unenjoyable to work with.
Instead, I want to move the link and someEmbeddedFunction out and just call them. So something along the lines of this:
function link(scope, element, attrs, MyService1, MyService2)
{
scope.myVars = MyService1.generateSomeList();
MyService2.runSomeCommands();
// Do a lot more codes here
// Lines and Lines
// Now run some embedded function here
someEmbeddedFunction();
}
function someEmbeddedFunction()
{
// This is much nicer and less tabbing involved
}
angular
.module('myApp')
.directive('myDiv', ['MyService1', 'MyService2',
function(MyService1, MyService2) {
return {
restrict: 'E',
link: link // This is line that I need to get it to work
}
]);
The problem is that MyService1 and MyService2 are not passed to the link function (i.e. if I only had a link function with scope, element, attrs then the code above would work just fine). How can I pass those variables as well?
I tried to call the function as link: link(scope, element, attrs, MyService1, MyService2) but then it says scope, element, attrs are undefined.
Note I realize that the someEmbeddedFunction can right now be moved out without a problem. This was just for demonstrating purposes.
Edit
The only way I can think to get this to work is call the link function from the directive this way:
link: function(scope, element, attrs) {
link(scope, element, attrs, MyService1, MyService2);
}
As you observed, the only way to call your non-standard link function is to do so manually within a "standard" link function.
i.e.
link: function(scope, element, attrs) {
link(scope, element, attrs, MyService1, MyService2);
}
This is because the link function doesn't get injected like other functions in Angular. Instead, it always gets the same series of arguments (regardless of what you call the function parameters):
The scope
The element (as an angular.element() instance)
The attrs object
An array or single controller instance that you required
A transclude function (if your directive uses transclusion)
Nothing else.
I use this scheme to keep it simple & readable:
var awesomeDir = function (MyService, MyAnotherService) {
var someEmbeddedFunction = function () {
MyService.doStuff();
};
var link = function ($scope, $elem, $attrs) {
someEmbeddedFunction();
};
return {
template: '<div>...</div>',
replace: true,
restrict: 'E',
link: link
};
};
awesomeDir.$inject = ['MyService', 'MyAnotherService'];
app.directive('awesomeDir', awesomeDir);

Call function from directive in $rootScope - AngularJS

What I want to do:
I'm trying to create a function within a directive that can be called from the $rootScope.
The Problem:
It seems to only be working on the last item in the DOM which has this directive. I'm guessing that what's happening is rootScope.myFunction gets overwritten each time this directive runs.
The Question:
How can I create one function in the $rootScope which, when called, runs the internal function for each directive instead of just the last one?
The Relevant Code:
(function() {
angular.module('home')
.directive('closeBar', closeBar);
closeBar.$inject = ['$rootScope', '$window'];
function closeBar(rootScope, window) {
return {
scope: true,
restrict: 'A',
link: function(scope, element, attrs) {
var myFunction = function() {
// do stuff
};
myFunction();
rootScope.myFunction = function() {
myFunction();
};
}
};
}
})();
Then in a different script, I want to call:
rootScope.myFunction();
Note, I'm not allowed to share actual code from the project I'm working on so this is a more general question not tied to a specific use case.
A simple solution would be to create a special function in your rootScope that accepts functions as an argument, and pushes it into an array, and you will be able to invoke that function later which will call all the registered functions.
angular.module('myApp').run(['$rootScope', function($root) {
var functionsToCall = [];
$root.registerDirectiveFunction = function(fn, context) {
context = context || window;
functionsToCall.push({fn: fn, context: context});
}
$root.myFunction = function(args) {
functionsToCall.forEach(function(fnObj) {
fnObj.fn.apply(fnObj.context,args);
});
}
}
And in your directive:
link: function($scope, $el, $attr) {
function myFunct() {
}
$scope.$root.registerDirectiveFunction(myFunct, $scope);
//call it if you want
$scope.$root.myFunction();
}
This should cover your scenario.

Modify template in directive (dynamically adding another directive)

Problem
Dynamically add the ng-bind attribute through a custom directive to be able to use ng-bind, ng-bind-html or ng-bind-html-unsafe in a custom directive with out manually adding to the template definition everywhere.
Example
http://jsfiddle.net/nstuart/hUxp7/2/
Broken Directive
angular.module('app').directive('bindTest', [
'$compile',
function ($compile) {
return {
restrict: 'A',
scope: true,
compile: function (tElem, tAttrs) {
if (!tElem.attr('ng-bind')) {
tElem.attr('ng-bind', 'content');
$compile(tElem)
}
return function (scope, elem, attrs) {
console.log('Linking...');
scope.content = "Content!";
};
}
};
}]);
Solution
No idea. Really I can not figure out why something like the above fiddle doesn't work. Tried it with and with out the extra $compile in there.
Workaround
I can work around it might adding a template value in the directive, but that wraps the content in an extra div, and I would like to be able to that if possible. (See fiddle)
Second Workaround
See the fiddle here: http://jsfiddle.net/nstuart/hUxp7/4/ (as suggested by Dr. Ikarus below). I'm considering this a workaround for right now, because it still feels like you should be able to modify the template before you get to the linking function and the changes should be found/applied.
You could do the compiling part inside the linking function, like this:
angular.module('app').directive('bindTest', ['$compile', function ($compile) {
return {
restrict: 'A',
scope: true,
link: {
post: function(scope, element, attrs){
if (!element.attr('ng-bind')) {
element.attr('ng-bind', 'content');
var compiledElement = $compile(element)(scope);
}
console.log('Linking...');
scope.content = "Content!";
}
}
};
}]);
Let me know how well this worked for you http://jsfiddle.net/bPCFj/
This way seems more elegant (no dependency with $compile) and appropriate to your case :
angular.module('app').directive('myCustomDirective', function () {
return {
restrict: 'A',
scope: {},
template: function(tElem, tAttrs) {
return tAttrs['ng-bind'];
},
link: function (scope, elem) {
scope.content = "Happy!";
}
};
});
jsFiddle : http://jsfiddle.net/hUxp7/8/
From Angular directive documentation :
You can specify template as a string representing the template or as a function which takes two arguments tElement and tAttrs (described in the compile function api below) and returns a string value representing the template.
The source code tells all! Check out the compileNodes() function and its use of collectDirectives().
First, collectDirectives finds all the directives on a single node. After we've collected all the directives on that node, then the directives are applied to the node.
So when your compile function on the bindTest directive executes, the running $compile() is past the point of collecting the directives to compile.
The extra call to $compile in your bindTest directive won't work because you are not linking the directive to the $scope. You don't have access to the $scope in the compile function, but you can use the same strategy in a link function where you do have access to the $scope
You guys were so close.
function MyDirective($compile) {
function compileMyDirective(tElement) {
tElement.attr('ng-bind', 'someScopeProp');
return postLinkMyDirective;
}
function postLinkMyDirective(iScope, iElement, iAttrs) {
if (!('ngBind' in iAttrs)) {
// Before $compile is run below, `ng-bind` is just a DOM attribute
// and thus is not in iAttrs yet.
$compile(iElement)(iScope);
}
}
var defObj = {
compile: compileMyDirective,
scope: {
someScopeProp: '=myDirective'
}
};
return defObj;
}
The result will be:
<ANY my-directive="'hello'" ng-bind="someScopeProp">hello</ANY>

Categories