Calling $compile for a directive, outside of the directive - angularjs

From all the examples I've seen this should work: (See EDIT, I answered my own question.)
.directive('ssmViewport', ['$compile', 'ssmLayoutMan', function($compile, layoutManager) {
return {
restrict: 'AE',
replace: false, // TODO: remove if false is the default.
link: function (scope, element, attrs) {
layoutManager.renderView = function (view) {
console.log('this text does appear in the log...');
element.append('This text should appear...'); // but it doesn't appear
// these two commented lines are what I'm really trying to do.
//element.append(view.template);
//$compile(element.contents())(scope);
}
};
}
};
}])
Elsewhere in the code I call:
layoutManager.renderView({template: '<div some-custom-directive></div>'});
But alas... the element does not seem to change. I have tried scope.$apply() after my $compile but it just throws an error about already being in the digest, and still doesn't display changes to the element.
EDIT:
I answered my own question. I will mark a correct answer for anyone who can explain why this is so...
The answer is:
ssmViewport is a directive that was defined in some markup that was injected like so:
var html = $compile(templateThatContainsTheSSMViewport)(scope);
element.html(html);
But if I inject it like below, then it will work:
element.html(templateThatContainsTheSSMViewport);
$compile(element.contents())(scope);
I just started learning angular yesterday, so I don't understand the difference yet... Any enlightenment on the issue would be a boon :)

Related

AngularJS $parser not being called when dynamically adding the directive

So what i'm trying to achieve is to be able to add a directive that contains a parser through another directive.
When directly adding the parser directive on an html element it works completely fine. the parser directive i currently use:
.directive('parseTest', [
function () {
return {
restrict: 'A',
require: ['ngModel'],
link: {
post: function (scope, element, attributes, ctrls) {
var controller = ctrls[0];
controller.$parsers.unshift(function (value) {
var result = value.toLowerCase();
controller.$setViewValue(value);
controller.$render();
return result;
})
}
}
}
}
])
Now when i add this directive through another directive the parser never gets called weirdly enough. The directive that generated the parsetest directive:
.directive('generateTest', ['$compile',
function ($compile) {
return {
restrict: 'A',
compile: function (elem, attrs) {
elem.attr('parse-test', '');
elem.removeAttr('generate-test');
var linkFn = $compile(elem);
return function (scope, element, attr) {
linkFn(scope);
}
}
}
}
])
The following works fine:
<input class="form-control col-sm-6" ng-model="model.parsetest" parse-test/>
The following doesn't work (While the generated result html is the same)
<input class="form-control col-sm-6" ng-model="model.generateTest" generate-test />
So my question is how can i get the parser working when it is in a dynamicly added directive?
Note, i already tried the solution to a similar issue from this question, but that doesn't work for me.
EDIT: Here is a plnkr that demonstrates the issue, both fields have the parse-test directive applied to it that should make the value in the model lowercase, but it only works for the one that is not dynamically added as shown in the console logs.
So I've found the solution, so for anyone that stumbles on the same issue here it is.
The 2 small changes have to made to the directive that generates the directive that contains a parser or formatter.
First of set the priority of the directive to a number higher or equal as 1. Secondly put terminal on true. Those 2 settings seem to resolve the issue.
The problem probably lies in that the default execution of nested directives makes it so that the parser and formatters get inserted slightly to late which is why we need to make sure the directive gets generated first thing before anything else.
This is just an assumption of why it works tho, if anyone else has an explanation it would be great :)
As for the code, the directive that generates another directive should look something like:
directive('generateTest', ['$compile',
function ($compile) {
return {
restrict: 'A',
terminal: true,
priority: 1,
compile: function (elem, attrs) {
attrs.$set('parseTest', '');
attrs.$set('generateTest', undefined);
var linkFn = $compile(elem);
return function (scope, element, attr) {
linkFn(scope);
}
}
}
}
])

Angularjs - Pass optional parameter for Directive from HTML

I am trying to pass an optional parameter isvalid from my html to the directive. I have followed all the steps mentioned in documentation but it still looks like i am doing something wrong.. i am not able to read the value in my directive. can you let me know what am i doing wrong?
HTML
<customvideo isvalid="true"></div>
MY Directive
Update: I had simplified for questioning purpose and hence you were seeing $scope. I have updated the actual directive now
// Set the directive
angular
.module('custom.directives')
.directive('customvideo', customvideoDirective);
// Set the directive $injections
customvideoDirective.$inject = ['$Scope'];
function customvideoDirective($Scope)
{
return {
compile: compile,
restrict: 'A',
$Scope: {
isvalid: '=?'
}
};
function compile() {
console.log($Scope.isvalid); //this is undefined
}
}
})();
Change you $Scope property inside Directive Definition Object (DDO) should be changed scope. Also use link function instead of using compile, as compile function has access to only raw DOM, there will be no scope available in it. Even you can return and PreLink/PostLink from compile function, but in this case I believe using link would be appropriate.
angular.module('directives')
.directive('customvideo', function () {
return {
link: link,
restrict: 'A',
scope: {
isvalid: '=?'
}
}
);
function link(scope) {
console.log(scope.isvalid);
}
Found a way to work it out.. not sure if it is the best.. but hey.. it works :) Posting it here for those who come here for answers...
I was writing the syntax for compile wrongly... I changed it to
function compile(tElement, tAttrs, transclude)
{
// the tAttrs param has all the attributes associated with the element
console.log(tAttrs.isvalid);
}

Syntax In Directive

I followed this YouTube on Directives
https://www.youtube.com/watch?v=0r5QvzjjKDc
and it was very good, imo. Got through following along successfully right up until the very end (link). The debugger is telling me
TypeError: element.click is not a function
I've looked at this seven ways to Sunday and I'm just not seeing where it's not matching what he's got. Is the syntax error jumping out at you?
Thanks.
angular
.module('app.directives.contactCard', [])
.directive('contactCard', function() {
return {
/* choices are E-Element, A-Attribute, or C */
restrict: 'E',
scope: {
friend: '=',
title: '=',
},
replace: true,
transclude: true,
templateUrl: "contactCard.html",
link: function(scope, element, attrs) {
element.click(function() {
alert('click');
});
},
controller: function($scope) {
console.log($scope.friend);
}
}
})
This looks pretty good, just a slight problem with the element syntax. The error is indicating there is no such function (i.e. click()) on element.
Try using the following, which uses bind:
element.bind('click', function() {
alert('click');
})
I replicated your directive here: http://plnkr.co/edit/SX0zwYipydVvo6EMVfzE
Overall you're on the right track, but the hypothetical click function like that is more reminiscent of the jQuery way of doing things than the Angular way. Typically, if it feels like you're trying to manipulate the features of the DOM through JS, you're thinking in jQuery instead of Angular. I realize that the tutorial utilized it, but it somewhat defeats the purpose of Angular, and I think hides a lot of its real power.
Common conventions for click-functions would be to assign a function to $scope in the directive's controller (or to the directive itself), then refer to it with ng-click in the directive's template.
If you can update your question to include the template, I can give a more specific example. I expect it'll be extremely succinct.

Why AngularJS directive compiling not support nested directives

In my understanding, $compile should be able to support nested directive compilation/link, but we came across an issue that the compilation/link is incomplete - only the outermost directive got rendered into DOM, and the issue only reproduced when both below conditions are true:
The inner directive template is loaded via templateUrl (e.g. async manner)
The compilation is triggered outside Angular context.
I wrote a jsfiddler to demo it, part code listed below, for complete case http://jsfiddle.net/pattern/7KjWP/
myApp.directive('plTest', function($compile){
return {
restrict :'A',
scope: {},
replace: true,
template: '<div>plTest rendered </div>',
link: function (scope, element){
$('#button1').on('click', function(){
var ele;
ele = $compile('<div pl-shared />')(scope);
console.log('plTest compile got : '+ ele[0].outerHTML);
// scope.$apply();
element.append(ele);
});
}
};
});
myApp.directive('plShared', function($compile, $timeout){
return {
restrict: 'A',
scope: {},
replace: true,
link: function (scope, element){
// comment out below line to make render success
//$timeout(function(){});
var el = $compile('<div pl-item></div>')(scope);
console.log('plShared compile got:' + el[0].outerHTML);
element.append(el);
}
};
});
myApp.directive('plItem', function($timeout){
return {
restrict: 'A',
scope:{},
template:'<div>plItem rendered <div pl-avatar/></div>',
link: function(scope){
}
};
});
myApp.directive('plAvatar', function(){
return {
restrict: 'A',
scope: {}
, templateUrl: 'avatar.html'
// ,template: 'content of avatar.html <div pl-image></div>'
};
});
Interestingly, i can workaround the issue by either calling scope.$apply() somewhere after compile() call (line 27)
or adding $timeout(function(){}) call into the link func of one of inner directive (line 41). Is this a defect or by design?
$(foo).on(bar, handler) is a jQuery event, which means AngularJS does not know the specifics of it, and will not (can not) run an apply-digest cycle after it to process all the bindings.
scope.$apply was made for this, and as you rightly say, fixes it. The rule of thumb is: if you implement UI functionality in an AngularJS application using other libraries (specifically: outside of apply-digest cycles), you must call scope.$apply yourself.
HTH!
After element.append(el), try to compile again as you have just modified the DOM.
You could try something such as $compile(element)(scope); or $compile(element.contents())(scope);.
As said before me, I would also change the event handler as follows :
$('#button1').on('click', function(){
scope.$apply( function(){
//blablalba
});
});
Also, justa piece of advice in case you would want to minify your code, I would declare the compile dependency using the following syntax :
.directive('directiveName',['$service1',''$service2,...,'$compile', function($service1, $service2,...,$compile){
//blablabla
}]}

Manipulating DOM in directive, now what?

I have made a directive (inline-edit) and manipulated the DOM in the compile function, but how can I make the other directives that I have added to work? I guess I need to compile it, but how? See my jsfiddle here: http://jsfiddle.net/tidelipop/m4gbZ/
ng-click does not work as it is, but the strange thing is, why do ng-bind work? You can see that it does work if you unhide the textarea in dev tools.
angular.module('MyApp', [], function($compileProvider){
$compileProvider.directive("inlineEdit", function($compile, $q){
return {
restrict: "A",
scope: true,
controller: function($scope){
$scope.editMode = true;
$scope.save = function(){
console.log("Saving...");
};
},
compile: function(tElement, tAttrs){
tElement
.attr("ng-hide", "editMode")
.attr("ng-click", "editMode=!editMode")
.after("<textarea ng-show=\"editMode\" ng-model=\""+tAttrs.ngBind+"\"></textarea><button ng-click=\"save()\">Save</button>");
//var scopeResolver = $q.defer();
//$compile(tElement.parent().contents())(scopeResolver.promise);
return function(scope, element, attrs, controller){
//scopeResolver.resolve(scope);
//$compile(element.parent().contents())(scope);
console.log(element.parent().contents());
};
}
};
});
})
.controller("UserAdminCtrl", function($scope){
$scope.data_copy = {
user: {
user_id: 'sevaxahe',
comment: 'test'
}
};
});
It looks like your directive is conflicting with the ng-bind, I don't really know why, but the question I asked myself looking at your code was : Wouldn't it be easier using a template and a custon attribute for the model (instead of ng-bind) ?
And the answer is yes !
Actually that's just my opinion, but here is what I did by modifying your code http://jsfiddle.net/DotDotDot/m4gbZ/73/
I let you have a look, I had to change some parts (the ng-click doesn't work well on the textarea so I put this behavior on the Save button) but I think this is almost what you wanted. On code side, I modified the HTML to avoid calling ng-bind, using a custom scope variable which will be caught in the directive :
<span inline-edit ff="data_copy.user.comment">First</span>
On the directive side, I got rid of all the compile/controller stuff, and I added a template
return {
restrict: "A",
template:'<div><span ng-hide="editMode" ng-click="editMode=!editMode">{{aModel}}</span><textarea ng-show="editMode" ng-model="aModel"></textarea> <button ng-click="save()">{{getLabel()}}</button></div>',
replace:true,
scope: {aModel:'=ff'},
link: function(scope, element, attrs){
console.log(element)
scope.editMode = true;
scope.save = function(){
console.log("Saving...");
scope.editMode=!scope.editMode;
};
scope.getLabel=function(){
if(scope.editMode)
return "Save";
else
return "Change";
}
console.log(element.parent().contents());
}
}
Why ? The template, because angular will compile it itself without any intervention.
I added replace:true to replace the line, but it's optionnal
The scope part is more important. scope: {'=ff'} tells angular that I want to use an isolated scope, and I want the scope.aModel value to be bound with the ff variable passed in the HTML.
The '=' means that the modifications will be evaluated from the parent scope, and every modification will be reflected in the parent and in the directive
I replaced your controller and your compile function (no element to compile, and adding function can be done here instead of a dedicated controller) by a linking function containing the functions needed. As I said before, I added the editMode change behavior to the Save button, so I added a bit more code, but it's not the main point, I think you may have to change things here to reflect your expected behavior
I hope this will help you, as I don't really answer your question, but I think you could also explore this way
++

Resources