AngularJS Link function not being called - angularjs

For some reason my link function inside my directive is not being called. I can see that my directive is called with a console.log but not the link function. Also don't mind the controller parameter I will be using that with my parent directive. I have also tried restrict: 'E' with no luck as well. I am not using it for this example. Not sure what is causing it to skip link. Any thoughts?
module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTab', function () {
console.log('directive was hit');
function linkFn(scope, ele, attrs, controller) {
console.log('Link is called');
};
return {
require: '^ji-Tabset',
restrict: 'C',
transclude: true,
link: linkFn
}
});
}
HTML
<ji-form name="Main Form">
<ji-tabset name="Tabs">
<ji-tab tab-name="General"></ji-tab>
<ji-tab tab-name="Stats"></ji-tab>
</ji-tabset>
</ji-form>
Parent directive
module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTabset', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
templateUrl: 'FormTest/views/ji-Tabset.html',
controller: function ($scope) {
var tabPanelItems = $scope.tabPanelItems = [];
$scope.tabSettings = {
dataSource: tabPanelItems
}
}
};
});
}

module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTab', function () {
console.log('directive was hit');
function linkFn(scope, ele, attrs, controller) {
console.log('Link is called');
};
return {
require: '^ji-Tabset', //<-- this must be `^jiTabset` read mistake 1
restrict: 'C', //<-- this must be `E` which stands for element, (jiTab) C is for class, read mistake 2
transclude: true,
link: linkFn
}
});
}
From docs
Mistake 1
Normalization
Angular normalizes an element's tag and attribute name to determine which elements match which directives. We typically refer to directives by their case-sensitive camelCase normalized name (e.g. ngModel). However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case forms, typically using dash-delimited attributes on DOM elements (e.g. ng-model).
The normalization process is as follows:
Strip x- and data- from the front of the element/attributes.
Convert the :, -, or _-delimited name to camelCase.
Mistake 2
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
These restrictions can all be combined as needed:
'AEC' - matches either attribute or element or class name
Mistake 3
You dont have ng-transclude attribute in your jiTabset directive, make sure you have it there 'FormTest/views/ji-Tabset.html'
Worknig demo
Open browser console
angular.module('FormTest', []);
angular.module('FormTest') //Gets the FormTest Module
.directive('jiTabset', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div>ji-tabset<div ng-transclude></div></div>',
controller: function ($scope) {
var tabPanelItems = $scope.tabPanelItems = [];
$scope.tabSettings = {
dataSource: tabPanelItems
}
}
};
});
angular.module('FormTest') //Gets the FormTest Module
.directive('jiTab', function () {
function linkFn(scope, ele, attrs, controller) {
console.log('Link is called');
};
return {
require: '^jiTabset',
restrict: 'E',
transclude: true,
link: linkFn
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="FormTest">
<ji-form name="Main Form">
<ji-tabset name="Tabs">
<ji-tab tab-name="General"></ji-tab>
<ji-tab tab-name="Stats"></ji-tab>
</ji-tabset>
</ji-form>
</div>

By assigning a variable to a function and calling it where ever you want by its variable name,
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTab', function () {
var linkFn = function (scope, ele, attrs, controller) {
console.log('Link is called');
};
return {
restrict: 'C',
transclude: true,
link: linkFn
}
});
by this way i think you can achieve i hope.

Is it possible that the link function must live outside the directive ITSELF?
Try this:
module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTab', function () {
console.log('directive was hit');
return {
require: '^jiTabset',
restrict: 'E',
transclude: true,
link: linkFn
}
});
function linkFn(scope, ele, attrs, controller) {
console.log('Link is called');
};
}

You are using the directive as an element so you must change the restrict property value to 'E'.
There is no controller in jiTabSet directive so there is no need to require this directive in the jiTab directive. More info: https://docs.angularjs.org/guide/directive#creating-directives-that-communicate
When a directive uses this option (require), $compile will throw an
error unless the specified controller is found. The ^ prefix means
that this directive searches for the controller on its parents
(without the ^ prefix, the directive would look for the controller on
just its own element).
Update your directive like this:
.directive('jiTab', function () {
console.log('directive was hit');
function linkFn(scope, ele, attrs) {
console.log('Link is called');
};
return {
restrict: 'E',
transclude: true,
link: linkFn
}
});

Related

Unable to pass interpolated value during transclusion in directives

I am creating an email directive as below,
angular.module('app')
.directive('email', function () {
return {
restrict: 'EA',
transclude: true,
template: '{{email}}',
link: function (scope, element, attrs, ctrl, transclude) {
transclude(scope, function (clone) {
scope.email = clone.text();
});
}
};
});
I am using it in the HTML as below,
<email>{{emailId}}</email>
However, I am unable to pass the interpolated value of emailId inside the directive. The output of the directive ends up as,
{{emailId}}
How do we pass the actual interpolated value inside the directive?
Plnkr - http://plnkr.co/edit/AeBfp3VayKihygelKr9C?p=preview
To pass a variable to directive scope, simply use an attribute:
angular.module('app')
.directive('email', function () {
return {
restrict: 'EA',
̶t̶r̶a̶n̶s̶c̶l̶u̶d̶e̶:̶ ̶t̶r̶u̶e̶,̶
template: '{{email}}',
link: function (scope, element, attrs, ctrl, transclude) {
//transclude(scope, function (clone) {
// scope.email = clone.text();
//});
scope.email = scope.$eval(attrs.addr);
//OR
scope.$watch(attrs.addr, function(value) {
scope.email = value;
});
}
};
});
Usage:
<̶e̶m̶a̶i̶l̶>̶{̶{̶e̶m̶a̶i̶l̶I̶d̶}̶}̶<̶/̶e̶m̶a̶i̶l̶>̶
<email addr="emailId"></email>
There is no need to use transclusion to evaluate an Angular Expression.
For more information, see
AngularJS scope.$eval API Reference
AngularJS scope.$watch API Reference
You Just have to add Only on directive template ng-transclude on your element
here is the code
angular.module('app')
.directive('email', function () {
return {
restrict: 'EA',
transclude: true,
template: '<a href="{{email}}" ng-transclude>{{email}}</a>',
link: function (scope, element, attrs, ctrl, transclude) {
}
};
});
here is the link
Plunker http://next.plnkr.co/edit/SY3ih9NwAqz7ZhZg?open=lib%2Fscript.js&preview

combining transclude and directive inheritance in AngularJS

i have this html
<div ng-app="myApp">
<parent>
<child></child>
</parent>
</div>
and the following Angular code
var myApp = angular.module('myApp',[]);
myApp.directive('parent', function() {
return {
transclude: true,
link: function(scope, element, attrs) {
scope.getContent = function () {
console.log('called getContent');
}
},
restrict: 'E',
template: '<span>parent <span ng-transclude></span></span>'
}
});
myApp.directive('child', function() {
return {
restrict: 'E',
scope:true,
template: '<div>child</div>',
link: function(scope, element, attrs) {
scope.getContent();
}
}
});
JSfiddle of the above is here
my problem in this example is i can see the inheritance working and transclusion working in isolation but when i try to combine the two, the child directive has no knowledge of the parent scope and hence the function getContent. So no console.log and before that the child directive errors that scope.getContent is undefined.
I realise that this might be that the child directive is no longer a child having been transcluded so i was thinking i need to start playing with post and prelink functions in the compile method maybe? or do i need to define a custom transclude function in the parent?
Any pointers for the code or further reading appreciated - i have read and similar questions on here about this kind of thing but am finding it difficult to follow and hoping some one can solve my hello world to kickstart my understanding
thanks in advance
You should read that article: http://www.undefinednull.com/2014/07/07/practical-guide-to-prelink-postlink-and-controller-methods-of-angular-directives/
Basically, your child link is called before the parent one. What you want to do is use your parent pre-link function so scope.getContent is defined beforte the child is linked.
link: {
pre: function (scope, element, attrs) {
scope.getContent = function () {
console.log('called');
}
}
}
JSfiddle: http://jsfiddle.net/floribon/gpwasrkz/3/
Have a look at "Creating Directives that Communicate" in the docs. Maybe this is a solution for what you are trying o achieve. You can require the parent controller and call it's functions:
var myApp = angular.module('myApp', []);
myApp.directive('parent', function () {
return {
transclude: true,
controller: function ($scope) {
this.getContent = function () {
console.log('called')
}
},
link: function (scope, element, attrs) {},
restrict: 'E',
template: '<span>parent <span ng-transclude></span></span>'
}
});
myApp.directive('child', function () {
return {
restrict: 'E',
scope: true,
require: '^parent',
template: '<div>child</div>',
link: function (scope, element, attrs, ctrl) {
ctrl.getContent();
}
}
});
See this fiddle.

How to append a custom angular directive into another custom angular directive?

I have two custom angular directives and one appends the second repeatedly. The problem is that although the tag is appended, the template of the directive is not. When I manually put it in, it works.
See this jsfiddle: http://jsfiddle.net/HB7LU/5555/
Here is the code where the appending takes place:
myApp.directive('formList', function () {
return {
template: '<my-form></my-form>',
require:'^repeatableForm',
restrict: 'E',
link: function (scope, element, attrs, repeatableFormCtrl) {
scope.add = function () {
console.log("test");
element.append('appended <my-form></my-form>'); // apended<my-form></my-form> will appear but not the contents of <my-form>
};
}
};
});
You have to use $compile service to manually compile your my-form directive like this:
myApp.directive('formList', function ($compile) {
return {
template: '<my-form></my-form>',
require:'^repeatableForm',
restrict: 'E',
link: function (scope, element, attrs, repeatableFormCtrl) {
scope.add = function () {
console.log("test");
var newForm = $compile('<span>appended </span><my-form></my-form>')(scope);
element.append(newForm);
};
}
};
});
Example JSFiddle: http://jsfiddle.net/9L3whcqc/

How can I get my directive to access the controllers scope

I have a setup like this:
<controller>
<directive>
in my controller that has a function that returns an html string. How can I get my directive to render this by accessing the controllers scope?
Or maybe I should just put the controller in the directive?
app.controller('controller', ['$scope', 'DataService', function ($scope, DataService) {
$scope.parseJson = function () {
//returns the html
};
}]);
directive
app.directive('Output', function () {
return {
restrict: 'A',
replace: true,
template: '<need html from controller>',
link: function(scope, element, attr) {
//render
//scope.parseJson();
}
};
});
You should use the isolated scope: '&' option
app.directive('output', ['$sce', function ($sce) {
return {
restrict: 'A',
replace: true,
template: "<div ng-bind-html='parsed'></div>",
scope:{
output: "&"
},
link: function(scope){
scope.parsed = $sce.trustAsHtml(scope.output());
}
};
}]);
Template:
<div output="parseJson()"></div>
The directive and the controller should be sharing the scope already. Don't bother using a template for the directive, just get the HTML string in you linking function (you already have the method call in there) and modify the element directly using element.html(). Take a look at the element docs for more info.
app.directive('Output', function ($compile) {
return {
restrict: 'A',
link: function(scope, element, attr) {
var templateString = scope.parseJson();
var compiledTemplate = $compile(templateString)(scope);
compiledTemplate.appendTo("TheElementYouWishtoAppendYourDirectiveTo");
}
};
});

AngularJS How to access elements inside directive before they get replaced

How do I get the input element from within the directive before the template overwrites the contents?
html
<div xxx>
<input a="1" />
</div>
js
app.directive('xxx', function(){
return {
restrict: 'A',
template: '<p></p>',
replace: true, //if false, just leaves the parent div, still no input
compile: function(element, attrs) {
console.log(element);
return function (scope, iElement, iAttrs) {
}
}
};
});
i am on angular 1.0.x, I cannot pass in optional scope parameters with the '=?' syntax and i want to be able to override a portion of the default template of the directive in a very flexible way. instead of adding a scope variable or attribute everytime that I just plan on passing through the directive, I want to be able to supply the whole element to be used.
edit
the input must retain the scope of the directive, and not the parent.
edit
I am trying to include a partial template inside a directive that will overwrite a piece of the actual template. The piece I am including therefore needs to have access to the directive's scope and not the parent's.
Update
It seems if I do not provide a template or a template URL and instead replace the contents manually using the $templateCache I can have access to the inner elements. I want to let angular handle the template and the replacement though and just want to be able to access the contents in the directive naturally before they get replaced.
Solution
Plunkr
html
<body ng-controller="MainCtrl">
<div editable="obj.email">
<input validate-email="error message" ng-model="obj.email" name="contactEmail" type="text" />
</div>
</body>
js
app.controller('MainCtrl', function($scope) {
$scope.obj = {
email: 'xxx'
};
});
app.directive('editable', function($log){
return {
restrict: 'A',
transclude: true,
template: '<div ng-show="localScopeVar">{{value}}<div ng-transclude></div></div>',
scope: {
value: '=editable'
},
link: function(scope) {
scope.localScopeVar = true;
}
};
});
app.directive('validateEmail', function($log){
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function(scope, el, attrs, ctrl) {
console.log(attrs['validateEmail']);
}
};
});
I believe you're looking for the transclude function (link is to 1.0.8 docs). You can see what's going on with:
app.directive('xxx', function($log){
return {
restrict: 'A',
transclude: true,
compile: function(element, attrs, transclude) {
$log.info("every instance element:", element);
return function (scope, iElement, iAttrs) {
$log.info("this instance element:", element);
transclude(scope, function(clone){
$log.info("clone:", clone);
});
}
}
};
});

Resources