问题
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.
回答1:
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.
回答2:
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:
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.
回答3:
If you have already implemented HttpInterceptor for api you can simplify above Pipe code by letting interceptor handle headers. Below is updated version using 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
}
});
});
}
}
`
And here is example interceptor:
@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);
}
}
回答4:
This solution for Angular 5 and a mix of solutions from Armen Vardanyan and Charles. Armen's solution works for Angular 5, but first tries to load http://localhost/null url. To solve it I included Charles' tiny blank image:
@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() { }};
});
}
}
来源:https://stackoverflow.com/questions/46563607/angular-4-image-async-with-bearer-headers