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.
Related
I have my directive and I want to change the controller and html of a small section of the page onclick of a submit button. Changeing the HTML is working but changing the controller is not.
The line: attrs.ngController = "wordlistsPageController";
is not working. Please let me know what I can do to dynamically set the controller since this does not work.
It is a little web app game, so I do not want to change the whole page, just the game section between 3 game pages.
Here is my directive:
myApp.directive('gamePanel', function ($compile) {
return {
restrict: 'E',
templateUrl: "templates/wordlistPage.ejs",
link : function(scope, elem, attrs) {
attrs.ngController = "wordlistsPageController";//NOT WORKING
scope.submitWordlist = function() {
//change html
elem.html('<ng-include src="\'templates/gameOptionsDialogPage.ejs\'"></ng-include>');
$compile(elem.contents())(scope);
//change controller
attrs.ngController = "optionsPageController";///NOT WORKING
};
scope.backToWordlist = function() {
elem.html('<ng-include src="\'templates/wordlistPage.ejs\'"></ng-include>');
$compile(elem.contents())(scope);
attrs.ngController = "wordlistsPageController";///NOT WORKING
};
}//end link
}
});//end directive
How to Dynamically Change a Directive Controller
To instantiate different controllers inside a directive linking function, use the $controller service.
angular.module("myApp").directive('gamePanel', function ($controller) {
return {
restrict: 'E',
template: "<p>Game Panel Template</p>",
//controller: "pageController as vm",
link : function(scope, elem, attrs, ctrl, transclude) {
var locals = { $scope: scope,
$element: elem,
$attrs: attrs,
$transclude, transclude
};
scope.vm = $controller("pageController", locals);
}//end link
}
});
The above example instantiates a controller and puts it on scope as vm.
Best practice
The injected locals should follow the conventions of the locals provided by the $compile service. For more information on $compile service injected locals, see AngularJS $compile service API Reference API -- controller.
Beware: memory leaks
When destroying scopes, all watchers created on those scopes should be de-registered. This can be done by invoking scope.$destroy().
When destroying controllers, all watchers created by the controller should be de-registered. This can be done by invoking the de-register function returned by each $watch.
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
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
When using require: "^directive1" in directive2 You can access it's variables and function in link function link: function (scope, element, attrs, ctrl) {} - ctrl is directive's 1 controller.
How to use ctrl in controller - not link func?
Is there anything that can be shared between the controllers? Yes, there is: the directives can share the same scope!
You need to expose the controller on the shared scope object. The simplest way to do that is in the link function of your second directive:
var linkFn = function (scope, element, attrs, ctrl) {
scope.exposedCtrl = ctrl;
};
As long as you're not using isolated scopes, you should be able to access $scope.exposedCtrl in your parent directive. Just remember that the exposedCtrl property won't be initialized until the child directive's link function has been called.
I found perfect solution for my issue:
.controller('myCtrl', ['$element', function($element) {
var parentCtrl = $element.parent().controller('diective1');
}])
source: https://github.com/angular/angular.js/issues/10998#issuecomment-73308256
My question is related to directories and two of its properties.
What is the reason that the parameters for the link function don't have a $ sign as a prefix?
e.g
link: function (scope, element, attrs, ctrl) {
scope.$watch("item.quantity", function () {
ctrl.updateTotal();
});
}
in contrast to the controller property:
controller: function ($scope, $element, $attrs) {
}
I know that the first one is a link function and thus is written without a $ sign.
But why make a difference? Is it partly because you can create your own scope in a directive and as a result the scope doesn't necessarily mean to be related with the $scope of the controller?
That would explain the scope parameter but I can't think of any explanation regarding the other ones.
Thanks in advance.
By convention the $ prefixes are not used with functions which are not injected by the $injector. So the link: function (scope, element, attrs [,ctrl]) will not be injected. It always has the same parameters in the same order.
When dealing with functions where the dependency injection provides the arguments, you must use $scope, otherwise it won't be injected.
TL;DR
This is more or less to confuse Angular learners, but it is vital that you have to use positioned parameters for some functions (like postLink).
Additional info and links
For those of you who want to know the exact details, I recommend reading following chapters of AngularJS Guide:
Creating Directives that Communicate, please read the paragraph above the Summary.
Comprehensive Directive API
In the latter link you can see the directive definition object containing some methods with positioned parameters (please note that in this example there are many options which don't make sense in combination):
myModule.directive('directiveName', function factory(injectables) {
var directiveDefinitionObject = {
template: function(tElement, tAttrs) { ... },
templateUrl: function(tElement, tAttrs) { ... },
// controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) { ... },
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
},
link: {
pre: function preLink(scope, iElement, iAttrs, controller) { ... },
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
// or
link: function postLink(scope, iElement, iAttrs, controller) { ... }
};
return directiveDefinitionObject;
});
If you use a controller function in the DDO (abreviation for directive definition object), the arguments will be injected (hence the prefix $):
// ... somewhere in the DDO ...
controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
You can find documentation for the "special" injectables here
Sure there are more than the mentioned functions with positioned parameters, e.g. the $animate enter(), leave() etc.
Angular is just calling a method with these specific objects as parameters. If we wanted to (but of course it goes against convention), we could name these parameters whatever we wanted to, as long as we retained the order they were being used.