Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Can't update DOM-node attr while using {{}} expression #12813

Closed
dhilt opened this issue Sep 10, 2015 · 9 comments
Closed

Can't update DOM-node attr while using {{}} expression #12813

dhilt opened this issue Sep 10, 2015 · 9 comments

Comments

@dhilt
Copy link

dhilt commented Sep 10, 2015

There is a kind of race condition between dom-node attr internal recalculation when we are using {{}}-expression and dom-node attr external modifying. This topic is based on #12763 issue and also related to #9109 issue. Also I'd like to thank @gkalpak for he clarified a problem.


Here is a little repro on this issue: http://jsfiddle.net/dhilt/8tceyw2w/9/

I have an element with style-attr contains {{}}-expression:

<div id="myElement" style="padding-left: 15px; color:{{toggle ? 'red' : 'black' }}">

and I want to play with some style property which isn't presented in the markup:

document.getElementById('myElement').style.paddingTop = '20px'; 

Let's run the demo!

1. Initial state. Before 1st digest ends.

Run the demo. Get the alert before out from $watch.
We see that both of padding-left and padding-top properties are set to 15 and 20 px.
And all of them are applied properly; we can see it despite the UI data is still unbound cause of alert-interruption.

2. Initial state. The end of initial digest.

Close the alert. Let the 1st digest cycle end.
See that padding-top is no longer applied to the element.
Something is broken and initial template's value of style-attr has a destructive priority over further updates on it.

3. Recalculate style expression via $scope.toggle changing.

Click on checkbox, start the new digest cycle.
We see that there is no problem with inline padding-left property.
But padding-top property is completely removed from element's style. Just an empty string.
And myElementStyle.paddingTop = '20px' within $watch doesn't work.


.
Thanks and good luck)

@shusain
Copy link

shusain commented Sep 13, 2015

You shouldn't be doing any DOM manipulation in a controller only time a DOM access/mutation should occur is in a directive. If you use the ng-style directive you can avoid the destructive behavior even leaving the DOM manipulation in the controller but I would strong strongly advise against it http://jsfiddle.net/8tceyw2w/10/

@gkalpak
Copy link
Member

gkalpak commented Sep 14, 2015

@shusain, the point is that trying to externally change an attribute's interpolated value is broken. (Where that happens from (e.g. directive, controller etc) is not really important here.)

So, what you say is true in general, but this is just a POC of an actual bug.

@shusain
Copy link

shusain commented Sep 14, 2015

I see what you're saying but following certain rules even when just reproducing an issue seems reasonable to do anyhow. (seems you both know what you're talking about but less knowledgeable devs may see this and think it's the right way to do things)

I can see how this would happen since when the interpolation happens to process the style attribute it's not aware of the styles set on the element directly and so it ignores those but the padding-left is still part of the attribute value that it'll apply after parsing/processing the interpolated value. I suppose the behavior you're expecting is that the style interpolation doesn't remove any existing styles that the interpolated value itself doesn't replace but this seems like it will be a confusing solution itself.

@Narretz
Copy link
Contributor

Narretz commented Sep 28, 2015

I'd also say that this can be expected, given how angular works.
Question is, where can we document it so that people stumble upon an explanation?
Otherwise, I think we need a PR that implements some sort of logic that special cases existing attributes to see if it works or if it's worth it.

@artuska
Copy link

artuska commented Oct 31, 2015

(Moved this comment from the closed #9109 issue).

This issue is not fixed untill latest 1.4.7 version.

I'm using Angular 1.4.7 (also tested in 1.4.5 and 1.4.6) — I have the same issues as described in this topic — if I have 2 or more {{...}} in class attribute then ng-class and ng-show|hide directives stop working.

(In fact, they are not working only in first digest cycle — after the scope is updated second time (after some action which updates the scope, for example), everything works as expected :)

So, here ng-class does not work:
<div class="button button_{{name}} button_{{name}}_{{action}}" ng-class="{'processable': isAllowed(action), 'active': madeAction(action), 'foo': 1, 'bar': true}" ng-click="runButton(action)">

All functions return true, also foo and bar are true... but ng-class just not running and I do not see any processable|active|foo|bar classes in my class attribute.

ng-class starts working in two ways:

  1. If I just remove some {{...}} from the class attribute:
    <div class="button button_{{name}}" ng-class="{'processable': isAllowed(action), 'active': madeAction(action), 'foo': 1, 'bar': true}" ng-click="runButton(action)">
    everything magically starts working.

  2. Or, if I just update the scope (ng-click updates the scope) — both ng-class and ng-show|hide directives start workng as expected.

@Narretz
Copy link
Contributor

Narretz commented Oct 31, 2015

@artuska Yours is actually a slightly different problem (possibly caused by the same underlying issue). Question: why do you want to use interpolation and ngClass at the same time?

@artuska
Copy link

artuska commented Oct 31, 2015

@Narretz because of code style:

<div class="mix-statistics mix-statistics_{{name}}">
    <ul class="mix-statistics__list mix-statistics__list_{{name}}">
        <li class="mix-statistics__item mix-statistics__item_{{name}} mix-statistics__item_{{name}}_{{action}}" ng-switch="action" ng-mouseenter="openBlock(action)" ng-mouseleave="closeBlock(action)" ng-repeat="action in actions">
            <div class="mix-statistics__button mix-statistics__button_{{name}} mix-statistics__button_{{name}}_{{action}}" ng-class="{'processable': isAllowed(action), 'active': isActive(action)}" ng-click="runAction(action)">
                <div class="mix-statistics__icon mix-statistics__icon_{{name}} mix-statistics__icon_{{name}}_{{action}}">
                    <div class="mix-statistics__symbol mix-statistics__symbol_{{name}} mix-statistics__symbol_{{name}}_{{action}}">
                        <svg class="icon icon_centered">
                            <use xlink:href="" ng-attr-xlink:href="{{'#icon-' + action}}"></use>
                        </svg>
                    </div>
                </div>

                <div class="mix-statistics__counter mix-statistics__counter_{{name}} mix-statistics__counter_{{name}}_{{action}}" ng-show="(mix.statistics[action])" ng-bind="mix.statistics[action]"></div>
            </div>
        </li>
    </ul>
</div>

Also, because I cannot set all those {{name}}_{{action}} with ng-class — well, I can, but I don't want to — it will look extremely ugly.

@zweissman
Copy link

zweissman commented May 18, 2016

I ran into this in Angular v1.5.5 as well. I just fixed it like this...

<div class="static-class" ng-class="{ dynamic-class1: conditional1, dynamic-class2: conditional2}">

to

<div ng-class="{ static-class: true, dynamic-class1: conditional1, dynamic-class2: conditional2}">

or if your class name contains a variable

<div ng-class="{ static-class-{{variable}}: true, dynamic-class1: conditional1, dynamic-class2: conditional2}">

@dantheother
Copy link

@zweissman thanks, that helped. In my case I had to wrap the interpolated classes in single quotes

<div ng-class="{ 'static-class-{{variable}}': true, 'dynamic-class1': conditional1, 'dynamic-class2': conditional2}">

gkalpak added a commit to gkalpak/angular.js that referenced this issue Jun 25, 2016
gkalpak added a commit to gkalpak/angular.js that referenced this issue Jun 25, 2016
@gkalpak gkalpak closed this as completed in a2c1675 Jul 4, 2016
gkalpak added a commit that referenced this issue Jul 4, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
7 participants