se um caminho ngSrc for resolvido para um 404, existe uma maneira de retornar ao padrão?

129

O aplicativo que estou construindo exige que meu usuário defina 4 informações antes que esta imagem possa ser carregada. Esta imagem é a peça central do aplicativo, portanto, o link quebrado da imagem faz com que pareça que a coisa toda está funcionando. Gostaria de ter outra imagem em seu lugar no 404.

Alguma ideia? Eu gostaria de evitar escrever uma diretiva personalizada para isso.

Fiquei surpreso por não encontrar uma pergunta semelhante, especialmente quando a primeira pergunta nos documentos é a mesma!

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

will_hardin
fonte
Isso pode ser relevante: stackoverflow.com/q/13781685/1218080
holográfica-princípio
Para
saber mais

Respostas:

252

É uma diretiva bastante simples observar um erro ao carregar uma imagem e substituir o 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 você deseja exibir a imagem de erro quando o ngSrc estiver em branco, adicione-o (Plunker) :

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

O problema é que o ngSrc não atualiza o atributo src se o valor estiver em branco.

Jason Goemaat
fonte
Funciona bem se o URL estiver quebrado (404), mas se for uma string vazia, ng-src engole silenciosamente o erro.
Stephen Patten
2
Se uma sequência vazia não for válida para o seu modelo, faça com que a expressão à qual você está se ligando com ng-src não retorne uma sequência vazia.
22614 Justin Lovero
Script atualizado para uso apoio do srcatributo para a err-srcimagem: plnkr.co/edit/b05WtghBOHkxKdttZ8q5
Ryan Schumacher
@JustinLovero Considero um erro angular e o denunciei. O problema é que ngSrc não definirá o atributo src se o valor estiver em branco. Na verdade, isso pode ser um problema se, por exemplo, você estiver exibindo um telefone e carregar outro telefone com ajax sem um URL de imagem. A imagem do telefone antigo continuaria sendo exibida, mas acho que um erro ou espaço reservado seria mais apropriado. Sim, você pode lidar com isso em seu modelo, mas o comportamento de deixar uma imagem antiga é, penso, mais errado do que exibir um erro.
Jason Goemaat
@JasonGoemaat, como eu faria para substituir a imagem quebrada pelo texto?
simeg
48

Um pouco tarde para a festa, embora eu tenha encontrado uma solução para mais ou menos o mesmo problema em um sistema que estou construindo.

Minha idéia, porém, era lidar com TODAS as imgtags de imagem globalmente.

Eu não queria ter que apimentar minhas HTMLdiretrizes desnecessárias, como err-srcas mostradas aqui. Frequentemente, especialmente com imagens dinâmicas, você não saberá se está faltando até que seja tarde demais. Adicionar diretivas extras à chance de uma imagem estar faltando parece um exagero para mim.

Em vez disso, estendo a imgtag existente - que é, na verdade, o que são as diretivas Angular.

Então - é isso que eu criei.

Nota : Isso requer que a biblioteca JQuery completa esteja presente e não apenas os JQlite Angular enviados porque estamos usando.error()

Você pode vê-lo em ação neste Plunker

A diretiva é mais ou menos assim:

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 ocorre um erro (o que ocorre porque a imagem não existe ou é inacessível etc.), capturamos e reagimos. Você pode tentar obter os tamanhos de imagem também - se eles estivessem presentes na imagem / estilo em primeiro lugar. Caso contrário, defina-se como padrão.

Este exemplo está usando placehold.it para que uma imagem seja exibida.

Agora, TODAS as imagens, independentemente do uso srcou ng-srcse cobrem para o caso de nada carregar ...

Darren Wainwright
fonte
2
solução muito boa! vamos votar aqui no topo!
Matthijs
1
Isso é muito bonito, tenho que dizer. Na verdade, não quero que nenhuma imagem apareça se ela não resolver, então usei: element.css ("display", "none"); para ocultá-lo totalmente #
1145 Pompeu
1
nice :) ou você pode simplesmente removê-lo do DOM completamente, com element.remove (); :)
Darren Wainwright
Funciona muito bem, obrigado!
ecorvo 21/01
Editou um pouco sua solução, por isso funciona também quando o caminho está vazio. stackoverflow.com/a/35884288/2014288
andrfas
21

Para expandir a solução Jason para capturar os dois casos de erro de carregamento ou de uma sequência de código-fonte vazia, podemos apenas adicionar um relógio.

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
fonte
2
Código agradável, mas parece um exagero para adicionar um relógio para o que pode ser facilmente verificado com ng-ifno modelo (a string src vazio)
alonisser
Parece que você pode substituir o ngSrc por sua própria diretiva e configurar a ligação ao erro para usar o errSrc em vez de ter uma diretiva errSrc separada, se é isso que você deseja. Você poderia usar attrs.$observe('ngSrc', function(value) {...});, é isso que o ngSrc usa internamente em vez de $ watch #
Jason Goemaat
Todo o problema com espaços em branco é que o ngSrc não atualiza o atributo src se o valor estiver em branco; portanto, se você tivesse sua própria diretiva substituindo o ngSrc, não seria necessário um cheque em branco.
21814 Jason Goemaat
1
@ Pokokono - você pode verificar dentro da função de erro se o atributo 'src' atual é o mesmo que 'errSrc'.
precisa saber é o seguinte
1
@BiswasKhayargoli - acrescentou uma chamada para cancelar a assinatura, por isso não terá qualquer custo de processamento após o carregamento 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;
            });
        }
    };
});

em HTML:

<img class="img-circle" check-image ng-src="{{item.profileImg}}" />
Mehul
fonte
Eu sinto que este deve ser a resposta aceite uma vez que ele não usa jQuery e resolve o problema
Gregra
10

Se a imagem for 404 ou a imagem for nula vazia, seja o que for, não há necessidade de diretivas, você pode simplesmente usar o filtro ng-src como este :)

<img ng-src="{{ p.image || 'img/no-image.png' }}" />
NeoNe
fonte
2
Não, se p.image retornar um 404 em uma chamada AJAX, ele tratará isso como 'true' e não passará para o segundo img / no-image.png.
James Gentes
2
@ JamesGentes - Esse não parece ser o caso. Esse uso do ng-src funciona exatamente para mim, como mostrado. E é muito mais simples.
Shanemgrey
@ Shaneveeg obrigado, isso é interessante - Gostaria de saber se o back-end é o que é diferente. Estou executando o Node com Express, talvez ele lide com isso de maneira diferente.
James Gentes
2
@ JamesGentes - Correção do meu comentário anterior. O OP está procurando uma solução quando houver um URI válido retornado pelo modelo, mas um 404 quando for seguido. Nesse caso, esta solução não funciona, resulta em imagem quebrada. Se angular não retornar nada para o valor da imagem, isso escolherá corretamente o fallback.
Shanemgrey
Estou usando o Laravel e funciona perfeito para mim se 404, nulo ou vazio for o caso. mas talvez expresso retorna algum tipo de erro em vez de resposta código como 404 então sim, provavelmente, depende da estrutura de servidor
neone
8

Eu uso algo assim, mas assume que team.logo é válido. Força o padrão se "team.logo" não estiver definido ou estiver vazio.

<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
fonte
2
Isso é correto, uma vez que avalia "team.logo" como uma string (true / false), enquanto ng-src verá {{team.logo}} como verdadeiro mesmo se ele retorna um 404.
James Gentes
1

Existe um motivo específico para você não poder declarar a imagem de fallback no seu código?

Pelo que entendi, você tem dois casos possíveis para sua fonte de imagem:

  1. Defina corretamente as informações <4 = Imagem de fallback.
  2. Defina corretamente as informações == 4 = URL gerado.

Acho que isso deve ser tratado pelo seu aplicativo. Se o URL correto não puder ser determinado no momento, transmita um URL de imagem de carregamento / fallback / espaço reservado.

O raciocínio é que você nunca tem uma imagem 'ausente', porque declarou explicitamente o URL correto a ser exibido a qualquer momento.

Alex Osborn
fonte
Desculpe, eu deveria ter esclarecido que, mesmo que todos os quatro valores estejam definidos, o URL ainda pode 404 (o usuário pode adicionar pastas ao longo do caminho.) Enquanto isso, adicionei uma função que verifica os cabeçalhos de resposta do URL quando todos os valores estão prontos. Ele ainda seria bom para lidar com isso sem tratamento especial, aposto que vai se deparar com isso novamente :)
will_hardin
1
Legal, você deve mostrar isso como resposta e marcá-lo como aceito para qualquer pessoa que pesquisar no futuro.
Alex Osborn
1

Sugiro que você queira usar a diretiva 'if statement' do Angular UI Utils para resolver seu problema, conforme encontrado em http://angular-ui.github.io/ . Acabei de usá-lo para fazer exatamente a mesma coisa.

Isso não foi testado, mas você pode fazer algo como:

Código do controlador:

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

(ou mais simples)

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

HTML no modo de exibição: <img ui-if="showImage()" ng-src="images/{{data.value}}.jpg" />

Ou ainda mais simples, você pode simplesmente usar uma propriedade de escopo:

Código do controlador:

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

HTML no modo de exibição: <img ui-if="showImage" ng-src="images/{{data.value}}.jpg" />

Para uma imagem de espaço reservado, basta adicionar outra <img>marca semelhante, mas acrescente seu ui-ifparâmetro com uma !marca de exclamação ( ) e faça com que ngSrc tenha o caminho para a imagem de espaço reservado ou use uma srcmarca conforme o HTML normal.

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

Obviamente, todos os exemplos de código acima estão assumindo que cada um dos valores1, valor2, valor3 e valor4 será igual a null/ falsequando cada uma das suas quatro informações estiver incompleta (e, portanto, também com um valor booleano de truequando estiverem completas).

PS. O projeto AngularUI foi recentemente dividido em subprojetos, e a documentação para ui-ifparece estar faltando atualmente (provavelmente está no pacote em algum lugar). No entanto, é bastante simples de usar, como você pode ver, e registrei um 'problema' do Github no projeto Angular UI para apontar para a equipe também.

UPDATE: 'ui-if' está ausente no projeto AngularUI porque foi integrado ao código principal do AngularJS! Apenas a partir da v1.1.x, atualmente marcada como 'instável'.

Matty J
fonte
ng-ifchegou ao núcleo btw (a partir de 1.1.5, se não me engano).
princípio holográfico
1

Aqui está uma solução que eu criei usando javascript nativo. Estou verificando se a imagem está quebrada e, em seguida, adicionando uma classe à imagem e alterando a fonte.

Eu recebi parte da minha resposta em uma resposta do 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
fonte
0

Veio com minha própria solução. Ele substitui a imagem se src ou ngSrc estiver vazio e se img retornar 404.

(garfo da solução @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
fonte
0

Isso permitirá apenas fazer loop duas vezes, para verificar se o ng-src não existe ou usar o err-src, isso evita que o loop 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
fonte