I have the following directive
.directive('famAction', function () {
var directive = {
restrict: 'A',
scope: {action: '='},
link: link
};
function link(scope, element) {
if (scope.action.hasOwnProperty('state')) {
element.attr('ui-sref', scope.action.state);
}
if (scope.action.hasOwnProperty('func')) {
element.bind('click', scope.action.func);
}
}
return directive;
})
The problem is that, when adding the ui-sref attribute, the attribute isn't compiled ant therefore I don't have the generater href tag, so the link doesn't work.
How can I do to dynamically add a ui-sref attribute to an element and then compile it ?
I even tried this, without success:
.directive('famAction', function () {
var directive = {
restrict: 'A',
scope: {action: '='},
compile: compile
};
function compile(tElement, tAttrs) {
return {
pre: function preLink(scope, element, attrs) {
if (scope.action.hasOwnProperty('state')) {
element.attr('ui-sref', scope.action.state);
}
},
post: function postLink(scope, element, attrs) {
if (scope.action.hasOwnProperty('func')) {
element.bind('click', scope.action.func);
}
}
};
}
return directive;
})
PS: My action object can be one of the following:
{state: 'app.flaws.relevant', icon: 'chain-broken'}
{func: vm.ignoreFlaw, icon: 'ambulance'}
You cannot add directives to the element currently being compiled! The reason is that Angular has already parsed it and extracted the directives to be compiled. In contrast, if you wanted to add an attribute directive to an internal element, you should do it in the compile function (not in preLink - the template has already been compiled there and the addition of the attribute would have no effect). For cases like this, where you want access to the scope, it will not work: compile cannot access the scope (it has not been created yet).
What can you do in your case?
Manual compilation:
Remove the element that has the ui-sref from the template of your famAction directive.
In pre- or postLink, use $compile to compile the template of the element that contains the ui-sref, i.e. something along the lines of:
var uiSrefElem = $compile(
'<button ui.sref="' + scope.action.state + '">Go!</button>'
);
Add this uiSrefElem to the element of the directive.
Programmatic navigation: Place a function in the scope/controller that uses $state.go(scope.action.state). Call this function when the directive element is clicked.
I would go for (2).
Related
I'm trying to generate a smart-table directive from within a custom directive I've defined:
<div ng-controller="myContrtoller">
<containing-directive></containing-directive>
</div>
The directive definition:
angular.module('app')
.directive('containingDirective', function() {
return {
restrict: 'E',
replace: true,
template: '<table st-table="collection" st-pipe="scopeFun"></table>',
link: function(scope, elem, attrs) {
scope.scopeFun = function () {
// solve the misteries of life
}
}
}
});
As you can see my directive tries to replace the element by the template generated by the st-table directive, using the st-pipe directive depending on the first, briefly:
ng.module('smart-table')
.controller('stTableController' function () {
// body...
})
.directive('stTable', function () {
return {
restrict: 'A',
controller: 'stTableController',
link: function (scope, element, attr, ctrl) {
// body
}
};
})
.directive('stPipe', function (config, $timeout) {
return {
require: 'stTable',
scope: {
stPipe: '='
},
link: {
pre: function (scope, element, attrs, ctrl) {
var pipePromise = null;
if (ng.isFunction(scope.stPipe)) { // THIS IS ALWAYS UNDEFINED
// DO THINGS
}
},
post: function (scope, element, attrs, ctrl) {
ctrl.pipe();
}
}
};
});
Problem:
The st-pipe directive checks the scope var stPipe if it is defined or not by: if (ng.isFunction(scope.stPipe)). This turns out to be ALWAYS undefined. By inspecting I found two things:
From the stPipe directive, the value supposed to be scope.stPipe that is my scopeFun defined within my containingDirective is undefined on the scope object BUT defined within the scope.$parent object.
If I define my $scope.scopeFun within the myContrtoller I don't have any problem, everything works.
Solution:
I did find a solutions but I don't know what really is going on:
Set replace: false in the containingDirective
Define the scope.scopeFun in the pre-link function of containingDirective
Questions:
Why is the scopeFun available in the stPipe directive scope object if defined in the controller and why it is available in the scope.$parent if defined in the containingDirective?
What is really going on with my solution, and is it possible to find a cleaner solution?
From the docs: "The replacement process migrates all of the attributes / classes from the old element to the new one" so what was happening was this:
<containing-directive whatever-attribute=whatever></containing-directive>
was being replaced with
<table st-table="collection" st-pipe="scopeFun" whatever-attribute=whatever></table>
and somehow st-table did not enjoy the extra attributes (even with no attributes at all..).
By wrapping the containingDirective directive template within another div fixed the problem (I can now use replace:true):
<div><table st-table="collection" st-pipe="scopeFun"></table></div>
If someone has a more structured answer would be really appreciated
I am not able to get my view updated while updating scope variable in post link function.
Following is the use of my directive.
<my-directive color='purple'>
</my-directive>
Following is the definition of my directive.
app.directive('myDirective', function () {
console.log('My Directive Called');
return {
restrict: 'E',
scope: {
localVar: '#color'
},
//template: '<span></span>', // When I enable this template it works fine.
/* This link way it is working fine.
link: function (scope, iElement, iAttrs) {
console.log(iElement);
iAttrs.color = 'red';
}*/
//This is not working Reason don't know.
compile: function (tElement, tAttrs) {
var spanElem = angular.element('<span> {{ localVar }} </span>');
spanElem.attr('color', tAttrs.color);
tElement.replaceWith(spanElem);
return function (scope, iElement, iAttrs) {
iAttrs.color = 'red';
};
}
};
});
I want to know the reason why this code is not working. It will work if I specify the template property in directive definition object. But I want to know what is going wrong in above code.
Please help me.
It's much easy if you do somehting like this:
JSFiddle
angular.module('myApp', [])
.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
localVar: '#color'
},
template: '<span> {{ localVar }} </span>'
};
});
Without calling link function there is no two way data binding between template created by compile function and scope.
That's why when you turn on link function you get the desired result.
From angular docs.Please read this point.
HTML compilation happens in three phases:
$compile traverses the DOM and matches directives.
If the compiler finds that an element matches a directive, then the directive is added to the list of directives that match the DOM element. A single element may match multiple directives.
Once all directives matching a DOM element have been identified, the compiler sorts the directives by their priority.
Each directive's compile functions are executed. Each compile function has a chance to modify the DOM. Each compile function returns a link function. These functions are composed into a "combined" link function, which invokes each directive's returned link function.
$compile links the template with the scope by calling the combined linking function from the previous step. This in turn will call the linking function of the individual directives, registering listeners on the elements and setting up $watchs with the scope as each directive is configured to do.
The result of this is a live binding between the scope and the DOM. So at this point, a change in a model on the compiled scope will be reflected in the DOM.
EDIT CODE :
If you want to do it withour compile and link function,try to use isolated scope
EDIT CODE 2:
.directive('myDirective', function () {
console.log('My Directive Called');
return {
restrict: 'E',
scope: {
localVar: '#color'
},
template : '<span> {{ localVar }} </span>'
};
});
HTML :
<my-directive color='purple'>
</my-directive>
EDIT CODE 3:
directive('myDirective', function () {
console.log('My Directive Called');
return {
restrict: 'EA',
template: '<span>{{ localVar }}</span>', // When I enable this template it works fine.
compile: function (tElement, tAttrs) {
return {
post: function postLink(scope, iElement, iAttrs, controller) {
scope.localVar = 'red';
}
}
}
};
})
I'm using ng-repeat and I need to pass a scope variable into a directive's compile function. I know how to do it with the link function, but not the compile function.
My html looks like:
<div ng-repeat="item in chapter.main">
<block type="item.type"></block>
</div>
Let's say item.type="blah" no matter the item.
Then this link function works fine
app.directive('block', function() {
return {
restrict: 'E',
link: function(scope, element, attributes){
scope.$watch(attributes.type, function(value){
console.log(value); //will output "blah" which is correct
});
}
}
});
But I can't do the same with compile?
app.directive('block', function() {
return {
restrict: 'E',
compile: function(element, attrs, scope) {
scope.$watch(attrs.type, function(value){
console.log(value);
});
}
}
});
The error I get is "cannot read property $watch of undefined"..
This is how I'd like my directive to look like:
app.directive('block', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
element.append('<div ng-include="\'{{type}}-template.html\'"></div>');
//or element.append('<div ng-include="\'{' + attrs.type + '}-template.html\'"></div>');
//except the above won't interpret attr.type as a variable, just as the literal string 'item.type'
}
}
});
The compile function doesn't have scope as one it's parameter.
function compile(tElement, tAttrs, transclude) { ... }
NOTE: transclude is deprecated in the latest version of Angular.
Is there any reason you don't want to use link?
From the DOC
The compile function deals with transforming the template DOM. Since most directives do not do template transformation, it is not used often. The compile function takes the following arguments:
tElement - template element - The element where the directive has been declared. It is safe to do template transformation on the element and child elements only.
tAttrs - template attributes - Normalized list of attributes declared on this element shared between all directive compile functions.
transclude - [DEPRECATED!] A transclude linking function: function(scope, cloneLinkingFn)
UPDATE
To access the scope from inside compile function, you need to have either a preLink or postLink function. In your case, you need only the postLink function. So this ...
compile: function compile(tElement, tAttrs, transclude) {
return function postLink(scope, element, attrs) { ... }
},
PROPOSED SOLUTION Might not be exact but should help you on your way.
html
<div ng-app="myApp" ng-controller="app">
<block type="item.type"></block>
</div>
JS (Controller + Directive)
var myApp = angular.module('myApp', []);
myApp.controller('app', function ($scope, $http) {
$scope.item = {
type: 'someTmpl'
};
}).directive('block', ['$compile', function ($compile) {
return {
restrict: 'AE',
transclude: true,
scope: {
type: '='
},
compile: function (element, attrs) {
return function (scope, element, attrs) {
var tmpl;
tmpl = scope.type + '-template.html';
console.log(tmpl);
element.append('<div ng-include=' + tmpl + '></div>');
$compile(element.contents())(scope);
};
}
};
}]);
Given the following directive:
myApp.directive("test", function () {
return {
restrict: 'E',
scope: {
model: "="
},
template: "<div id='dialog_{{model.dialogId}}'></div>",
replace: true,
link: function (scope, element, attrs) {
alert($("dialog_" + scope.model.dialogId).length); <-- This is 0
}
}
});
I need to run a jQuery UI method on the div in the template, but I can't seem to get a reference to it from the DOM in my link function. Is there a way to run a function after the template has been added to the DOM?
You have element property.
You can do something like var div= element.find("div");
And if you want attach jquery plugin, just do $(div).jqueryPlugin();, you have to include jQuery, but if you don't angular provides jQuery Lite.
What I ended up doing was this:
link: function (scope, element, attrs) {
$timeout(function () {
alert($("#dialog_" + scope.model.dialogId).length);
}, 0);
}
I forgot to put the # in front of the selector, but it still only works if I use the timeout. Not sure why.
I can define a directive that affects all <a> elements in a document like so:
myApp.directive('a', function() {
return {
restrict: 'E',
link: function(scope, element) {
// Some custom logic to apply to all <a> elements
}
};
});
Can I do the same, but for elements matching a given CSS selector? Like this?
myApp.directive('a[href^="mailto:"]', function() {
return {
restrict: 'E',
link: function(scope, element) {
// Some custom logic to apply to all <a> elements
// w/ a href attribute starting in "mailto:"
}
};
});
No.
When you register directive under a specific name angular puts directive into directive cache under the new name or pushes it to the list of already existing directives under the specified name.
After that, angular searches dom through to find correspondence between your directive and (tagName|attrName|className|commentName) and when found angular calls compile function of each directive in the list and passes the found (element, attrs) as arguments into compile function.
So in your case a[href^="mailto:"] will be searched as is '<a[href^="mailto:"]></a[href^="mailto:"]>' which is obviously inexistent, and the same for attribute, class and comment.
In your case the most sane solution is:
myApp.directive('a', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
if (attrs.href.indexOf('mailto:') !== 0) { return; }
// Some custom logic to apply to all a[href^="mailto:"] elements
}
};
});