6 votes

Image asynchrone Angular 4 avec en-têtes porteurs

Ma tâche consiste à faire des demandes d'images asynchrones avec des en-têtes d'authentification. J'ai des chemins d'images comme ceci :

<img src="{{file.src}}"/>

Et j'ai besoin d'ajouter Bearer Token à l'en-tête pour de telles requêtes. La page contient beaucoup d'images, donc les requêtes ajax ne sont pas adaptées. Je ne sais pas comment faire.

7voto

Charles Points 295

En supposant que vous ayez implémenté un HttpIntercepter pour ajouter l'en-tête, voici une solution qui fonctionne réellement (en Angular 4) :

import { Pipe, PipeTransform } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Pipe({
  name: 'secure'
})
export class SecurePipe implements PipeTransform {

  constructor(private http: HttpClient) { }

  transform(url: string) {

    return new Observable<string>((observer) => {
      // This is a tiny blank image
      observer.next('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');

      // The next and error callbacks from the observer
      const {next, error} = observer;

      this.http.get(url, {responseType: 'blob'}).subscribe(response => {
        const reader = new FileReader();
        reader.readAsDataURL(response);
        reader.onloadend = function() {
          observer.next(reader.result);
        };
      });

      return {unsubscribe() {  }};
    });
  }
}

Vous l'utilisez comme ça :

<img [src]="'/api/image/42' | secure | async" />

Les solutions précédentes étaient très imparfaites. Je ne garantis pas que cette solution soit parfaite, mais elle a été testée et fonctionne pour moi.

Vous ne pouvez pas retourner l'observable que vous obtenez de http.get ! Je ne sais pas pourquoi les solutions précédentes supposent que vous le pouvez. L'observable pour http.get indique quand les données sont récupérées du serveur. Mais, il y a un autre processus asynchrone qui doit être complété après cela : l'appel à reader.readAsDataURL. Vous devez donc créer un Observable sur lequel vous appellerez next après la fin de ce processus.

De plus, si vous ne mettez pas immédiatement quelque chose dans l'image, le navigateur essaiera toujours de charger l'image en utilisant HTTP et vous obtiendrez une erreur dans votre console JavaScript. C'est la raison du premier appel à observer.next qui introduit une image GIF vide et minuscule.

Un problème avec cette approche est que si l'image est utilisée plus d'une fois, elle sera chargée à chaque fois. Même si le navigateur met l'image en cache, vous devez effectuer la conversion en base64 à chaque fois. J'ai créé un cache qui stocke l'image de sorte que les futures requêtes ne soient pas nécessaires après la première.

4voto

Armen Vardanyan Points 2123

Maintenant, il n'y a aucun moyen de faire un appel autorisé juste via la balise en html, les navigateurs ne fournissent pas d'API pour cela, vous devrez donc faire une requête XHR. Voici une solution de contournement : récupérer l'image via XHR, la convertir en blob, puis convertir le blob en base64 et insérer l'image dans le src du tag. Cette solution nécessitera deux tuyaux pour être claire : l'un est un tuyau personnalisé pour faire des appels XHR et l'autre est le tuyau intégré d'Angular async . Voici notre tuyau personnalisé :

import { Pipe, PipeTransform } from '@angular/core';
import { Http, RequestOptions, Headers, ResponseContentType } from @angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';

@Pipe({name: 'image'})
export class ImagePipe implements PipeTransform {
  constructor(private http: Http) {}

  transform(url: string) {
  const headers = new Headers({'Authorization': 'MY TOKEN', 'Content-Type': 'image/*'}); /* tell that XHR is going to receive an image as response, so it can be then converted to blob, and also provide your token in a way that your server expects */
  return this.http.get(url, new RequestOptions({headers: headers, responseType: ResponseContentType.Blob})) // specify that response should be treated as blob data
  .map(response => response.blob()) // take the blob
  .switchMap(blob => {
  // return new observable which emits a base64 string when blob is converted to base64
      return Observable.create(observer => { 
        const  reader = new FileReader(); 
        reader.readAsDataURL(blob); // convert blob to base64
        reader.onloadend = function() {             
              observer.next(reader.result); // emit the base64 string result
        }
      });
    });
  }
}

Et voilà votre html :

<img [src]="('https://www.w3schools.com/css/trolltunga.jpg' | image) | async" />

Nous utilisons notre pipe pour obtenir un observable d'une chaîne de caractères base64, et async pour insérer la chaîne émise à l'intérieur de l'élément src étiquette.

Si vous regardez à l'intérieur de la Network vous verrez que votre en-tête d'autorisation a été fourni pendant l'appel XHR : enter image description here Une chose que vous devez garder à l'esprit est CORS : votre serveur de service d'images doit être configuré de manière à accepter les appels XHR pour les images du domaine sur lequel votre application Angular est exécutée. Vous devrez également fournir des urls absolues au tuyau personnalisé, sinon il effectuera des requêtes vers le domaine de l'application Angular elle-même.

0voto

Ilkka Nisula Points 139

Si vous avez déjà implémenté HttpInterceptor pour l'api, vous pouvez simplifier le code Pipe ci-dessus en laissant l'intercepteur gérer les en-têtes. Voici une version mise à jour utilisant HttpClient.

@Pipe({
  name: 'image',
})
export class ImagePipe implements PipeTransform {
  constructor(
    private http: HttpClient,
    private config: ConfigProvider
  ) { }

  transform(url: string) {
    return this.http.get(url, {responseType: "blob"})
      .switchMap(blob => {
        // return new observable which emits a base64 string when blob is converted to base64
        return Observable.create(observer => {
          const reader = new FileReader();
          reader.readAsDataURL(blob); // convert blob to base64
          reader.onloadend = function () {
            observer.next(reader.result); // emit the base64 string result
          }
        });
      });
  }
}
`

Et voici un exemple intercepteur :

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(private config: ConfigProvider) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

      const authReq = req.clone({
        setHeaders: {
          Authorization: this.config.getAuthorization(),
          'X-App-ClientId': this.config.authentication['X-App-ClientId']
        }
      });
      return next.handle(authReq);
    }
}

0voto

Vil Points 185

Cette solution pour Angular 5 et un mélange de solutions de Armen Vardanyan et Charles. La solution d'Armen fonctionne pour Angular 5, mais tente d'abord de charger http://localhost/null url. Pour résoudre ce problème, j'ai inclus la petite image vide de Charles :

@Pipe({name: 'secure'})
export class SecurePipe implements PipeTransform {
  constructor(private http: Http,
    public g: BaseGroupService) {}

    transform(url: string) {

      return new Observable<string>((observer) => {
        observer.next('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
        const {next, error} = observer;

        const headers = new Headers({'Authorization': 'TOKEN', 'Content-Type': 'image/*'}); 
        this.http.get(url, new RequestOptions({headers: headers, responseType: ResponseContentType.Blob})).subscribe(response => {
          const reader = new FileReader();
          reader.readAsDataURL(response.blob());
          reader.onloadend = function() {
            observer.next(reader.result);
          };
        });
        return {unsubscribe() {  }};
      });
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X