Асинхронное изображение Angular 4 с заголовками носителя

Моя задача — делать асинхронные запросы изображений с заголовками авторизации. У меня есть такие пути к изображениям:

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

И мне нужно добавить токен носителя в заголовок для таких запросов. Страница содержит много изображений, поэтому ajax-запросы не подходят. Понятия не имею, как это сделать.


person Katya Makeeva    schedule 04.10.2017    source источник
comment
Как вы думаете, почему XHR-ы не подходят для этого?   -  person Ján Halaša    schedule 04.10.2017
comment
@JánHalaša - что ты имеешь в виду? Я подумал, может быть, в Angular 4 есть что-то по умолчанию для этой проблемы, я имею в виду вещи для проекта Resful API.   -  person Katya Makeeva    schedule 04.10.2017


Ответы (4)


Предполагая, что вы реализовали HttpIntercepter для добавления заголовка, вот решение, которое действительно работает (в 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() {  }};
    });
  }
}

Вы используете это так:

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

Предыдущие решения имели серьезные недостатки. Я не гарантирую, что это идеально, но на самом деле это проверено и работает для меня.

Вы не можете вернуть наблюдаемое, полученное с http.get! Я не знаю, почему предыдущие решения предполагают, что вы можете. Наблюдаемый для http.get указывает, когда данные извлекаются с сервера. Но после этого необходимо выполнить еще один асинхронный процесс: вызов reader.readAsDataURL. Поэтому вам нужно создать Observable, который вы будете вызывать после завершения этого процесса.

Кроме того, если вы не поместите что-то в изображение сразу, браузер все равно попытается загрузить изображение с помощью HTTP, и вы получите сообщение об ошибке в консоли JavaScript. Это причина первого вызова Observer.next, который помещает пустое крошечное изображение GIF.

Проблема с этим подходом заключается в том, что если изображение используется более одного раза, оно будет загружаться каждый раз. Даже если браузер кеширует, вы каждый раз выполняете преобразование в base64. Я создал кеш, в котором хранится изображение, чтобы последующие запросы не требовались после первого.

person Charles    schedule 05.03.2018
comment
Я столкнулся с той же проблемой. Изображение загружается каждый раз. Не могли бы вы поделиться своим решением, чтобы избежать этого? Спасибо - person SeaBiscuit; 18.04.2020

Теперь нет возможности сделать авторизованный вызов только через тег в html, браузеры не предоставляют API для этого, поэтому вам придется сделать запрос XHR. Вот обходной путь: получить изображение через XHR, преобразовать его в blob, затем преобразовать blob в base64 и вставить изображение в src тега. Для этого решения потребуются два канала: один — пользовательский канал для вызовов XHR, а другой — встроенный канал Angular async. Вот наша нестандартная труба:

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

И вот ваш html:

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

Мы используем наш конвейер, чтобы получить наблюдаемую строку base64, и async, чтобы вставить фактическую испускаемую строку внутри тега src.

Если вы посмотрите на вкладку Network, вы увидите, что ваш заголовок авторизации был предоставлен во время вызова XHR: < img src="https://i.stack.imgur.com/NY4fT.png" alt="введите описание изображения здесь"> Одна вещь, которую вы должны иметь в виду, это CORS: ваш сервер обслуживания изображений должен быть настроен таким образом, что он принимает вызовы XHR для изображений из домена, на котором работает ваше приложение Angular, вам также придется указать абсолютные URL-адреса для пользовательского канала, иначе он будет делать запросы к самому домену приложения Angular.

person Armen Vardanyan    schedule 04.10.2017
comment
Это не работает в Angular 4. Должно быть, используется устаревшая версия HttpClient? - person Charles; 05.03.2018
comment
Он использует Http, а не HttpClient, да, он устарел. вам нужно указать некоторые вещи при использовании HttpClient. - person Armen Vardanyan; 12.03.2018
comment
@FanJin Я так не думаю. Видео - это другой формат, и я не думаю, что вы можете просто использовать base64 для видео. Кроме того, даже если бы вы могли, видео обычно загружаются и отображаются фрагментами, поэтому, если вы попытаетесь загрузить, а затем преобразовать видео в такой формат, вы получите ужасный пользовательский опыт. - person Armen Vardanyan; 08.06.2020
comment
например, см. blog.strongbrew.io/safe-image-requests- угловой - person Cédric Françoys; 01.11.2020

Если вы уже реализовали HttpInterceptor для API, вы можете упростить приведенный выше код Pipe, позволив перехватчику обрабатывать заголовки. Ниже представлена ​​обновленная версия с использованием 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
          }
        });
      });
  }
}
`

А вот пример перехватчика:

@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);
    }
}
person Ilkka Nisula    schedule 22.01.2018
comment
Это не работает. Должно быть, используется устаревшая версия HttpClient, поскольку похоже, что switchMap больше не существует или, по крайней мере, не компилируется как есть. И он не распознает, что здесь есть два асинхронных процесса подряд: http.get и readAsDataURL. - person Charles; 05.03.2018
comment
@Charles, просто чтобы вы знали, что switchMap стал оператором rxjs, который необходимо использовать внутри функции канала: learnrxjs.io/operators/transformation/switchmap.html - person Antoine Rucquoy; 26.12.2019

Это решение для Angular 5 и смесь решений от Армена Варданяна и Чарльза. Решение Армена работает для Angular 5, но сначала пытается загрузить URL-адрес http://localhost/null. Чтобы решить эту проблему, я включил крошечное пустое изображение Чарльза:

@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() {  }};
      });
    }
}
person Vil    schedule 11.07.2018