dynamically added directive in AngularJS - angularjs

I'm writing a custom directive. I want the directive to add an ng-click attribute to the element
attrs.$set('ng-click','clicked()');
I have tried adding the ng-click directive in compile function, and pre and post link functions. The attribute is added but doesn't work. I appreciate any insights. Thanks!
.directive('myDir', function () {
return{
compile: function (tElement, tAttrs, transclude) {
//tAttrs.$set('ng-click','clicked()');
return {
pre: function (scope, element, attrs) {
//attrs.$set('ng-click','clicked()');
},
post: function (scope, element, attrs) {
//attrs.$set('ng-click','clicked()');
scope.clicked = function(){
console.log('clicked!!!');
};
}
};
}
};
});

You've added the attribute via jQuery so Angular doesn't know about it. The quick answer is to wrap the call in a scope.$apply:
scope.$apply(function() {
tAttrs.$set('ng-click','clicked()');
});
So Angular knows you changed it.
But for other approaches that might work more cleanly with Angular look at What is the best way to conditionally apply attributes in Angular?

Related

Directive within another directive - scope var undefined

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

AngularJS: Add attribute, compile directive

Is it possible to somehow add directive as simple attribute in compile function and let angular handle compiling of added directive?
Provided example bellow obviously does not work, but my proper question would be, what is the cleanest way to achieve that?
var app = angular.module('app', []);
app.directive('testOne', function ($compile) {
return {
restrict: 'A',
priority: 10000,
compile: function (element, attrs) {
element.attr('test-two', '');
}
};
});
app.directive('testTwo', function () {
return {
restrict: 'A',
priority: 10,
compile: function (element, attrs) {
console.log(2);
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div test-one></div>
</div>
In the link phase, you can call $compile(element)(scope) to recompile the element to allow AngularJS to pick up the newly added directive. However, to prevent an infinite loop (since your initial directive will also be re-compiled), you should remove the initial directive's attribute first:
link: function (scope, element) {
element.removeAttr('test-one');
element.attr('test-two', '');
$compile(element)(scope);
}
Edit: You may also want to set terminal: true on your initial directive to prevent other directives from kicking in before your attribute massaging is done. You may also have to play around with the priority of the directive for similar reasons.

dynamically add ui-sref to an element

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).

AngularJS - How do I get access to my template in a directive?

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.

$log (or other services inside AngularJS directives)

I have the following directive I use to initialize the timeago plugin.
Directives.directive('timeago', function() {
return function(scope, element, attrs) {
$(element).attr('title', scope.post.utc_posted);
$(element).timeago();
}
});
How could I use/pass $log inside the function I'm returning?
You can just inject it the normal way. BTW element is already a jQuery variable and does not need $(element) - providing you're loading jQuery before Angular.
Directives.directive('timeago', function($log) {
return {
link: function(scope, element, attrs) {
element.attr('title', scope.post.utc_posted);
element.timeago();
}
}
});

Resources