si un chemin ngSrc se résout en 404, y a-t-il un moyen de revenir à une valeur par défaut?

129

L'application que je suis en train de créer nécessite que mon utilisateur définisse 4 informations avant même que cette image ait une chance de se charger. Cette image est la pièce maîtresse de l'application, donc le lien d'image rompu donne l'impression que tout est bouché. J'aimerais qu'une autre image prenne sa place sur une 404.

Des idées? Je voudrais éviter d'écrire une directive personnalisée pour cela.

J'ai été surpris de ne pas pouvoir trouver une question similaire, surtout lorsque la première question dans la documentation est la même!

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

will_hardin
la source
Cela pourrait être pertinent: stackoverflow.com/q/13781685/1218080
principe holographique

Réponses:

252

C'est une directive assez simple pour surveiller une erreur lors du chargement d'une image et pour remplacer le 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 vous souhaitez afficher l'image d'erreur lorsque ngSrc est vide, vous pouvez ajouter ceci (Plunker) :

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

Le problème est que ngSrc ne met pas à jour l'attribut src si la valeur est vide.

Jason Goemaat
la source
Fonctionne bien si l'url est cassée (404), mais si c'est une chaîne vide, ng-src avale silencieusement l'erreur.
Stephen Patten
2
Si une chaîne vide n'est pas valide pour votre modèle, faites en sorte que l'expression à laquelle vous liez avec ng-src ne renvoie pas une chaîne vide.
Justin Lovero
Script mis à jour pour prendre en charge l'utilisation de l' srcattribut pour l' err-srcimage: plnkr.co/edit/b05WtghBOHkxKdttZ8q5
Ryan Schumacher
@JustinLovero Je considère que c'est un bug angulaire et je l'ai signalé. Le problème est que ngSrc ne définira pas l'attribut src si la valeur est vide. Cela pourrait en fait être un problème si, par exemple, vous affichez un téléphone et chargez un autre téléphone avec ajax qui n'a pas d'url d'image. L'image de l'ancien téléphone continuerait d'être affichée, mais je pense qu'une erreur ou un espace réservé serait plus approprié. Oui, vous pouvez le gérer dans votre modèle, mais le comportement de laisser une ancienne image est, je pense, plus faux que d'afficher une erreur.
Jason Goemaat
@JasonGoemaat, comment dois-je faire pour remplacer l'image cassée par du texte?
simeg
48

Un peu tard à la fête, même si j'ai trouvé une solution à plus ou moins le même problème dans un système que je construis.

Mon idée était, cependant, de gérer CHAQUE imgbalise d' image à l'échelle mondiale.

Je ne voulais pas avoir à poivrer mon HTMLavec des directives inutiles, comme err-srccelles montrées ici. Très souvent, en particulier avec des images dynamiques, vous ne saurez pas s'il manque jusqu'à ce qu'il soit trop tard. Ajouter des directives supplémentaires au cas où une image serait manquante me semble exagéré.

Au lieu de cela, j'étends la imgbalise existante - ce qui, vraiment, est ce que sont les directives angulaires.

Donc - c'est ce que j'ai trouvé.

Remarque : Cela nécessite que la bibliothèque JQuery complète soit présente et pas seulement le JQlite Angular livré avec car nous utilisons.error()

Vous pouvez le voir en action sur ce Plunker

La directive ressemble à peu près à ceci:

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

Lorsqu'une erreur se produit (qui sera parce que l'image n'existe pas ou est inaccessible, etc.) nous captons et réagissons. Vous pouvez également essayer d'obtenir les tailles d'image - si elles étaient présentes sur l'image / le style en premier lieu. Sinon, définissez-vous une valeur par défaut.

Cet exemple utilise placehold.it pour afficher une image à la place.

Maintenant, CHAQUE image, indépendamment de l'utilisation srcou ng-srcse couvre au cas où rien ne se charge ...

Darren Wainwright
la source
2
très belle solution! votons celui-ci au sommet!
Matthijs
1
C'est assez beau je dois dire. En fait, je ne veux pas du tout d'afficher une image si elle ne résout pas, alors j'ai utilisé: element.css ("display", "none"); pour le cacher totalement
Pompey
1
nice :) ou vous pouvez simplement le supprimer complètement du DOM, avec element.remove (); :)
Darren Wainwright
Fonctionne très bien, merci!
ecorvo
Edité un peu votre solution, donc ça marche aussi quand le chemin est vide. stackoverflow.com/a/35884288/2014288
andrfas
21

Pour étendre la solution Jason pour attraper les deux cas d'erreur de chargement ou de chaîne source vide, nous pouvons simplement ajouter une montre.

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

    }
  }
});
user2899845
la source
2
Bon code, mais semble exagéré d'ajouter une montre pour ce qui peut être facilement vérifié ng-ifdans le modèle (la chaîne src vide)
alonisser
On dirait que vous pouvez simplement remplacer ngSrc par votre propre directive et lui faire configurer la liaison en cas d'erreur pour utiliser errSrc au lieu d'avoir une directive errSrc séparée si c'est ce que vous voulez. Vous pouvez utiliser attrs.$observe('ngSrc', function(value) {...});, c'est ce que ngSrc utilise en interne au lieu de $ watch
Jason Goemaat
Le problème avec les blancs est que ngSrc ne met pas à jour l'attribut src si la valeur est vide, donc si vous aviez votre propre directive remplaçant ngSrc, il n'aurait pas besoin d'une vérification vide.
Jason Goemaat
1
@Pokono - vous pouvez vérifier à l'intérieur de la fonction d'erreur si l'attribut 'src' actuel est le même que 'errSrc'.
user2899845
1
@BiswasKhayargoli - a ajouté un appel pour se désabonner, il n'aura donc aucun coût de traitement après le chargement initial
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
la source
Je pense que cela devrait être la réponse acceptée car elle n'utilise pas jQuery et résout le problème
Gregra
10

Si l'image vaut 404 ou si l'image est nulle vide quoi qu'il en soit, vous n'avez pas besoin de directives, vous pouvez simplement utiliser le filtre ng-src comme ceci :)

<img ng-src="{{ p.image || 'img/no-image.png' }}" />
NeoNe
la source
2
Non, si p.image renvoie un 404 sur un appel AJAX, il le traitera comme «vrai» et ne passera pas au deuxième img / no-image.png.
James Gentes
2
@JamesGentes - Cela ne semble pas être le cas. Cette utilisation de ng-src fonctionne pour moi exactement comme indiqué. Et c'est tellement plus simple.
shanemgrey
@shaneveeg merci, c'est intéressant - je me demande si le back-end est ce qui est différent. J'utilise Node avec Express, peut-être qu'il le gère différemment.
James Gentes
2
@JamesGentes - Correction de mon commentaire précédent. L'OP recherche une solution lorsqu'il y a un URI valide renvoyé par le modèle, mais un 404 lorsqu'il est suivi. Dans ce cas, cette solution ne fonctionne pas, il en résulte une image cassée. Si angular ne renvoie rien pour la valeur de l'image, cela choisit correctement le repli.
shanemgrey
J'utilise Laravel et cela fonctionne parfaitement pour moi si 404, nul ou vide est le cas. mais peut-être qu'Express renvoie une sorte d'erreur au lieu d'une réponse de code comme 404, donc oui dépend probablement du cadre du serveur
NeoNe
8

J'utilise quelque chose comme ça, mais cela suppose que team.logo est valide. Il force la valeur par défaut si "team.logo" n'est pas défini ou est vide.

<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
la source
2
C'est correct, car il évalue "team.logo" comme une chaîne (vrai / faux) alors que ng-src verra {{team.logo}} comme vrai même s'il renvoie un 404.
James Gentes
1

Existe-t-il une raison spécifique pour laquelle vous ne pouvez pas déclarer l'image de secours dans votre code?

Si je comprends bien, vous avez deux cas possibles pour votre source d'image:

  1. Définir correctement les informations <4 = Image de repli.
  2. Définir correctement les informations == 4 = URL générée.

Je pense que cela devrait être géré par votre application - si l'URL correcte ne peut pas être déterminée actuellement, passez plutôt une URL d'image de chargement / de secours / d'espace réservé.

Le raisonnement est que vous n'avez jamais d'image «manquante», car vous avez explicitement déclaré l'URL correcte à afficher à tout moment.

Alex Osborn
la source
Désolé, j'aurais dû préciser que même si les 4 valeurs sont définies, l'URL peut toujours 404 (l'utilisateur peut ajouter des dossiers en cours de route.) En attendant, j'ai ajouté une fonction qui vérifie les en-têtes de réponse de l'URL lorsque toutes les valeurs sont fixés. Ce serait toujours bien de gérer cela sans traitement spécial, je parie que je vais le revoir :)
will_hardin
1
Cool, vous devriez montrer cela comme une réponse et la marquer comme acceptée pour tous ceux qui recherchent à l'avenir.
Alex Osborn
1

Je suggère que vous aimeriez peut-être utiliser la directive «if statement» Angular UI Utils pour résoudre votre problème, comme indiqué sur http://angular-ui.github.io/ . Je viens de l'utiliser pour faire exactement la même chose.

Ceci n'est pas testé, mais vous pouvez faire quelque chose comme:

Code contrôleur:

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

(ou plus simple)

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

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

Ou encore plus simple, vous pouvez simplement utiliser une propriété scope:

Code contrôleur:

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

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

Pour une image d'espace réservé, ajoutez simplement une autre <img>balise similaire, mais ajoutez votre ui-ifparamètre avec un point d'exclamation ( !), et faites soit que ngSrc ait le chemin vers l'image d'espace réservé, soit utilisez simplement une srcbalise selon le vieux HTML normal.

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

De toute évidence, tous les exemples de code ci-dessus supposent que chacune des valeurs valeur1, valeur2, valeur3 et valeur4 équivaudra à null/ falsequand chacune de vos 4 informations est incomplète (et donc aussi à une valeur booléenne de truequand elles sont complètes).

PS. Le projet AngularUI a récemment été divisé en sous-projets, et la documentation pour ui-ifsemble manquer actuellement (elle est probablement dans le package quelque part cependant). Cependant, il est assez simple à utiliser comme vous pouvez le voir, et j'ai enregistré un `` problème '' de Github sur le projet Angular UI pour le signaler également à l'équipe.

MISE À JOUR: 'ui-if' est absent du projet AngularUI car il a été intégré au code AngularJS de base! Seulement à partir de la v1.1.x, qui est actuellement marquée comme «instable».

Matty J
la source
ng-ifen a fait le noyau btw (à partir de 1.1.5 si je ne me trompe pas).
principe holographique
1

Voici une solution que j'ai trouvée en utilisant javascript natif. Je vérifie si l'image est cassée, puis j'ajoute une classe à l'image au cas où et en changeant la source.

J'ai obtenu une partie de ma réponse d'une réponse 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
la source
0

Je suis venu avec ma propre solution. Il remplace l'image à la fois si src ou ngSrc est vide et si img renvoie 404.

(fourchette de la solution @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
la source
0

Cela ne permettra que de faire une boucle deux fois, pour vérifier si le ng-src n'existe pas sinon utilisez le err-src, cela empêche la boucle continue.

(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
la source