Si una ruta ngSrc se resuelve en un 404, ¿hay alguna forma de recurrir a un valor predeterminado?

129

La aplicación que estoy creando requiere que mi usuario establezca 4 piezas de información antes de que esta imagen tenga la posibilidad de cargarse. Esta imagen es la pieza central de la aplicación, por lo que el enlace de imagen rota hace que parezca que todo está borrado. Me gustaría que otra imagen tome su lugar en un 404.

¿Algunas ideas? Me gustaría evitar escribir una directiva personalizada para esto.

Me sorprendió no poder encontrar una pregunta similar, ¡especialmente cuando la primera pregunta en los documentos es la misma!

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

will_hardin
fuente
Esto podría ser relevante: stackoverflow.com/q/13781685/1218080
principio holográfico
relacionado: stackoverflow.com/questions/27549134/…
usuario2954463

Respuestas:

252

Es una directiva bastante simple observar un error al cargar una imagen y reemplazar el 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);
        }
      });
    }
  }
});

Si desea mostrar la imagen de error cuando ngSrc está en blanco, puede agregar esto (Plunker) :

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

El problema es que ngSrc no actualiza el atributo src si el valor está en blanco.

Jason Goemaat
fuente
Funciona bien si la url está rota (404), pero si es una cadena vacía, ng-src se traga el error en silencio.
Stephen Patten
2
Si una cadena vacía no es válida para su modelo, hágalo de modo que la expresión a la que está vinculando con ng-src no devuelva una cadena vacía.
Justin Lovero
Script actualizado para admitir el uso del srcatributo de la err-srcimagen: plnkr.co/edit/b05WtghBOHkxKdttZ8q5
Ryan Schumacher
@JustinLovero Lo considero un error en angular y lo reporté. El problema es que ngSrc no establecerá el atributo src si el valor está en blanco. Eso podría ser un problema si, por ejemplo, está mostrando un teléfono y carga otro teléfono con ajax al que le falta una URL de imagen. La imagen del teléfono antiguo continuaría mostrándose, pero creo que sería más apropiado un error o marcador de posición. Sí, podría manejarlo en su modelo, pero creo que el comportamiento de dejar una imagen antigua es más incorrecto que mostrar un error.
Jason Goemaat
@JasonGoemaat, ¿cómo haría para reemplazar la imagen rota con texto?
simeg
48

Poco tarde para la fiesta, aunque se me ocurrió una solución para más o menos el mismo problema en un sistema que estoy construyendo.

Sin embargo, mi idea era manejar CADA imgetiqueta de imagen a nivel mundial.

No quería tener que salpicar mis HTMLdirectivas innecesarias, como las err-srcque se muestran aquí. Muy a menudo, especialmente con imágenes dinámicas, no sabrá si falta hasta que sea demasiado tarde. Agregar directivas adicionales en caso de que falte una imagen me parece excesivo.

En cambio, extiendo la imgetiqueta existente , que, en realidad, de eso se tratan las directivas angulares.

Entonces, esto es lo que se me ocurrió.

Nota : Esto requiere que la biblioteca JQuery completa esté presente y no solo el JQlite Angular que se envía porque estamos usando.error()

Puedes verlo en acción en este Plunker

La directiva se ve más o menos así:

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');
            });
        }
    }
});

Cuando se produce un error (que se debe a que la imagen no existe o es inalcanzable, etc.) capturamos y reaccionamos. También puede intentar obtener los tamaños de imagen, si estaban presentes en la imagen / estilo en primer lugar. Si no es así, configúrate por defecto.

Este ejemplo está usando placehold.it para que se muestre una imagen en su lugar.

Ahora CADA imagen, independientemente de su uso srco se ng-srcha cubierto en caso de que no se cargue nada ...

Darren Wainwright
fuente
2
Muy buena solución! ¡votemos este por la cima!
Matthijs
1
Esto es bastante hermoso, tengo que decirlo. De hecho, no quiero que se muestre ninguna imagen si no se resuelve, así que utilicé: element.css ("display", "none"); para esconderlo totalmente
Pompeyo
1
nice :) o simplemente puedes eliminarlo del DOM por completo, con element.remove (); :)
Darren Wainwright
Funciona muy bien, gracias!
ecorvo
Editó un poco su solución, por lo que funciona también cuando la ruta está vacía. stackoverflow.com/a/35884288/2014288
andrfas
21

Para expandir la solución de Jason para detectar ambos casos de un error de carga o una cadena de fuente vacía, simplemente podemos agregar un reloj.

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);

    }
  }
});
usuario2899845
fuente
2
Código agradable, pero parece una exageración para añadir un reloj para lo que se puede comprobar fácilmente con ng-ifla plantilla (la cadena src vacío)
alonisser
Parece que podría reemplazar ngSrc con su propia directiva y configurar el enlace por error para usar errSrc en lugar de tener una directiva errSrc separada si eso es lo que desea. Podrías usar attrs.$observe('ngSrc', function(value) {...});, eso es lo que ngSrc usa internamente en lugar de $ watch
Jason Goemaat
Todo el problema con los espacios en blanco es porque ngSrc no actualiza el atributo src si el valor está en blanco, por lo que si tuviera su propia directiva reemplazando ngSrc no necesitaría un cheque en blanco.
Jason Goemaat
1
@Pokono: puede verificar dentro de la función de error si el atributo 'src' actual es el mismo que 'errSrc'.
user2899845
1
@BiswasKhayargoli - agregó una llamada para darse de baja, por lo que no tendrá ningún costo de procesamiento después de la carga inicial
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;
            });
        }
    };
});

en HTML:

<img class="img-circle" check-image ng-src="{{item.profileImg}}" />
Mehul
fuente
Creo que esta debería ser la respuesta aceptada, ya que no utiliza jQuery y resuelve el problema
Gregra
10

Si la imagen es 404 o la imagen está nula vacía, no hay necesidad de directivas, simplemente puede usar el filtro ng-src como este :)

<img ng-src="{{ p.image || 'img/no-image.png' }}" />
NeoNe
fuente
2
No, si p.image devuelve un 404 en una llamada AJAX, lo tratará como 'verdadero' y no pasará al segundo img / no-image.png.
James Gentes
2
@JamesGentes - Ese no parece ser el caso. Este uso de ng-src funciona para mí exactamente como se muestra. Y es mucho más simple.
shanemgrey
@shaneveeg gracias, eso es interesante. Me pregunto si el back-end es lo que es diferente. Estoy ejecutando Node con Express, tal vez lo maneja de manera diferente.
James Gentes
2
@JamesGentes - Corrección de mi comentario anterior. El OP está buscando una solución cuando el modelo devuelve un URI válido, pero un 404 cuando se sigue. En ese caso, esta solución no funciona, da como resultado una imagen rota. Si angular no devuelve nada para el valor de la imagen, esto elige correctamente la reserva.
shanemgrey
Estoy usando Laravel y funciona perfecto para mí si 404, nulo o vacío es el caso. pero tal vez Express devuelve algún tipo de error en lugar de una respuesta de código como 404, por lo que sí probablemente depende del marco del servidor
NeoNe
8

Uso algo como esto, pero supone que team.logo es válido. Se fuerza por defecto si "team.logo" no está configurado o está vacío.

<img ng-if="team.logo" ng-src="https://api.example.com/images/{{team.logo}}">
<img ng-hide="team.logo" ng-src="img/default.png">
evandentremont
fuente
2
Esto es correcto, ya que evalúa "team.logo" como una cadena (verdadero / falso) mientras que ng-src verá {{team.logo}} como verdadero incluso si devuelve un 404.
James Gentes
1

¿Hay alguna razón específica por la que no pueda declarar la imagen alternativa en su código?

Según tengo entendido, tiene dos casos posibles para su fuente de imagen:

  1. Establecer correctamente los elementos de información <4 = Imagen de reserva.
  2. Establecer correctamente la información == 4 = URL generada.

Creo que esto debería ser manejado por su aplicación: si la URL correcta no se puede determinar actualmente, en su lugar, pase una URL de imagen de carga / reserva / marcador de posición.

El razonamiento es que nunca tiene una imagen 'faltante', porque ha declarado explícitamente la URL correcta para mostrar en cualquier momento.

Alex Osborn
fuente
Lo siento, debería haber aclarado que incluso si los 4 valores están configurados, la URL aún puede 404 (el usuario puede agregar carpetas en el camino). Mientras tanto, he agregado una función que verifica los encabezados de respuesta de la URL cuando todos los valores se establecen. Todavía sería bueno para manejar esto sin ningún tratamiento especial, apuesto a que vendrá a través de este nuevo :)
will_hardin
1
Genial, debes mostrar eso como una respuesta y marcarlo como aceptado para cualquier persona que busque en el futuro.
Alex Osborn
1

Le sugiero que le guste usar la directiva 'declaración de if' de Angular UI Utils para resolver su problema, como se encuentra en http://angular-ui.github.io/ . Lo acabo de usar para hacer exactamente lo mismo.

Esto no se ha probado, pero podría hacer algo como:

Código del controlador:

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

(o más simple)

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

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

O incluso más simple, podría usar una propiedad de alcance:

Código del controlador:

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

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

Para una imagen de marcador de posición, simplemente agregue otra <img>etiqueta similar, pero anteponga su ui-ifparámetro con un signo de exclamación ( !) y haga que ngSrc tenga la ruta a la imagen de marcador de posición, o simplemente use una srcetiqueta según el código HTML normal.

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

Obviamente, todos los ejemplos de código anteriores suponen que cada valor1, valor2, valor3 y valor4 equivaldrán a null/ falsecuando cada una de sus 4 piezas de información esté incompleta (y, por lo tanto, también a un valor booleano de truecuando estén completas).

PD. El proyecto AngularUI se ha dividido recientemente en subproyectos, y la documentación para ui-ifparece estar ausente actualmente (aunque probablemente esté en el paquete). Sin embargo, es bastante sencillo de usar como puede ver, y he registrado un 'problema' de Github en el proyecto Angular UI para señalarlo también al equipo.

ACTUALIZACIÓN: 'ui-if' falta en el proyecto AngularUI porque se ha integrado en el código central de AngularJS. Sin embargo, solo a partir de v1.1.x, que actualmente está marcado como 'inestable'.

Matty J
fuente
ng-iflo hice en el núcleo por cierto (a partir de 1.1.5 si no me equivoco).
principio holográfico el
1

Aquí hay una solución que se me ocurrió usando JavaScript nativo. Estoy comprobando si la imagen está rota y luego agrego una clase a la imagen por si acaso y estoy cambiando la fuente.

Recibí parte de mi respuesta de una respuesta de 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';
            };
        }
    }
});
Anselm Marie
fuente
0

Se me ocurrió mi propia solución. Reemplaza la imagen tanto si src como ngSrc está vacío, y si img devuelve 404.

(tenedor de la solución @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);
        }
    }
}
});
andrfas
fuente
0

Esto permitirá que solo se repita dos veces, para verificar si ng-src no existe; de ​​lo contrario, use err-src, esto evita que el bucle continúe.

(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);
                }              
            });
            }
        }
    }
})();
bryan kim artificio
fuente