se un percorso ngSrc si risolve in un 404, c'è un modo per ripristinare un valore predefinito?


129

L'applicazione che sto creando richiede che il mio utente imposti 4 informazioni prima che questa immagine abbia persino la possibilità di essere caricata. Questa immagine è l'elemento centrale dell'applicazione, quindi il collegamento all'immagine interrotto fa sembrare che tutto sia bloccato. Vorrei che un'altra immagine prendesse il suo posto su una 404.

Qualche idea? Vorrei evitare di scrivere una direttiva personalizzata per questo.

Sono stato sorpreso di non riuscire a trovare una domanda simile, soprattutto quando la prima domanda nei documenti è la stessa!

http://docs.angularjs.org/api/ng.directive:ngSrc


Questo potrebbe essere rilevante: stackoverflow.com/q/13781685/1218080
principio olografico

Risposte:


252

È una direttiva piuttosto semplice da guardare per un errore nel caricamento di un'immagine e per sostituire l'src. (Plunker)

html:

<img ng-src="smiley.png" err-src="http://google.com/favicon.ico" />

Javascript:

var app = angular.module("MyApp", []);

app.directive('errSrc', function() {
  return {
    link: function(scope, element, attrs) {
      element.bind('error', function() {
        if (attrs.src != attrs.errSrc) {
          attrs.$set('src', attrs.errSrc);
        }
      });
    }
  }
});

Se si desidera visualizzare l'immagine di errore quando ngSrc è vuoto, è possibile aggiungere questo (Plunker) :

attrs.$observe('ngSrc', function(value) {
  if (!value && attrs.errSrc) {
    attrs.$set('src', attrs.errSrc);
  }
});

Il problema è che ngSrc non aggiorna l'attributo src se il valore è vuoto.


Funziona bene se l'URL è rotto (404), ma se è una stringa vuota ng-src ingoia silenziosamente l'errore.
Stephen Patten,

2
Se una stringa vuota non è valida per il tuo modello, assicurati che l'espressione a cui stai vincolando con ng-src non restituisca una stringa vuota.
Justin Lovero,

Script aggiornato per supportare l'uso srcdell'attributo per l' err-srcimmagine: plnkr.co/edit/b05WtghBOHkxKdttZ8q5
Ryan Schumacher,

@JustinLovero Lo considero un bug in angolare e l'ho segnalato. Il problema è che ngSrc non imposterà l'attributo src se il valore è vuoto. Questo potrebbe effettivamente essere un problema se, ad esempio, stai visualizzando un telefono e carichi un altro telefono con Ajax a cui manca un URL immagine. L'immagine del vecchio telefono continuerebbe a essere visualizzata, ma penso che un errore o un segnaposto sarebbero più appropriati. Sì, potresti gestirlo nel tuo modello ma il comportamento di lasciare una vecchia immagine è, a mio avviso, più sbagliato rispetto alla visualizzazione di un errore.
Jason Goemaat,

@JasonGoemaat, come farei per sostituire l'immagine rotta con del testo?
simeg

48

Poco tardi alla festa, anche se ho trovato una soluzione a più o meno lo stesso problema in un sistema che sto costruendo.

La mia idea era, tuttavia, quella di gestire OGNI imgtag immagine a livello globale.

Non volevo esagerare HTMLcon direttive inutili, come err-srcquelle mostrate qui. Abbastanza spesso, specialmente con immagini dinamiche, non saprai se manca fino a quando è troppo tardi. L'aggiunta di direttive extra sul caso che manca a un'immagine mi sembra eccessivo.

Invece, estendo il imgtag esistente - che, in realtà, è ciò di cui tratta le direttive angolari.

Quindi - questo è quello che mi è venuto in mente.

Nota : ciò richiede la presenza della libreria JQuery completa e non solo della JQlite Angular perché stiamo usando.error()

Puoi vederlo in azione in questo Plunker

La direttiva si presenta in questo modo:

app.directive('img', function () {
    return {
        restrict: 'E',        
        link: function (scope, element, attrs) {     
            // show an image-missing image
            element.error(function () {
                var w = element.width();
                var h = element.height();
                // using 20 here because it seems even a missing image will have ~18px width 
                // after this error function has been called
                if (w <= 20) { w = 100; }
                if (h <= 20) { h = 100; }
                var url = 'http://placehold.it/' + w + 'x' + h + '/cccccc/ffffff&text=Oh No!';
                element.prop('src', url);
                element.css('border', 'double 3px #cccccc');
            });
        }
    }
});

Quando si verifica un errore (che sarà dovuto al fatto che l'immagine non esiste o è irraggiungibile ecc.) Catturiamo e reagiamo. Puoi anche tentare di ottenere le dimensioni dell'immagine, in primo luogo se erano presenti sull'immagine / stile. In caso contrario, imposta te stesso un valore predefinito.

Questo esempio usa invece placehold.it per mostrare un'immagine.

Ora OGNI immagine, indipendentemente dall'uso srco si ng-srcè coperta nel caso in cui nulla si carichi ...


2
soluzione molto bella! votiamo questo verso l'alto!
Matthijs,

1
Questo è abbastanza bello, devo dire. In realtà non voglio che nessuna immagine venga mostrata se non si risolve, quindi ho usato: element.css ("display", "none"); per nasconderlo totalmente
Pompeo,

1
bello :) o potresti semplicemente rimuoverlo del tutto dal DOM, con element.remove (); :)
Darren Wainwright,

Funziona benissimo, grazie!
ecorvo,

Modificato un po 'la tua soluzione, quindi funziona anche quando il percorso è vuoto. stackoverflow.com/a/35884288/2014288
andrfas

21

Per espandere la soluzione Jason in modo da rilevare entrambi i casi di errore di caricamento o una stringa di origine vuota, possiamo semplicemente aggiungere un watch.

html:

<img ng-src="smiley.png" err-src="http://google.com/favicon.ico" />

Javascript:

var app = angular.module("MyApp", []);

app.directive('errSrc', function() {
  return {
    link: function(scope, element, attrs) {

      var watcher = scope.$watch(function() {
          return attrs['ngSrc'];
        }, function (value) {
          if (!value) {
            element.attr('src', attrs.errSrc);  
          }
      });

      element.bind('error', function() {
        element.attr('src', attrs.errSrc);
      });

      //unsubscribe on success
      element.bind('load', watcher);

    }
  }
});

2
Bel codice, ma sembra eccessivo aggiungere un orologio per ciò che può essere facilmente verificato con ng-ifil modello (la stringa src vuota)
alonisser,

Sembra che tu possa semplicemente sostituire ngSrc con la tua direttiva e aver impostato il bind on error per usare errSrc invece di avere una direttiva errSrc separata se è quello che vuoi. Potresti usare attrs.$observe('ngSrc', function(value) {...});, questo è quello che ngSrc usa internamente invece di $ watch
Jason Goemaat

L'intero problema con gli spazi vuoti è perché ngSrc non aggiorna l'attributo src se il valore è vuoto, quindi se avessi la tua direttiva che sostituisce ngSrc non avrebbe bisogno di un controllo vuoto.
Jason Goemaat,

1
@Pokono - puoi verificare all'interno della funzione di errore se l'attuale attributo 'src' è uguale a 'errSrc'.
user2899845

1
@BiswasKhayargoli - aggiunta una chiamata per annullare l'iscrizione, quindi non avrà alcun costo di elaborazione dopo il caricamento iniziale
user2899845

11
App.directive('checkImage', function ($q) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            attrs.$observe('ngSrc', function (ngSrc) {
                var deferred = $q.defer();
                var image = new Image();
                image.onerror = function () {
                    deferred.resolve(false);
                    element.attr('src', BASE_URL + '/assets/images/default_photo.png'); // set default image
                };
                image.onload = function () {
                    deferred.resolve(true);
                };
                image.src = ngSrc;
                return deferred.promise;
            });
        }
    };
});

in HTML:

<img class="img-circle" check-image ng-src="{{item.profileImg}}" />

Penso che questa dovrebbe essere la risposta accettata poiché non utilizza jQuery e risolve il problema
Gregra,

10

Se l'immagine è 404 o l'immagine è nulla vuota qualunque cosa non sia necessaria per le direttive puoi semplicemente usare il filtro ng-src come questo :)

<img ng-src="{{ p.image || 'img/no-image.png' }}" />

2
No, se p.image restituisce un 404 su una chiamata AJAX, lo tratterà come 'vero' e non passerà al secondo img / no-image.png.
James Gentes,

2
@JamesGentes - Questo non sembra essere il caso. Questo uso di ng-src funziona per me esattamente come mostrato. Ed è molto più semplice.
shanemgrey,

@shaneveeg grazie, è interessante - mi chiedo se il back-end sia diverso. Sto eseguendo Node con Express, forse lo gestisce in modo diverso.
James Gentes,

2
@JamesGentes - Correzione del mio commento precedente. L'OP sta cercando una soluzione quando viene restituito un URI valido dal modello, ma un 404 quando viene seguito. In tal caso, questa soluzione non funziona, si traduce in un'immagine interrotta. Se angolare non restituisce nulla per il valore dell'immagine, questo sceglie correttamente il fallback.
Shanhangrey,

Sto usando Laravel e funziona perfettamente per me se 404, null o vuoto è il caso. ma forse Express restituisce un qualche tipo di errore invece della risposta del codice come 404, quindi sì probabilmente dipende dal framework del server
NeoNe,

8

Uso qualcosa del genere, ma presuppone che team.logo sia valido. Forza l'impostazione predefinita se "team.logo" non è impostato o è vuoto.

<img ng-if="team.logo" ng-src="https://api.example.com/images/{{team.logo}}">
<img ng-hide="team.logo" ng-src="img/default.png">

2
Questo è corretto, poiché valuta "team.logo" come una stringa (vero / falso) mentre ng-src vedrà {{team.logo}} come vero anche se restituisce un 404.
James Gentes,


1

C'è un motivo specifico per cui non puoi dichiarare l'immagine di fallback nel tuo codice?

A quanto ho capito, hai due possibili casi per la tua fonte di immagini:

  1. Impostare correttamente le informazioni <4 = Immagine di fallback.
  2. Imposta correttamente le informazioni == 4 = URL generato.

Penso che questo dovrebbe essere gestito dalla tua app - se attualmente non è possibile determinare l'URL corretto, passa invece un URL di immagine di caricamento / fallback / segnaposto.

Il ragionamento è che non hai mai un'immagine "mancante", perché hai esplicitamente dichiarato l'URL corretto da visualizzare in qualsiasi momento.


Spiacenti, avrei dovuto chiarire che anche se tutti e 4 i valori sono impostati, l'URL può comunque 404 (l'utente può aggiungere cartelle lungo la strada.) Nel frattempo ho aggiunto una funzione che controlla le intestazioni di risposta dell'URL quando tutti i valori sono impostati. Sarebbe comunque bello gestirlo senza un trattamento speciale, scommetto che lo troverò di nuovo :)
will_hardin

1
Bene, dovresti mostrarlo come una risposta e contrassegnarlo come accettato per chiunque cerchi in futuro.
Alex Osborn,

1

Ti suggerisco di utilizzare la direttiva "if statement" di Angular UI Utils per risolvere il tuo problema, come si trova su http://angular-ui.github.io/ . L'ho appena usato per fare esattamente la stessa cosa.

Questo non è testato, ma potresti fare qualcosa del tipo:

Codice controller:

$scope.showImage = function () {
    if (value1 && value2 && value3 && value4) { 
        return true;
    } else {
        return false;
    }
};

(o più semplice)

$scope.showImage = function () {
    return value1 && value2 && value3 && value4;
};

HTML in vista: <img ui-if="showImage()" ng-src="images/{{data.value}}.jpg" />

O ancora più semplice, potresti semplicemente usare una proprietà scope:

Codice controller:

$scope.showImage = value1 && value2 && value3 && value4;

HTML in vista: <img ui-if="showImage" ng-src="images/{{data.value}}.jpg" />

Per un'immagine segnaposto, basta aggiungere un altro <img>tag simile ma anteporre il ui-ifparametro con un punto esclamativo ( !) e fare in modo che ngSrc abbia il percorso dell'immagine del segnaposto o utilizzare semplicemente un srctag come nel normale vecchio HTML.

per esempio. <img ui-if="!showImage" src="images/placeholder.jpg" />

Ovviamente, tutti i precedenti esempi di codice presuppongono che ciascuno di value1, value2, value3 e value4 corrisponderà a null/ falsequando ognuna delle tue 4 informazioni è incompleta (e quindi anche a un valore booleano di truequando sono complete).

PS. Il progetto AngularUI è stato recentemente suddiviso in sottoprogetti e la documentazione per ui-ifsembra attualmente mancare (probabilmente è nel pacchetto da qualche parte). Tuttavia, è piuttosto semplice da usare come puoi vedere, e ho registrato un "problema" di Github sul progetto dell'interfaccia utente angolare per segnalarlo anche al team.

AGGIORNAMENTO: "ui-if" non è presente nel progetto AngularUI perché è stato integrato nel codice AngularJS principale! Solo a partire da v1.1.x, che è attualmente contrassegnato come 'instabile'.


ng-iffatto nel core tra l'altro (a partire da 1.1.5 se non sbaglio).
principio olografico

1

Ecco una soluzione che mi è venuta in mente usando JavaScript nativo. Sto verificando se l'immagine è rotta, quindi aggiungo una classe all'immagine per ogni evenienza e cambio l'origine.

Ho ricevuto parte della mia risposta da una risposta di Quora http://www.quora.com/How-do-I-use-JavaScript-to-find-if-an-image-is-broken

app.directive('imageErrorDirective', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element[0].onerror = function () {
                element[0].className = element[0].className + " image-error";
                element[0].src = 'http://img3.wikia.nocookie.net/__cb20140329055736/pokemon/images/c/c9/702Dedenne.png';
            };
        }
    }
});

0

È venuto con la mia soluzione. Sostituisce l'immagine sia se src o ngSrc è vuoto e se img restituisce 404.

(fork della soluzione @Darren)

directive('img', function () {
return {
    restrict: 'E',        
    link: function (scope, element, attrs) {   
        if((('ngSrc' in attrs) && typeof(attrs['ngSrc'])==='undefined') || (('src' in attrs) && typeof(attrs['src'])==='undefined')) {
            (function () {
                replaceImg();
            })();
        };
        element.error(function () {
            replaceImg();
        });

        function replaceImg() {
            var w = element.width();
            var h = element.height();
            // using 20 here because it seems even a missing image will have ~18px width 
            // after this error function has been called
            if (w <= 20) { w = 100; }
            if (h <= 20) { h = 100; }
            var url = 'http://placehold.it/' + w + 'x' + h + '/cccccc/ffffff&text=No image';
            element.prop('src', url);
        }
    }
}
});

0

Ciò consentirà solo di eseguire il ciclo due volte, per verificare se ng-src non esiste altrimenti utilizzare err-src, questo impedisce il ciclo continuo.

(function () {
    'use strict';
    angular.module('pilierApp').directive('errSrc', errSrc);

    function errSrc() {
        return {
            link: function(scope, element, attrs) {
             element.error(function () {
                // to prevent looping error check if src == ngSrc
                if (element.prop('src')==attrs.ngSrc){
                     //stop loop here
                     element.prop('src', attrs.errSrc);
                }              
            });
            }
        }
    }
})();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.