Use case: I wanna append an attribute (_directive_) depending on a condition.
Using _ng-attr-custom-directive_ to add _custom-directive_ dynamically to an element.
Issue: _customDirective_ matches with _ng-attr-custom-directive_ : My directive is instantiated and 'executed'.
Expected: _customDirective_ doesn't match with _ng-attr-custom-directive_ , after execution of the _ng-attr_ directive, it append or not, depending on the evaluation of the expression, and then, if appended, 'execute' the directive.
A plnkr showing the issue :
https://plnkr.co/edit/J1XKP7XWqvSEa6feoWmR
I'm not sure if this is expected behavior or not. I tend to say it's not, because Angular usually doesn't allow you to add directives "dynamically". It also doesn't copy the value of the ng-attr-... attributes, so it's not really useful there either. There are also no tests for this behavior. Still, I assume changing this would be a BC, because the behavior has been like this for a long time (although I see no practical use case for relying on that behavior).
I don't think it should be able to dymanically add directive's using ng-attr-....
The current behavior was kind of a surprise to me tbh. But then again, it has been like this for a long time and it only affects a very uncommon usecase, so I would probably not break it.
Maybe it's not the common usecase, but the fact that the compiler matches ng-attr-x with x directive is not normal: As the documentation says:
_The normalization process is as follows:
Strip x- and data- from the front of the element/attributes.
Convert the :, -, or _-delimited name to camelCase_.
@OmarNaghmouchi, this could just be a documentation issue :)
Here is how I think about it:
ng-attr-foo provides a way to dynamically add/remore the foo property.ng-attr-foo determine whether a [foo] directive gets applied to the element or not, the decision should be made based on the info available at compilation time.Such kind of logic seems better suited for either a parent directive's template function or the foo directive's linking functions.
Afaik, ng-attr-foo doesn't _really_ allow you to remove the foo property - it allows you to add the foo attribute with an interpolated value. If you use it without an interpolated value, it doesn't add foo, and if you have an interpolated value, then there's no way to remove the attribute completely.
So I don' think figuring out if foo gets added is difficult. It'll be added if the attribute contains interpolation.
And @gkalpak I'm not really sure if your comment concludes that you think the current behavior is correct or that we should change it. ;)
ng-attr-foo does let you remove the attribute (if it contains interpolation and the interpolated expression evaluates to undefined).
My comment concludes that we should not change the current behavior :smiley:
You are right. This should probably be documented somewhere, because I don't find that intuitive.
My basic problem is with the normalization process. I don't think angular should matches ng-attr-foo with foo as the documentation already said. I Think it's a bug which have to be fixed.
@gkalpak I a almost sure it's not a documentation issue, since it's mentioned at the documentation of the method:
Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with
x-ordata-) to its normalized, camelCase form.
This should probably be documented somewhere, because I don't find that intuitive.
@Narretz, it is.
@OmarNaghmouchi, as I (tried to) explain in https://github.com/angular/angular.js/issues/14575#issuecomment-219388881, we can't implement it in a way that is reliable and less unexpected than the current behavior. The problem is the directives should be determined during the compile phase, but ng-attr- can evaluate its expression during the linking phase.
<div ng-attr-foo={{undefined}}></div> As my understanding of Angular, the $compiler will first, parse the DOM checking for directive and executing their compiling function once they match( found in the DOM). So, in this stage, while it is normalizing, it match the ng-attr directive but it is not supposed to match foo directive (which is not happening, it does match the foo directive), after all foo could just be another attribute (not a custom directive)), and in the linking phase, it evaluate the expression, depending on it value, it will append or not foo .
So, i don't see very well the relation between what i am saying and the phase of compiling/linking.
Maybe, i should take a look in Angular's code, ng-attr and normalizeespecially, to understand what u mean, but currently i am not seeing it.
@OmarNaghmouchi, assume we have:
// A `foo` directive
myApp.directive('foo', ...)
// The following HTML
<div ng-attr-foo="{{ someExpression }}"></div>
Are you saying that the foo attribute should not be applied to that <div> (regardless of the value of someExpression) ? That would be a major breaking change and I don't see this new behavior as more intuitive than the current one.
The whole point of ng-attr- was to be able to use interpolation (i.e. an Angular expression) to set the value of an attribute that doesn't normally accept {{...}}. In that sense, ng-attr-foo provides another way for adding attribute foo to an element, so it is "more expected" that the $compiler treats it as if [foo] is present.
I am not even sure if the "feature" of removing the attribute when the expression evaluates to undefined was there at the beginning or added later as a side effect of $compile.directive.Attributes handle undefined values. To offer some perspective, all ng-attr- does is call attrs.$set(...) passing the value of the expression. It is Attributes#$set that decides to remove the attribute if value is undefined.
In any case, since ng-attr-foo is an alternative way of adding the foo attribute, I don't think it makes sense to never apply a foo directive. Ideally, one might expect that the value of the expression would determine if the directive gets added or not, but this is unfortunately not possible (as explained above).
So this only leave's us with always applying the foo directive, as if the user had used the foo attribute directly (and not via ng-attr-foo).
Are you saying that the foo attribute should not be applied to that
(regardless of the value of someExpression) ?Yes, i am saying that the first time the
$compilerun, it shouldn't match thefoodirective if it found:
<div ng-attr-foo = {{expression}}></div>(regardless of the value of expression)
It should only match it when it found something like:
<div foo></div>
<div x-foo></div>or
<div data-foo></div>
It just matches theng-attr-directive, if the passed expression evaluate to something else thanundefined,ng-attr-(in itlinkfunction) add the attributefoo(which is in our case a directive) to the element and use$compileto recompile it container element, in case of, the added attributefoois a directive, and only then the$compilershould match, initialize and compile thefoodirective.
@OmarNaghmouchi, the $compiler only runs once and re-compiling already compiled elements is not supported (and leads to errors most of the time). So, we can't do that.
@gkalpak OK, thank you for your explanation.
I know it's been a while but I faced this issue today and the only solution that I found was to evaluate the required attribute's value inside the directive link function to whether apply it or not:
Inside custom directive's link function, first line:
if ($attrs.customDirective !== 'something') {
return;
}
Of course you could perform more advanced or complex validations. I just hope this helps someone else.