Why this syntax for link function inside directive is wrong? - angularjs

I have wasted a lot of time to figure out why my directive link function is not working:
In directive code you can write this line for controller:
controller: ['$scope', '$element', function($scope, $element) {
}],
But you CAN NOT write the same for link, like:
link: ['scope', 'element', 'attrs', function(scope, element, attrs) {}]
Only this will work:
link: function(scope, element, attrs) {}
Why is that?

Because controller is an injectable function, whereas link is not. It's a traditional function where the order of arguments matter, and not their name. It accepts, in order
the scope of the directive,
the element of the directive
the attributes of this element
the controller
a transclude function
This is documented: https://docs.angularjs.org/api/ng/service/$compile#-link-

Related

Which function will execute first in angularjs directives? link or controller?

I have gone through this link https://www.undefinednull.com/2014/07/07/practical-guide-to-prelink-postlink-and-controller-methods-of-angular-directives/, they said the order(first to last) of execution of link and controller is
Controller,
Pre-Link function,
Post-Link function
But here I read AngularJS: What is the need of the directive's link function when we already had directive's controller with scope? link executes before the controller. Which one I should believe in?
If it was first link then controller it wouldn't be possible to require other directives and and use theirs controllers in link function.
Take a look at the code from documentation:
var directiveDefinitionObject = {
controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
controllerAs: 'stringIdentifier',
require: 'siblingDirectiveName', // requiring another directive
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) { ... }, //siblingDirectiveName's controller is available in link function
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
},
};
return directiveDefinitionObject;
});
To support this statement we can read o the same page :
controller
Controller constructor function. The controller is instantiated before the pre-linking phase and can be accessed by other directives (see require attribute). This allows the directives to communicate with each other and augment each other's behavior.

Unable to share controller instance via require in angular directive

I have a confirm box which I want to attach another directive to called confirmBoxToggle, but I'm unable to share the same controller instance in order for it to work. I've looked at multiple examples and also read the docs to see if I'm doing something crazy, but the only thing I can see is that I don't declare my controller inside the directive but rather giving a reference to it. But I can't see this being the issue.
I get this error when doing this:
Controller 'confirmBox', required by directive 'confirmBoxToggle', can't be found!
What am I doing wrong?
The box directive:
core.directive('confirmBox', [function() {
return {
scope: {},
controller: 'ConfirmBoxCtrl',
controllerAs: 'confirmBox',
templateUrl: 'app/views/components/core/confirmation-box.html',
link: function(scope, element, attrs, ctrl) {
}
};
}]);
The toggle directive:
core.directive('confirmBoxToggle', [function() {
return {
scope: {},
require: '^confirmBox',
link: function(scope, element, attrs, ctrl) {
element.on('click', function() {
ctrl.toggleBox();
});
}
};
}]);
The controller for both directives:
core.controller('ConfirmBoxCtrl', [function() {
var confirmBox = this;
confirmBox.toggleBox = function() {
confirmBox.isActive = !confirmBox.isActive;
};
}]);
I use the directives like this:
<confirm-box></confirm-box>
<span confirm-box-toggle>Delete</span>
Controller confirm or confirmBox can't be found?
Do you use that controller elsewhere, and does it work on it's own?
Basically you used require: '^confirmBox' that means while using confirmBoxToggle directive, it must be wrap with confirmBoxdirective(should be there in parent element as ^) so that you could access to the confirmBox link function 4th parameter.
HTML
<confirm-box>
<span confirm-box-toggle>Delete</span>
</confirm-box>
Also you can't have templateUrl inside your confirmBox directive, which will replace your <span confirm-box-toggle>Delete</span> html by the template loaded form templateUrl.
Demo Plunkr

4th argument to the link function

I have a directive written by another developer that basically has following configuration:
{
controller: MyController,
controllerAs: 'myController',
link: function(scope, $element, attrs, ctrl) {
// Does some setup that requires controller
}
}
This works fine, controller is passed as fourth argument, directive works.
Now I decided to make the directive more flexible, reusable and stuff. So, to directive configuration I added
require: '?ngModel'
Suddenly, now my controller is never passed to a link function. There is no array for the fourth argument, there's no fifth argument, nada.
I tried adding controller to require directive - but it still does not find it.
How do I add require and pass the controller?
require means that the directive you required (ngModelController in this case) will have its controller sent as the fourth argument of the linking function. The default controller is the directive's, whose linking function is called, controller but requiring another directives overrides it (even if it's an optional requirement and the required directive isn't present, in which case the fourth argument will be undefined). Fortunately, require can be an array of directives, so this will work:
module.directive('myDirective', function() {
return {
controller: MyController,
controllerAs: 'myController',
require: ['myDirective', '?ngModel'],
link: function(scope, $element, attrs, controllers) {
var MyDirectiveController = controllers[0]; //this directive's controller
var ngModelController = controllers[1];
}
};
});
PLUNKER

How to remove attribute directives from element directive in AngularJS?

I have an element directive and an attribute directive:
<my-element my-attribute="value"></my-element>
my-attribute is a directive which require ngModel:
app.directive('myAttribute', [
function() {
var definition = {
restrict: 'A',
require: 'ngModel',
link: function ($scope, $element, $attrs, ctrl) {...}
}
return definition;
my-element can be used without ng-model but the template of my-element contains input which always has ng-model. The my-attribute should be removed from my-element and added to input inside my-element. In my compile function in my-element directive I have:
var value = element.attr('my-attribute');
element.remove('my-attribute');
element.find('input').attr('my-attribute', value);
It works ok when attribute directive doesn't have require: 'ngModel'. But if I have ngModel required I get:
Error: [$compile:ctreq] Controller 'ngModel', required by directive 'myAttribute', can't be found!
Why? How can I fix this?
When you have require: 'ngModel' you'll receive an error if you don't have an ng-model attribute on that element the directive is on - that's what this directive property means. It says "give me the controller of ngModel's controller to be available as the 4th parameter in the link function.
To avoid this, if you don't always need to use it, you can add a ? sign before the ngModel and it means it's optional:
....
require: '?ngModel',
....
If you want to remove an attribute you should use
element.removeAttr('my-attribute');
I'm not sure about your intention in the link function, but you could make the link function in myAttribute run only if there is a ng-model attribute on the element:
require: 'ngModel',
compile: function(el, attr) {
if (attr.ngModel) {
return function(scope, elm, attr, ngModel) {
// link function
}
}
}
This is possible because angular doesn't try to find the required directives until it's going to execute the link function for that directive.

How to override image source within a directive

I want to override an image src within a directive. I've tried the following, none of which work.
app.directive('imgTransform', function () {
return {
retrict: 'A',
link: function (scope, elem, attrs) {
elem.attr('ng-src', 'foo');
attrs.ngSrc = 'foo';
elem.attr('src', 'foo');
attrs.src = 'foo';
}
};
});
Does the link function execute before the DOM binding takes place?
I also tried creating an isolate scope and binding to the ngSrc attribute:
scope: {
src: '#ngSrc'
},
Then setting scope.ngSrc in the link function. This did not work either.
Am I missing something?
http://jsfiddle.net/benfosterdev/y7JE9/
You need to modify it before you get to your linking function.
I'd suggest in the controller function, as opposed to digging to deep into the $compiler.
restrict: "A",
controller: function ($scope, $element, $attrs) {
$attrs.$set('ngSrc', 'someOtherValue');
},
link: function (scope, el, attrs) {...}
Here's a fiddle: http://jsfiddle.net/y7JE9/5/
Also, use $observe when looking up interpolated values in your link function. Good luck : )

Resources