I am working on custom directive of angular 1.6
I want to call a function after my directive is loaded. Is there a way to achieve this?
I tried link function, but it seems the link function is executed before the directive is loading.
Any help will be appreciated.
This is my directive code
<pagination total-data= noOfRecords></pagination>
app.directive("pagination", [pagination]);
function pagination() {
return {
restrict: 'EA',
templateUrl: 'template/directives/pagination.html',
scope: {
totalData: '=',
},
link: dirPaginationControlsLinkFn
};
}
Since you are using AngularJS V1.6, consider making the directive a component:
app.component("pagination", {
templateUrl: 'template/directives/pagination.html',
bindings: {
totalData: '<',
},
controller: "paginationCtrl"
})
And use the $onChanges and $onInit life-cycle hooks:
app.controller("paginationCtrl", function() {
this.$onChanges = function(changesObj) {
if (changesObj.totalData) {
console.log(changesObj.totalData.currentValue);
console.log(changesObj.totalData.previousValue);
console.log(changesObj.totalData.isFirstChange());
};
};
this.$onInit = function() {
//Code to execute after component loaded
//...
});
});
Components are directives structured in a way that they can be automatically upgraded to Angular 2+ components. They make the migration to Angular 2+ easier.
For more information, see
AngularJS Developer Guide - Components
AngularJS 1.5+ Components do not support Watchers, what is the work around?
You might add a watch to totalData in link function and do the stuff there.
scope.$watch('totalData', function () {
if (scope.totalData && scope.totalData.length > 0) { //or whatever condition is required
}
});
You can try calling your function inside $onInit() or inside $postLink() docs might help
Related
I'm currently coding a tab navigation example to gain practical experience with Angular. This example uses custom directives and controller inheritance.
Plunker can be found here.
The issue: once the directives have finished processing I'd like to select a default tab to display. But at the point of calling the selectTab method of the myTabs controller (line 41 in script.js $ctrl.selectTab(0)), Angular hasn't yet finished processing the myTab directive (which generates the tab links), so the tabs array is empty and the selection fails.
I tried using $timeout without a delay, but that fails. $timeout only works with a delay of say 500ms set, which is hacky.
Is there an event or command available that signals the end of Angular processing certain directives, particularly directives that inherit from one another?
What I suspect is happening:
The myTabs directive finishes processing then fires its link function, but it's fired before the myTab directive is done processing. I can't place the selectTab method call within the myTab link function because would get called multiple times (based on the number of tabs processed.) Hope I'm explaining this clearly...I need a massage
app.directive('myTabs', ['$timeout', function($timeout) {
return {
restrict: 'E',
transclude: true,
controllerAs: 'myTabsCtrl',
templateUrl: 'my-tabs.htm',
scope: {},
controller: function ($scope) {
vm = this;
this.tabs = [];
this.addTab = function (tab) {
this.tabs.push(tab);
};
this.selectTab = function selectTab (tabIndex) {
for (var i = 0; i < this.tabs.length; i++) {
this.tabs[i].selected = (i === tabIndex ? true : false);
}
};
},
link: function ($scope, $element, $attrs, $ctrl) {
$timeout(function () {
$ctrl.selectTab(0);
});
}
};
}]);
app.directive('myTab', function() {
return {
restrict: 'E',
require: '^^myTabs',
transclude: true,
templateUrl: 'my-tab.htm',
scope: {
title: '#'
},
link: function (scope, element, attrs, ctrl){
scope.tab = {
title: scope.title,
selected: false
};
ctrl.addTab(scope.tab);
}
};
});
Each individual tab is registering itself with the parent directive controller. Simply set the selected flag on the first tab when it registers itself:
this.tabs = [];
this.addTab = function (tab) {
console.log("add tab ", tab);
if (!this.tabs.length) {
//Set flag for first tab to register
tab.selected = true;
};
this.tabs.push(tab);
};
The DEMO on PLNKR
AngularJS 1.5.3 introduced the $postLink life-cycle hook:
Life-cycle hooks
Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the directive:
$postLink() - Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain templateUrl directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs.
--AngularJS $compile Service API Reference -- Life-cycle hooks
The $postLink life-cycle hook won't work in this case because the directives use templateUrl. It also won't work with directives that build DOM after postlink; ng-repeat, ng-if, ng-include, etc.
This is my first question so I hope I can explain the situation
The angularJs documentation here talks about having the directive templateUrl as a function to be returned dynamically. There is also a Plunkler live demo here.
.directive('....', function() {
return {
templateUrl: function(elem, attr){
return **.... scope.Somthing ...**;
}
};
});
the function does not take a scope parameter and this is the main issue
The only way so far that i found is to set the TemplateUrl dynamically with relevance to the directive scope is this way
.directive('....', function() {
return {
link: function (scope, element, attrs) {
scope.getTemplateUrl = function () {
return **.... scope.Somthing ...**;
};
},
template: '<ng-include src="getTemplateUrl()"/>'
};
});
another solution is
.directive('....', function() {
return {
controller: function ($scope) {
$scope.getTemplateUrl = function () {
return **.... scope.Somthing ...**;
};
},
template: '<ng-include src="getTemplateUrl()"/>'
};
});
my first issue is that this looks like patch to the problem
my second issue is having to build html string in the directive.
Is there any other way to achive ?
Lets split your problem for 2 problems :D
First problem is about storing template in js.
This problem can be solved using the $templateCache and injecting
<script id="myTemplate.html" type="text/ng-template"> <div> SOME MARKUP <div> </script>
Here you can read more about this
Second problem is about dynamic templating.
So there is 2 solutions (as far as i know :D)
First solution you have already mentioned - using ng-include.
Second solution is to use $compile to dynamicly compile html with angular directives.
First solution is little bit better, because in the second case you have to always remember about memory leak. Look here for more info and here
Given following common setup:
CtrlA (page level controller)
|- directiveAA (component e.g. button bar)
|- directiveAAA (sub-component e.g. button)
I would like to call CtrlA.methodA() from directiveAAA by passing the methodA down the chain using directive attributes - CtrlA -> directiveAA -> directiveAAA. So for example my directiveAAA "Save" button can call controller method "Save". Components directiveAA and directiveAAA are dumb components and only know about their environment given their attribute settings.
Before Typescript I would make use of inherited scope down the chain to call controller method $scope.save() from directiveAAA.
How would this work with Typescript? Would we still have to make use of injected scope into our controller, directive controller classes or can this be done without using scope, based on class inheritance?
So here's my question in code - its probably not perfect but gives the gist - the nub of the problem is marked with comment "this is where i need help":
module app.page {
class CtrlPage {
private ctrlPageBtnActions: string[] = ['goPrev', 'goNext'];
goPrev(){
console.log('go previous');
}
goNext(){
console.log('go next');
}
}
function pageDirective(){
return {
restrict: 'E',
replace: true,
template: '<button-bar actions="CtrlPage.ctrlPageActions"></button-bar>',
controller: CtrlPage,
controllerAs: 'ctrlPage',
bindToController: true
}
}
angular.module('app')
.directive('page', pageDirective);
}
module app.page.directives {
class CtrlBtnBar {
private actions: string[];
}
function buttonBar() {
return {
restrict: 'E',
replace: true,
template: '<div class="buttonBar"><btn action="CtrlBtnBar.actions[0]"></btn><btn action="CtrlBtnBar.actions[1]"></btn></div>'
controller: CtrlBtnBar,
controllerAs: 'CtrlBtnBar',
bindToController: {
actions: '='
}
}
}
angular.module('app')
.directive('buttonBar', buttonBar);
}
module app.page.directives {
class CtrlBtn {
private action: string;
handleClick(){
if (action === 'goNext'){
CtrlPage.goNext(); /// Here is where I need help
}
}
}
function btnDirective(){
return {
restrict: 'E',
replace: true,
template: '<input type="button" value="someCaption" ng-click="CtrlBtn.handleClick()"/>',
controller: CtrlBtn,
controllerAs: 'ctrlBtn',
bindToController: {
action: '#'
}
}
}
angular.module('app')
.directÃve('btn', btnDirective);
}
If you run the code in http://www.typescriptlang.org/Playground you will see that typescript understandably objects to CtrlPage reference from within btnDirective controller CtrlBtn, because within this context CtrlPage does not exist. Must we use angular $scope to access the "goNext" method, given that the btnDirective is dumb and is not aware of its parents controllers and only receives inputs from its attributes? Taking radim's tip into consideration I guess the answer is yes.
Typescript with AngularJS (ver 1) does NOT bring any change to angular's architecture/design. So scopes are scopes, and they will be inherited as they did (via .$new())
Also, any Typescript class inheritance has no impact on $scope inheritance. And that won't change even with Angular 2. If some component (a bit like controller class in Typescript today) will be using code from its parent (derive from it) - in runtime it will has no effect on its context.
So, use the angular as you did, just profit from strongly typed language support.
Check these Q & A with working examples with directives:
How can I define my controller using TypeScript?
How To bind data using TypeScript Controller & Angular Js
Angular Ui-router can't access $stateParams inside my controller
I was reading through this article:
http://teropa.info/blog/2014/10/24/how-ive-improved-my-angular-apps-by-banning-ng-controller.html
Which proposes that controllers be integrated into directives like this in order to remove the need to ever use ng-controller:
angular.module('contestantEditor', [])
.directive('cContestantEditorForm', function() {
return {
scope: {
contestants: '='
},
templateUrl: 'contestant_editor.html',
replace: true,
controller: 'ContestantEditorFormCtrl',
controllerAs: 'ctrl',
bindToController: true
};
})
.controller('ContestantEditorFormCtrl', function($scope) {
this.contestant = {};
this.save = function() {
this.contestants.push(this.contestant);
this.contestant = {};
};
});
In the comments, however, someone else proposed this solution:
angular.module('contestantEditor', [])
.directive('cContestantEditorForm', function () {
return {
scope: {
contestants: '='
},
templateUrl: 'contestant_editor.html',
replace: true,
link: function (scope) {
scope.contestant = {};
scope.save = function() {
scope.contestants.push(scope.contestant);
scope.contestant = {};
};
}
};
});
It achieves the exact same thing as the version with the controller without ever needing to make a controller. So I'm curious as to what the pros and cons of either method are versus writing angular traditionally with ng-controller, and whether controllers are even necessary by the end of it.
Here is the plunker for the first, and here is the second.
In directives, you should use link function whenever you can. Use controllers only when communication to other directives is needed.
You can find more about this discussion here. Specifically this best practice statement:
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.
Directives and Controllers are two totally different things.
Direvtives should be used for DOM manipulations.
if you want to know use controller inside DDO or use link function for your logic, then answer will be you should use controller in DDO in that cases when you want provide API and require your directive in other directives and use that API in extended directive
Controllers can't be replaced by directive
Controller should contain your businesses logic and it cant be replaced by directive and shouldn't have DOM manipulations.
Angular newbie here. I am trying to figure out what's going wrong while passing objects to directives.
here's my directive:
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
and this is the template where I call the directive:
<div walkmap="store.walks"></div>
store.walks is an array of objects.
When I run this, scope.walks logs as undefined while scope logs fine as an Scope and even has a walks child with all the data that I am looking for.
I am not sure what I am doing wrong here because this exact method has worked previously for me.
EDIT:
I've created a plunker with all the required code: http://plnkr.co/edit/uJCxrG
As you can see the {{walks}} is available in the scope but I need to access it in the link function where it is still logging as undefined.
Since you are using $resource to obtain your data, the directive's link function is running before the data is available (because the results from $resource are asynchronous), so the first time in the link function scope.walks will be empty/undefined. Since your directive template contains {{}}s, Angular sets up a $watch on walks, so when the $resource populates the data, the $watch triggers and the display updates. This also explains why you see the walks data in the console -- by the time you click the link to expand the scope, the data is populated.
To solve your issue, in your link function $watch to know when the data is available:
scope.$watch('walks', function(walks) {
console.log(scope.walks, walks);
})
In your production code, just guard against it being undefined:
scope.$watch('walks', function(walks) {
if(walks) { ... }
})
Update: If you are using a version of Angular where $resource supports promises, see also #sawe's answer.
you may also use
scope.walks.$promise.then(function(walks) {
if(walks) {
console.log(walks);
}
});
Another solution would be to add ControllerAs to the directive by which you can access the directive's variables.
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
controllerAs: 'dir',
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
And then, in your view, pass the variable using the controllerAs variable.
<div walkmap="store.walks" ng-init="dir.store.walks"></div>
Try:
<div walk-map="{{store.walks}}"></div>
angular.module('app').directive('walkMap', function($parse) {
return {
link: function(scope, el, attrs) {
console.log($parse(attrs.walkMap)(scope));
}
}
});
your declared $scope.store is not visible from the controller..you declare it inside a function..so it's only visible in the scope of that function, you need declare this outside:
app.controller('MainCtrl', function($scope, $resource, ClientData) {
$scope.store=[]; // <- declared in the "javascript" controller scope
ClientData.get({}, function(clientData) {
self.original = clientData;
$scope.clientData = new ClientData(self.original);
var storeToGet = "150-001 KT";
angular.forEach(clientData.stores, function(store){
if(store.name == storeToGet ) {
$scope.store = store; //declared here it's only visible inside the forEach
}
});
});
});