I'm in need of doing transclusion together with manual compilation in link function and I'm wondering if there is a better way than what I'm pasting below as "Dynamic". It seems to be working but I'm not sure what are the consequences of removing ng-transclude attribute and I'm removing it due to Angular complaining to Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <div ng-transclude="">.
Bottom-line is that I need to provide different templates for different instances of this directive with fallback to generic ones.
I can't use compile part. Here I have just simple example but I have also other cases with big link function that I would rather don't want to convert as this should be just "golden bullet" type of adjustment.
This little guy can be found here https://github.com/institut-de-genomique/Ultimate-DataTable/blob/master/src/directives/udt-form.js in original form.
Original
angular.module('ultimateDataTableServices')
.directive('udtForm', function () {
return {
restrict: 'A',
replace:true,
transclude:true,
templateUrl:'udt-form.html',
link: function(scope, element, attr) {
}
};
});
Dynamic
angular.module('ultimateDataTableServices')
.directive('udtForm', ['$compile', '$templateCache', function ($compile, $templateCache) {
return {
restrict: 'A',
transclude:true,
link: function(scope, element, attr, ctrls, transclude) {
var tplName = 'udt-form.html',
tplPath = scope.udtTable.configMaster.templates[tplName] || scope.udtTable.configMaster.templatesMaster[tplName] || tplName,
elem = angular.element($templateCache.get(tplPath));
elem.find('[ng-transclude]').removeAttr('ng-transclude').append(transclude());
element.replaceWith($compile($templateCache.get(tplPath))(scope));
}
};
}]);
TIA!
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 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).
I am trying this:
app.directive('adminTemplate', ['stateService', function (stateService) {
return {
restrict: 'E',
scope: {
src: "="
},
templateUrl: src,
link: function (scope, element, attrs) {
scope.stateService = stateService;
}
};
}]);
But it's giving me an error with Typescript saying "Could not find symbol src".
How can I get the template and how could I call it with this directive?
Scope values are not accessible from within a directive's templateUrl. The attributes are not compiled yet, so from within this context it is not possible to access the scope.
Please check my answer on a similar question or the plunker
What I did there is using a template containing a div with ng-include.
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 : )