angularjs : logging scope property in directive link function displays undefined - angularjs

I have this basic plnkr which just implements a basic "Hello, X" directive.
In the link function I am logging scope.name but I get undefined? Why is it so? Shouldn't it log the value of name property in console?

This is a known "problem" where interpolation of # attributes happens after linking function is invoked. There is a pull request open to change this issue but it is not clear if this one is going to be merged.
In the meantime a way of getting an interpolated value is by observing an attribute like so:
attrs.$observe('hello', function(changedValue){
console.log(scope.name);
});
And the plunk: http://plnkr.co/edit/Lnw6LuadTLhhcOTsPC8w?p=preview
So, at the end of the day this is a bit confusing behavior of AngularJS that might be changed in the future.

Pawel is right (https://stackoverflow.com/a/14552200/287070) but I wanted to add that the problem is that any attribute that contains {{}} interpolation will be set to null in the attrs parameter during the link function as the first $digest since the compilation has not yet run to evaluate these.
The fact that # bindings are null in linking functions is just a symptom of this.
Currently there is no real fix, since we can't start running $digests in the middle of the compilation process. So $observe (or $watch) is the only real way to get hold of these values.

For those in 2015 who are reading this post, please note that the way Angular handles "#" attributes has changed.
Angular 1.2 onwards, interpolation occurs prior to the invocation of the linking function.
An excellent post on this topic is present here.

Related

Angular HTML Expressions

I am learning angular js and have now a question where I couldn't find the right answer yet.
in the template HTML, you can use expressions to show the scope variables or call scope functions. But I see all the time different versions of it.
{{name}} shows the variable and binds it
{{::name}} the same thing but without binding
userdirective="{{::key}}" But what is the difference here?
ng-if="::field.sortable" With ng-if they are not using {{ but with there userdirective they do?
userdirective="{condition:isActive(route.name),mdColors:{color:'primary'}}" And then there is the last one with just one {. Thats when you create an object.right?
Maybe someone can help me to understand all of it.
Thank you very much for your time. Pat
{{name}} as you say is two-way data-binding
{{::name}} one way databinding
userdirective="{{::key}}" is the interesting case. This statement uses one-way binding into the userdirective ... which means after the $digest it just says userdirective="someValue"
So the userdirective gets that value as a plain value. Now I would have to test it but in the scopepart of the directiive it should say # so it gets read as a string and not as a expression.
The last one is simply as any JSON you build
{ name: value?true:false }
setting value according to conditions that angular evaluates, with a bit of magic involved :D
hope that helps
{{ anything here}} - That is angular expression interpolation. Angular interpolation - here you can find more about that. Basically idea that it interpolate anything you will put inside those brackets. So if you will put expression with some calculations or just variables related to current scope it will convert all variables to their values and apply calculations.
For instance: {{scopevar1 + scopevar2}} in case this variables has some values, let it be 1 and 2, as result we will see 3.
:: - This mean one time binding. For instance {{::scopevar1}} it will be interpolated once and will not check for changes of scopevar1, always stay as first value. Even if scopevar1 will change every second, the value in template will be the same. Angular Expressions - here you can find some live examples and more information.
userdirective="{{::key}}" - This case is nothing more then assigning dynamic value to your directive. UserDirective expectes to get a simple value, but we have it inside our scope, so we need to say: Hey, angular please interpolate scope variable - key, but only once, so my directive will get value, and will not looking for updates of key. And angular does it with pleasure!
userdirective="{condition:isActive(route.name),mdColors:{color:'primary'}}"
The last case is when your directive expects to get some kind of specific JSON. And we don't want to build it inside of controller. It is sometimes easier to do such things in the tempalte. So we put specific object with two properties: condition, mdColors. And saying that first property assigned to result of function, and second one is simple object {color:'primary'}.
That's it!
{{var}} is a two way binding expression and {{::var}} is a one-way binding expression. expression with :: will not change once set, it is a candidate for one-time binding.
go through : https://docs.angularjs.org/guide/expression for better examples on these
{{name}} is the regular case you will find. You basically print the variable name and update it once it changes.
{{::name}} is the same but your value will not receive updates once it stabilises.
So in the first case, your template updates once name is changed. In the latter, it isn't.
userdirective="{{::key}}" is a one-way one-time binding. Leave the :: out and your directive receives updates if key changes. However, if the directive changes key, it will not update the parent.
ng-if="::field.sortable" is a two-way binding. The changes go both ways. In this case, field.sortable is watched by the directive.
userdirective="{condition:isActive(route.name),mdColors:{color:'primary'}}" is used when you want to build adhoc-objects. A popular case is ng-class as well. You may build this object in the controller as well as you should not put too much logic in your template.
In any case, it is advisable to read the excellent docs https://docs.angularjs.org/guide

Multiple usages of same controller in angular

problem with angular. On my website a have comments. Each comment shares the same 'ng-controller="commentCtrl"' directive. Now when I have about 300 comments on my web site, there are 300 commentCtrl instances. In the html of the controller I am using ng-disabled="author_provided()" on a button.
When I am changing the author input text field, all the 300 comments are invoking author_provided() ( because this function depends on on the author ng-model). This causes performance issues. I want the author_provided() function be invoked only in the controller where I am changing the author. How to achieve that ?
The author_provided function will be evaluated for each comment on every $digest cycle. If you must circumvent this behavior, I suggest adding the author provided boolean as a property of the comment object. Then your template code can simply read: ng-disabled=comment.author_provided (no function call) and Angular will evaluate the result without calling the controller function.
The function is called for every comment because the templating engine can't know the result of ng-disabled for each comment without evaluating the controller function call.

AngularJS: manual $compile vs natural $compile over a recursive directive

I've tried to create my own recursive directive with AngularJS, wich calls itself to transform an object in a view with a pretty JSON format. Well, first I used a ng-include calling a script with the template, with a ng-if inside of it verifing if the current value was an object, if is, the template call itself.
I always think this is a ungly way to doind that, so I transform in a directive, much more simple.
More and less... Because I discover the world of recursive directives and found a lot of things, and most interesting. I even gave it a read in source code of Angular in github (I recommend you to read: https://github.com/angular/angular.js), with was a good things.
I searched so hard, and I thinkg I'm almost finding the awser that will cherish my heart! Because I learn a lot of new things and you guys, will help me.
Look my code in the link below: https://github.com/Daymannovaes/htmljs/blob/master/js/directives/recursiveDataTemplateDirective.js
my directive are: recursive-data-template="data", where data are an object. This directive will loop over the key and values of this object, and if the value was an object, will do this again. The condition are made with ng-if="isObject(value)".
Ok, my first problem was the infinite loop. I need to remove the content in the compile phase and then imperatively compile the content in the postLink phase. My question:
** Why the manual compile do not falls on the same problem of the infinite loop? **
I'm compiling the same content, no condition are made (if the if(!compiledContent) was removed, the infinite loop still not occurring), the diference (I think) are only they are maded in diferent place, but I wasn't able to find in internet someone who awser my question!
So, thank you!
(if the link doesn't working, here are the important code):
compile: function(templateElement, templateAttributes) {
/*
in compile time, we need to remove the innerHTML of template(url) because of its recursive.
Because of its recusiveness, when the $compile start to read the DOM tree and find a
recursiveDataTemplate directive (even its not will be evaluated all time by link function
because the ng-if, whatever) its start the do all the process again. So, we need the .remove()
*/
var templateDirectiveContent = templateElement.contents().remove();
var compiledContent;
return function($scope, linkElement, linkAttributes) {
/*
This verification avoid to compile the content to all siblings, because
when you compile the siblings, don't work (I don't know why, yet).
So, doing this we get only the top level link function (from each iteration)
*/
if(!compiledContent) {
compiledContent = $compile(templateDirectiveContent);
}
/*
Calling the link function passing the actual scope we get a clone object
wich contains the finish viewed object, the view itself, the DOM!!
Then, we attach the new dom in the element wich contains the directive
*/
compiledContent($scope, function(clone) {
linkElement.append(clone);
});
};
},
}
<ul>
<li data-ng-repeat="(key, value) in fields">
<span data-ng-if="!isNumber(key)">
{{key}}:
</span>
<span data-ng-if="isObject(value)" recursive-data-template="value"></span>
<span data-ng-if="!isObject(value)">
{{value}}
</span>
</li>
</ul>
I believe this excerpt from the official documentation is relevant to what you're asking:
Note: The compile function cannot handle directives that recursively use themselves in their own templates or compile functions. Compiling these directives results in an infinite loop and a stack overflow errors. This can be avoided by manually using $compile in the postLink function to imperatively compile a directive's template instead of relying on automatic template compilation via template or templateUrl declaration or manual compilation inside the compile function.
And going from the code you've provided, you've seem to have done what this note is suggesting - that is, manually compiling inside the function (postLink) you're returning for the compile property of your directive.
About the reason why compiling during the postLink rather than the compile phase avoids the infinite recursion, that's because all the elements of the DOM are compiled wether they are actually used or not, while the link will only be triggered when the element is actually linked: for insteance if a higher ng-if is falsy, its children element will not be preLinked, and thus neithr postLinked... At least from my understanding!
I recommend this good article: http://www.jvandemo.com/the-nitty-gritty-of-compile-and-link-functions-inside-angularjs-directives/

Controller <directiveName>, Required by Directive 'ngClass', Can't be Found

I get the error "Controller 'gssResponseGroup', required by directive 'ngClass', can't be found!" when using the linked Plunker files. Problem is, sometimes it works perfectly fine and other times I get this error. My guess is that the order of loading/compiling of the directives is not consistent.
Anyone have any ideas?
Plunker
I don't see why it states that it can't be found. It is defined right above in the same JavaScript file.
I can't recreate your error, but I am guessing the imperative transclude() call in your link function is creating a race condition. You're using pre-1.2 transcludes with 1.3, so take a look at the current docs for your transcluding needs: https://docs.angularjs.org/guide/directive
I'm also pretty sure the error is not referring to a dependency injection lookup failure, but because the require: '^gssResponseGroup', line in your sub-directive can't find the not-yet-instantiated / linked controller of a parent directive, since it's being transcluded in an ad-hoc way.

Angular directive failing silently (directive rendered too soon?)

I have an Angular app with a custom element directive (<uber-table>). <uber-table> is supposed to take a collection of objects, render them into a <table> and add some functionality (click row to toggle the underlying object as selected, search box for live filtering, action links on each row with customized click callback(s) for each object). I created a Plunker with the relevant code. Please note that Plunker is giving an error about map (Object [object Object] has no method 'map'), but locally I am not getting any errors.
The post-link function's element parameter is not the <uber-table> element as I expected. Instead it is the template's <div class="uber-table"> element. This is preventing me from extracting data from <uber-table>. What am I doing wrong? Any help will be much appreciated.
Here's a rundown on some of the issues.
First main issue is you want existing content already within the uber-table markup to exist, as well as a new template. Unless told otherwise the existing content ( columns) in this case will be overwritten. In order to include existing content in a directive that has a template, you need to set transclude:true then identify within template where this existing content needs to be placed using ng-transclude on element that will be parent of the content.
Here's demo with transclude fixed
New problems arise now where you are trying to use jQuery to loop over columns and return angular attrs => column.attrs . This throws undefined error.
I haven't tried to unravel this enough to sort out the columns issues yet. They should likely be handled by directive of their own
UPDATE: here's slightly revised error free version using jQuery to parse column count. I'm afraid am still confused a bit by structure of this. I don't see need to use jQuery to parse colunms, this could be converted to directive or pass column definitions into main directive from controller scope
After trying several things and looking at the documentation again, I finally got it working. The solution was to put the post-link function inside the compile function. Also I had to update my isolated scope to use =, set replace to true and set transclude to 'element'.
I updated Plunker if anybody wants to see the changes. The Plunker version isn't working, but since it is working locally, I'm not going to spend too much time on it.

Resources