49

I have a service that returns image URL, and it is called using the following code:

angular.forEach(results, function (item) {
     item.img = "/images/searchItem.jpg";
     $http.post("https://my.api.com/?id=" + item.id).success(
           function (url) {
               item.img = url;
           });
});

on my view I used to have an image with ng-src attribute, that worked perfectly like so:

<img  ng-src="{{item.img}}">

Then I've decided to use background-image on a SPAN instead:

<span ng-style="{'background':'transparent url({{item.img}})'}"></span>

now the flow works only on the first run, after that I can see (in the console) the following html

<span ng-style="{'background':'transparent url(http://expertsimages.liveperson.com/images/rating/rate10.gif)'}" style="background-image: url(https://fb.lp-chat.com/images/searchItem.jpg); background-color: transparent; background-position: initial initial; background-repeat: initial initial;"></span>

which indicates that the variable updated, however the html is still on its initial state.

I tried to call apply/digest on post success, but got an error $digest already in progress (which make sense). The strage thing is that after a normal digest (for example on other ui changes) the style is being applied and I see the right image.

What am I missing here?

Update: I've created a JS fiddle that demonstrates this issue...looks like a bug in angular to me.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Shlomi Schwartz
  • 8,693
  • 29
  • 109
  • 186

5 Answers5

93

I ran into this as well. Instead of evaluating with {{ }}, use string + value concatenation.

This will work:

<span ng-style="{'background':'transparent url(' + item.img + ')'}"></span>)

Here's a working version of your fiddle

cleversprocket
  • 1,379
  • 10
  • 11
  • I think its normal its about watcher which's not applying deep watch in this case. – Disfigure Mar 10 '17 at 14:59
  • example : backgroundColor is still the same even if its value changes, but if you add color property it will be applied because a new property on the first level is added. – Disfigure Mar 10 '17 at 15:10
12

It doesn't work because angular evaluate the content of single bracket only once and doesn't locate the variable you want to bind inside that string.

You could however create your own directive as such:

app.directive('backImg', function(){
    return function(scope, element, attrs){
        attrs.$observe('backImg', function(value) {
            element.css({
                'background': 'transparent url(' + value +')'
            });
        });
    };
});

and then use it in your template like this:

<div ng-if="vis" class="container" back-img="{{imgSrc}}"></div>

I have updated your fiddle and it seems to work fine http://jsfiddle.net/dS4pB/3/

GillesC
  • 10,647
  • 3
  • 40
  • 55
6

Where you are currently updating your scope variable, wrap this within a $timeout call to push the updates to your scope and subsequently your view, this will effectively bump the update to the next digest avoiding the digest already in progress conflict:

    $timeout(function(){
        $scope.var = value;
    });

This question thread may give you some useful info regarding apply vs timeout etc

Community
  • 1
  • 1
SW4
  • 69,876
  • 20
  • 132
  • 137
  • 2
    I tried the timeout, however it did not fix the issue ... I even tried a 10 sec delay, then inspected the element. after 10 sec the ng-style attribute updated however the view stayed the same. any new idea? – Shlomi Schwartz Apr 28 '14 at 11:49
  • 1
    This works perfectly. Because of the way timeout works you actually don't even need to put any time delay in at all. Thanks! – charliebeckwith Oct 14 '15 at 12:10
  • 1
    Thanks, there are tears of joy running down my cheeks. – Pacu Jan 24 '16 at 18:09
1

you are probably facing same issue which I faced recently and solved using ng-bind .

check the answer explained in this question :

AngularJS : Why ng-bind is better than {{}} in angular?

once I used ng-bind my innerhtml is properly been updated.

Community
  • 1
  • 1
user429035
  • 411
  • 1
  • 5
  • 14
-1

Angular 2 has a sweet way of doing this, [style.*]="value"

<span [style.background-color]="transparent" [style.background-image]="item.img"></span>
Jules
  • 55
  • 1
  • 3
  • @RPDeshaies Maybe, but as Angular 2 gains more of a following, this question will still potentially be top google result for people looking for these answers on the new framework – S. Buda Apr 26 '17 at 15:48
  • @S.Buda no because angular 2+ has no ng-style I'm here for legacyjs – Mathijs Segers Mar 27 '18 at 09:46