152

In angularjs you have the tag ng-src which has the purpose that you won't receive an error for an invalid url before angularjs gets to evaluate the variables placed in between {{ and }}.

The problem is that I use quite some DIV's with a background-image set to an url. I do this because of the excellent CSS3 property background-size which crops the image to the exact size of the DIV.

The only problem is that I receive a lot of errors for the exact same reason they created a ng-src tag: I have some variables in the url and the browser thinks the image doesn't exist.

I realize that there is a possibility of writing a crude {{"style='background-image:url(myVariableUrl)'"}}, but this seems 'dirty'.

I've searched a lot and can't find the right way to do this. My app is becoming a mess because of all of these errors.

Chris Seymour
  • 83,387
  • 30
  • 160
  • 202
Jasper Schulte
  • 7,181
  • 7
  • 23
  • 19

9 Answers9

232

This one works for me

<li ng-style="{'background-image':'url(/static/'+imgURL+')'}">...</li>
hooblei
  • 3,210
  • 2
  • 20
  • 17
  • 23
    I think this should be the accepted answer. There's no need to add a custom directive. – Jakob Løkke Madsen Jun 20 '14 at 09:26
  • 2
    I added an answer similar to that one, just using interpolation – wiherek Aug 20 '14 at 20:55
  • 1
    It's dirtier, in the sense that a directive fits all, everywhere you need a dynamic background. The question mentioned it felt less reusable. – Christian Bonato May 10 '16 at 18:21
  • Doesn't work in Angular 1.5.8, it still tries to load the image with `imgURL` not set and retries when `imgURL` gets set. – leroydev Sep 16 '16 at 19:27
  • This is incorrect, if you have any special variables like @% in your URL, it will break. – ericjam Sep 28 '16 at 20:25
  • @efwjames this answer provides the right idea; you're encountering a separate problem involving how the browser parses the `url` css declaration. You should quote its contents: `ng-style="{'background-image':'url(\'/static/'+imgURL+'\')'}"` although considering all the levels of quoting it becomes messy – Gershom Maes Jan 23 '18 at 16:12
202

ngSrc is a native directive, so it seems you want a similar directive that modifies your div's background-image style.

You could write your own directive that does exactly what you want. For example

app.directive('backImg', function(){
    return function(scope, element, attrs){
        var url = attrs.backImg;
        element.css({
            'background-image': 'url(' + url +')',
            'background-size' : 'cover'
        });
    };
});​

Which you would invoke like this

<div back-img="<some-image-url>" ></div>

JSFiddle with cute cats as a bonus: http://jsfiddle.net/jaimem/aSjwk/1/

jaime
  • 41,961
  • 10
  • 82
  • 52
  • 68
    Very nice directive; the only problem is that it doesn't support interpolation. I would probably modify it thusly: http://jsfiddle.net/BinaryMuse/aSjwk/2/ – Michelle Tilley Dec 09 '12 at 07:44
  • 1
    Thank you, it doesn´t matter that is is without interpolation, it has kittens! – Visgean Skeloru Jul 23 '13 at 16:38
  • Any security issues with this if you are loading from untrusted sources? – A F Jun 18 '15 at 23:42
  • this works, but only if your image url is already defined, use @mythz answer http://stackoverflow.com/a/15537359/1479680 to have a better obeservable url – Magico Apr 28 '16 at 15:07
69

The above answer doesn't support observable interpolation (and cost me a lot of time trying to debug). The jsFiddle link in @BrandonTilley comment was the answer that worked for me, which I'll re-post here for preservation:

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

Example using controller and template

Controller :

$scope.someID = ...;
/* 
The advantage of using directive will also work inside an ng-repeat :
someID can be inside an array of ID's 
*/

$scope.arrayOfIDs = [0,1,2,3];

Template :

Use in template like so :

<div back-img="img/service-sliders/{{someID}}/1.jpg"></div>

or like so :

<div ng-repeat="someID in arrayOfIDs" back-img="img/service-sliders/{{someID}}/1.jpg"></div>
Trisped
  • 5,705
  • 2
  • 45
  • 58
mythz
  • 141,670
  • 29
  • 246
  • 390
40

It's also possible to do something like this with ng-style:

ng-style="image_path != '' && {'background-image':'url('+image_path+')'}"

which would not attempt to fetch a non-existing image.

holographic-principle
  • 19,688
  • 10
  • 46
  • 62
  • 2
    @sqren also see this if you are using a newer version of Angular (1.1.5 and above): http://stackoverflow.com/a/12151492/1218080 . Support for an actual ternary operator in views was added. – holographic-principle Oct 28 '13 at 22:25
  • simple string concatenation like this worked for me on scope object, instead of interpolation. – Shardul Aug 13 '15 at 00:54
25

Similar to hooblei's answer, just with interpolation:

<li ng-style="{'background-image': 'url({{ image.source }})'}">...</li>
wiherek
  • 1,923
  • 19
  • 25
9

just a matter of taste but if you prefer accessing the variable or function directly like this:

<div id="playlist-icon" back-img="playlist.icon">

instead of interpolating like this:

<div id="playlist-icon" back-img="{{playlist.icon}}">

then you can define the directive a bit differently with scope.$watch which will do $parse on the attribute

angular.module('myApp', [])
.directive('bgImage', function(){

    return function(scope, element, attrs) {
        scope.$watch(attrs.bgImage, function(value) {
            element.css({
                'background-image': 'url(' + value +')',
                'background-size' : 'cover'
            });
        });
    };
})

there is more background on this here: AngularJS : Difference between the $observe and $watch methods

Community
  • 1
  • 1
kristok
  • 152
  • 1
  • 6
2

@jaime your answer is fine. But if your page has the $http.get then you have use ng-if

app.directive('backImg', function(){
    return function($scope, $element, $attrs){
        var url = $attrs.backImg;
        $element.css({
            'background-image': 'url(' + url + ')',
            'background-size': 'cover'
        });
    }
});

in the factory

app.factory('dataFactory', function($http){
    var factory = {};
    factory.getAboutData = function(){
        return $http.get("api/about-data.json");
    };
    return factory;
});

in the controller area

app.controller('aboutCtrl', function($scope, $http, dataFactory){
    $scope.aboutData = [];
    dataFactory.getAboutData().then(function(response){
        // get full home data
        $scope.aboutData = response.data;
        // banner data
        $scope.banner = {
            "image": $scope.aboutData.bannerImage,
            "text": $scope.aboutData.bannerText
        };
    });
});

and the view if you use the ng-if like below then you will get the image by interpolation otherwise, you can't get the image because directive get the value before HTTP request.

<div class="stat-banner"  ng-if="banner.image" background-image-directive="{{banner.image}}">
Subhojit Mondal
  • 453
  • 7
  • 17
1

I've found with 1.5 components that abstracting the styling from the DOM to work best in my async situation.

Example:

<div ng-style="{ 'background': $ctrl.backgroundUrl }"></div>

And in the controller, something likes this:

this.$onChanges = onChanges;

function onChanges(changes) {
    if (changes.value.currentValue){
        $ctrl.backgroundUrl = setBackgroundUrl(changes.value.currentValue);
    }
}

function setBackgroundUrl(value){
    return 'url(' + value.imgUrl + ')';
}
Pat Migliaccio
  • 1,031
  • 13
  • 24
0

Since you mentioned ng-src and it seems as though you want the page to finish rendering before loading your image, you may modify jaime's answer to run the native directive after the browser finishes rendering.

This blog post explains this pretty well; essentially, you insert the $timeout wrapper for window.setTimeout before the callback function wherein you make those modifications to the CSS.

aralar
  • 3,022
  • 7
  • 29
  • 44