adding angular dependencies in a link function shared across multiple directives - angularjs

I have several directives that use the same link function. (The link function adds some extra state and html depending on the use case.) So I declared this as follows:
function common_linkfunc(){...}
var Directive_1 = function($scope, etc) {
return {restrict:"E", ...
link: common_linkfunc,
controller: function($scope, etc) {...}
};
}
Directive_1.$injects = ["$scope", "etc"];
angular.module("module").directive("D1", Directive_1)...;
First change was when link function required $compile. Next I need to add $templateCache and my question is how can I do this systematically?
My first approach was to rewrite common_linkfunc as
function foo($compile, $templateCache) {
return common_linkfunc($compile, $templateCache) {...}
}
and then use this in every directive:
...
link: foo($compile, $templateCache),
...
But this is copy-and-paste! Is there an easier and less error prone way to do the same?

Regardless of the solution, you'll need to pass some argument to your common link function, because Angular won't inject anything into it for you. That being said, I can think of two different approaches:
1) Use arguments
app.directive('foo', function($http, $timeout) {
return {
restrict: 'E',
link: linkFn1.apply(null, arguments)
}
});
function linkFn1($http, $timeout) {
return function(scope, element, attrs) {
// ...
};
}
The downside here is that the order of the arguments in the directive function matters. If some other directive uses a different order, the code won't work properly.
2) Use $injector
app.directive('bar', function($injector) {
return {
restrict: 'E',
link: linkFn2($injector)
}
});
function linkFn2($injector) {
var $http = $injector.get('$http'),
$timeout = $injector.get('$timeout');
return function(scope, element, attrs) {
// ...
};
}
Working Plunker

Related

Compiling interpolation manually

I have a custom directive and rendering that directive using ng-repeat. what I need is I want to compile interpolation before passing into my custom directive.
Find plnkr below
https://plnkr.co/edit/bjdBSKCFPhgbE2aREupy?p=preview
Here I want to compile interpolation in this code <display-id mycompile id={{op.id}}> </display-id> using mycompile directive.
app.directive('mycompile', function ($compile, $interpolate) {
return {
restrict: 'EA',
replace: true,
compile: function ($scope, $elm, $attrs) {
return {
pre: function ($scope, $elm, $attrs) {
$interpolate($elm[0])($scope);
}
}
}
}
})
$interpolateProvider expects string to be an argument, so what you need is to convert your element to string and then back to DOM element if you want,
it could be achieved with outerHTML property like so:
$interpolate($elm.prop('outerHTML'))($scope);

Call method in controller from directive

HTML :
<div id="idOfDiv" ng-show="ngShowName">
Hello
</div>
I would like to call the function which is declared in my controller from my directive.
How can I do this? I don't receive an error when I call the function but nothing appears.
This is my directive and controller :
var d3DemoApp = angular.module('d3DemoApp', []);
d3DemoApp.controller('mainController', function AppCtrl ($scope,$http, dataService,userService,meanService,multipartForm) {
$scope.testFunc = function(){
$scope.ngShowName = true;
}
});
d3DemoApp.directive('directiveName', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
testFunc : '&'
},
link: function(scope) {
node.on("click", click);
function click(d) {
scope.$apply(function () {
scope.testFunc();
});
}
};
}]);
You shouldn't really be using controllers and directives. Angularjs is meant to be used as more of a component(directive) based structure and controllers are more page centric. However if you are going to be doing it this way, there are two ways you can go about it.
First Accessing $parent:
If your directive is inside the controllers scope you can access it using scope.$parent.mainController.testFunc();
Second (Preferred Way):
Create a service factory and store your function in there.
d3DemoApp.factory('clickFactory', [..., function(...) {
var service = {}
service.testFunc = function(...) {
//do something
}
return service;
}]);
d3DemoApp.directive('directiveName', ['clickFactory', function(clickFactory) {
return {
restrict: 'EA',
transclude: true,
link: function(scope, elem) {
elem.on("click", click);
function click(d) {
scope.$apply(function () {
clickFactory.testFunc();
});
}
};
}]);
Just a tip, any time you are using a directive you don't need to add $scope to the top of it. scope and scope.$parent is all you really need, you will always have the scope context. Also if you declare scope :{} in your directive you isolate the scope from the rest of the scope, which is fine but if your just starting out could make things quite a bit more difficult for you.
In your link function you are using node, which doesn't exist. Instead you must use element which is the second parameter to link.
link: function(scope, element) {
element.on("click", click);
function click(d) {
scope.$apply(function() {
scope.testFunc();
});
}

loop function through nested directive - angularjs

I try to loop a function through a nested directive. From the console.info in myCtrl I would expect the string "this should be logged".
angular.module('myApp')
.controller('myCtrl', function ($scope) {
$scope.aFunction = function(input) {
console.info(input.message);
}
})
.directive('levelOneDirective', function () {
return {
templateUrl: '<level-two-directive aFunction="aFunction(object)"></level-two-directive>',
restrict: 'EA',
scope: {
aFunction:"&"
},
link: function (scope, element, attrs) {
}
};
})
.directive('levelTwoDirective', function () {
return {
templateUrl: '<div ng-click="aFunction({message: 'this should be logged'})"></div>',
restrict: 'EA',
scope: {
aFunction:"&"
},
link: function (scope, element, attrs) {
}
};
});
And in my index.html I have something like:
<div ng-controller="myCtrl">
<level-one-directive aFunction="aFunction(object)"></level-one-directive>
</div>
But the console says undefined.
How to connect a function through nested directives?
You have several mistakes in your code but I assume it's because you try to adjust it to the question (such as aFunction as attribute instead of a-function and templateUrl instead of template).
You can have a 2-way binding (=) in your directives (both of them):
scope: {
aFunction:"="
},
And pass the function reference without the object:
<level-one-directive a-function="aFunction"></level-one-directive>
In the second directive HTML have:
<div ng-click="invokeFunction()"></div>
And then in the link function of your 2nd directive you can do:
scope.invokeFunction = function () {
scope.aFunction({message: 'this should be logged'});
}
The above works and I find it more convenient than & binding, which as you can see, is not quite easy to work with, and frankly I haven't messed around enough with it to figure out how (and if possible) to pass arguments through it.
I've seen this question, but it's binding straight on the link function, and you want it with an ng-click so it might not work for you. But perhaps you'll find your solution there.

Angularjs not picking up data-src

For some reason Angular isn't catching my data-src
When I do this:
<div foo data-src="{{ my_url }}"></div>
I get back nothing. However, it works when I manually type it: i.e
<div foo data-src="blah"></div>
What am I doing wrong here?
More of my code:
var foo = angular.module('foo', []);
foo.directive('foo', function ($document, $rootScope, $http, $resource) {
return {
restrict: 'A',
scope: true,
link: function (scope, elem, attrs) {
console.log(attrs.src)
}
});
[Edit]
I forgot to use camelCase for the attribute name when calling attrs.$observe(). Also, it looks like you do need to use ng-data-source). Correction made below...
You want to use attrs.$observe() in the link function for your directive. Angular will watch the attribute and execute a callback so the directive can be notified when the value of the interpolated expression changes.
Here's a link to a fiddle created by Mark Rajcok that demonstrates it. And a link to the documentation as well.
Finally, here is some sample code using your directive:
var foo = angular.module('foo', []);
foo.directive('foo', function ($document, $rootScope, $http, $resource) {
return {
restrict: 'A',
scope: true,
link: function (scope, elem, attrs) {
attrs.$observe('ngDataSrc', function(newValue) {
console.log('interpolated value', newValue);
}
}
});

Angular Directives: Link to Controller without Services

I'm gradually getting the hang of Angular directives and so far, have resorted to creating a service as an intermediary between controllers.
I was just wondering, in the context of directives (and linking functions) is it possible to give the controller access to variables from the linking function? (Without a service or global variables).
module.exports = function() {
return {
restrict: 'A',
templateUrl: 'partials/collection',
link: function(scope, element, attrs) {
var name = attrs.collectionName;
// from here
},
controller: function($scope, socket) {
$scope.models = [];
// to here
socket.on('ready', function() {
socket.emit(name + '/get');
});
}
}
};
I want the collection-name attribute's value to be available within my controller, so that I can make appropriate socket calls. Any idea?
You can add a method on the controller and the call it from the link function.
controller: function($scope, socket) {
this.setSocket = function(name){
{...}
}
}
On link:
link: function(scope, element, attrs, controller){
var name = attrs.collectionName;
controller.setSocket(name);
}
They share the same scope, so this should work.
module.exports = function() {
return {
restrict: 'A',
templateUrl: 'partials/collection',
link: function(scope, element, attrs) {
scope.name = attrs.collectionName;
// from here
},
controller: function($scope, socket) {
$scope.models = [];
// to here
socket.on('ready', function() {
socket.emit($scope.name + '/get');
});
}
}
};
There are a couple of ways of doing what you want
Just put everything in the link function. You can set functions and variables on the scope just like you might put in a controller.
Just put everything in the controller, in terms of setting scope variables or functions. It is injected with $attrs, which contains the normalised attribute values, so you have access to the attributes if you need them.
As far as I know, in most cases it doesn't make a difference where you assign variables or functions on the scope. The main difference between the two is that if you want to give your directive a public API, so other directives can communicate to it via require, then you must use this.something in the controller.
There maybe a better way to do it, but I've managed to get around the problem by changing my controller functions dependencies to include an $element argument. Then just used jqLite to get the value of the attribute in question.
controller: function($scope, $element, socket) {
var name = $element.attr('collection-name');
}
It's not fantastically elegant, but it works.

Resources