Ma tâche consiste à faire des demandes d’image asynchrones avec des en-têtes d’authentification. J'ai des chemins d'image comme ceci:
<img src="{{file.src}}"/>
Et je dois ajouter un jeton de support à l'en-tête pour de telles demandes. La page contient de nombreuses images, les requêtes ajax ne correspondent donc pas. Je ne sais pas comment faire cela.
En supposant que vous ayez implémenté un HttpIntercepter pour ajouter l'en-tête, voici une solution qui fonctionne réellement (dans 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 ceci:
<img [src]="'/api/image/42' | secure | async" />
Les solutions précédentes étaient assez imparfaites. Je ne garantis pas que c'est parfait, mais c'est effectivement testé et qui 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 le moment où les données sont extraites du serveur. Mais il existe un autre processus asynchrone qui doit être complété après cela: l'appel à reader.readAsDataURL. Vous devez donc créer un observable que vous appellerez ensuite une fois ce processus terminé.
De plus, si vous ne mettez pas quelque chose dans l'image immédiatement, 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 insère une petite image GIF vide.
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 en cache, vous effectuez la conversion en base64 à chaque fois. J'ai créé un cache qui stocke l'image afin que les futures requêtes ne soient plus nécessaires.
Désormais, il n’existe aucun moyen de passer un appel autorisé via la balise au format HTML, les navigateurs ne fournissant pas d’API à cet effet, vous devrez donc effectuer une demande XHR. Voici une solution de contournement: récupérez l'image via XHR, convertissez-la en blob, puis convertissez blob en base64 et insérez une image dans le src de la balise. Cette solution nécessite deux canaux clairs: l'un est un canal personnalisé permettant de passer des appels XHR et l'autre, le tube intégré de Angular async
. Voici notre pipe sur mesure:
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 voici votre code HTML:
<img [src]="('https://www.w3schools.com/css/trolltunga.jpg' | image) | async" />
Nous utilisons notre tube pour obtenir l'observable d'une chaîne base64 et async
pour insérer la chaîne réellement émise dans la balise src
.
Si vous regardez à l'intérieur de l'onglet Network
, vous verrez que votre en-tête d'autorisation a été fourni lors de l'appel XHR: Une chose à ne pas oublier est CORS: votre serveur de service d’image doit être configuré de manière à accepter les appels XHR pour les images du domaine sur lequel votre application Angular est en cours d’exécution. devra fournir des URL absolues au canal personnalisé, sinon il fera lui-même des requêtes au domaine de l'application Angular.
Si vous avez déjà implémenté HttpInterceptor for api, vous pouvez simplifier le code de canal ci-dessus en laissant les en-têtes de gestion d'intercepteur. La version ci-dessous est mise à jour avec 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 l'exemple 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);
}
}
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 essaye d'abord de charger http: // localhost/null url. Pour résoudre ce problème, j'ai inclus la petite image vierge 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() { }};
});
}
}