Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 image async with bearer headers

My task is to make async image requests with auth headers. I have image paths like this:

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

And I need to Add Bearer Token to header for such requests. Page contains many images, so ajax requests are don't fit. Have no idea how to do this.

like image 886
Katya Makeeva Avatar asked Oct 04 '17 11:10

Katya Makeeva


2 Answers

Now, there is no way to make an Authorized call just via the tag in html, browsers do not provide an API for this, so you will have to make an XHR request. Here is a workaround: get the image via XHR, convert it to blob, then convert blob to base64 and insert image to the src of the tag. This solution will require two pipes to be clear: one is a custom pipe for making XHR calls and the other is the Angular's built-in pipe async. Here is our custom pipe:

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

And here goes your html:

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

We use our pipe to get an observable of a base64 string, and async to insert the actual emitted string inside the src tag.

If you look inside the Network tab you will see that your Authorization header was provided during the XHR call: enter image description here One thing you need to keep in mind is CORS: your image serving server should be configured in a way that it accepts XHR calls for images from the domain your Angular app is running on, also, you will have to provide absolute urls to the custom pipe, otherwise it will make requests to the Angular app's domain itself.

like image 55
Armen Vardanyan Avatar answered Oct 26 '22 18:10

Armen Vardanyan


Assuming you have implemented an HttpIntercepter to add the header, here's a solution that actually does work (in 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() {  }};
    });
  }
}

You use it like this:

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

The previous solutions were pretty drastically flawed. I don't guarantee that this is perfect, but it is actually tested and working for me.

You can't return the observable you get from http.get! I don't know why the previous solutions assume you can. The observable for http.get indicates when the data is retrieved from the server. But, there is another asynchronous process that has to be completed after that: the call to reader.readAsDataURL. So you need to create an Observable that you will call next on after that process completes.

Also, if you don't put something into the image immediately, the browser will still try to load the image using HTTP and you get an error in your JavaScript console. That's the reason for the first observer.next call that puts in an empty, tiny GIF image.

An issue with this approach is that if the image is used more than once it will load each one every time. Even if the browser caches, you do the conversion to base64 every single time. I created a cache that stores the image so that future queries are not needed after the first.

like image 29
Charles Avatar answered Oct 26 '22 18:10

Charles