Get Image or byte data with http - javascript

For a web application I need to get my images with an ajax request because we have signature + authentication on our API, so we can't get images using a simple <img src="myapi/example/145"/>
Since we're using angular2, we obviously looked for blob or something like that, but as stated in static_response.d.ts file:
/**
* Not yet implemented
*/
blob(): any;
So okay, I can't do it for now, I have to wait for thie feature to be implemented.
But problem is I can't wait so I need a hotfix or a little hack to be able to get image data from response and I'll be able to remove my hack and set the blob() method call to be good when it will be implemented.
I tried this:
export class AppComponent {
constructor(private api:ApiService, private logger:Logger){}
title = 'Tests api';
src='http://placekitten.com/500/200'; //this is src attribute of my test image
onClick(){ //Called when I click on "test" button
this.api.test().then(res => {
console.log(res._body);
var blob = new Blob([new Uint8Array(res._body)],{
type: res.headers.get("Content-Type")
});
var urlCreator = window.URL;
this.src = urlCreator.createObjectURL(blob);
});
}
}
with ApiService.test() method:
test():Promise<any> {
return this.http.get(this._baseUrl + "myapi/example/145", this.getOptions())
//getOptions() is just creating three custom headers for
//authentication and CSRF protection using signature
.toPromise()
.then(res => {
this.logger.debug(res);
if(res.headers.get("Content-Type").startsWith("image/")){
return res;
}
return res.json();
})
.catch(res => {
this.logger.error(res);
return res.json();
} );
}
But I don't get any image from it and logging the response data shows a big string which is image data.
Do you have a hack to achieve this?

It is not necessary to extend BrowserXhr anymore. (Tested with angular 2.2.1)
RequestOptionsArgs now has a property responseType: ResponseContentType which can be set to ResponseContentType.Blob
Using DomSanitizer
import {DomSanitizer} from '#angular/platform-browser';
This example also creates a sanitized url that can be bound to the src property of an <img>
this.http.get(url, {
headers: {'Content-Type': 'image/jpg'},
responseType: ResponseContentType.Blob
})
.map(res => {
return new Blob([res._body], {
type: res.headers.get("Content-Type")
});
})
.map(blob => {
var urlCreator = window.URL;
return this.sanitizer.bypassSecurityTrustUrl(urlCreator.createObjectURL(blob));
})

Using the new Angular HttpClient is really easy to achieve this. Going off of tschuege's approach, it would be:
return this._http.get('/api/images/' + _id, {responseType: 'blob'}).map(blob => {
var urlCreator = window.URL;
return this._sanitizer.bypassSecurityTrustUrl(urlCreator.createObjectURL(blob));
})
The key is to set the responseType as 'blob' so that it doesn't attempt to parse it as JSON

I think that you missed to set the responseType on your request. Right now it's a bit tricky because it's not supported.
The workaround would be to override the BrowserXhr class to set the responseType on the xhr object itself...
You could extend the BrowserXhr:
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor() {}
build(): any {
let xhr = super.build();
xhr.responseType = 'arraybuffer';
return <any>(xhr);
}
}
and override the BrowserXhr provider with the extended class:
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);
The problem is here that you don't override for all requests. At the bootstrap level, it will override everything. So you could provide it in a sub injector within the providers attribute of the impacted component...
Here is a working plunkr: https://plnkr.co/edit/tC8xD16zwZ1UoEojebkm?p=preview.

This JSFiddle could help you:
https://jsfiddle.net/virginieLGB/yy7Zs/936/
The method is, as you wanted, creating a Blob from the URL provided
// Image returned should be an ArrayBuffer.
var xhr = new XMLHttpRequest();
xhr.open( "GET", "https://placekitten.com/500/200", true );
// Ask for the result as an ArrayBuffer.
xhr.responseType = "arraybuffer";
xhr.onload = function( e ) {
// Obtain a blob: URL for the image data.
var arrayBufferView = new Uint8Array( this.response );
var blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } );
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL( blob );
var img = document.querySelector( "#photo" );
img.src = imageUrl;
};
xhr.send();

Related

how to get response headers?

I am using angular 15.0. I get a list of items from backend (asp.net core 5) with an item appended to the header. the get method in the client side service is:
/** GET Paged commodities from the server ===============================*/
getPagedCommodities(pageSize: number, pageNumber: number): Observable<CommodityForList[]> {
let params: HttpParams = new HttpParams();
params = params.append('pageSize', pageSize);
params = params.append('pageNumber', pageNumber);
const headers = new HttpHeaders({
observe: 'response'
});
return this.http.get<CommodityForList[]>(this.baseUrl + '/getPagedCommodities/', { headers, params });
}
this method sends two parameters to the server and gets commodities list along with totalCount to paging the list. I need the totalCount to set the length property of mat-paginator and commodities list as observable to provide a dynamic search for user. therefore, in the commoditiesList component, the snippet code for this purpose is:
commoditiesForList$!: Observable<CommodityForList[]>;
this.commoditiesForList$ = this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1);
this.commoditiesForList$.subscribe( res => {
const totalCount = res.headers.get('X-Pagination');
})
but, here I have an error: Property 'headers' does not exist on type 'CommodityForList[]'.
when I change the type of commoditiesForList$ to HttpResponse<CommodityForList[]>, the error may be fixed, but receiving the commodities list as observable will have a problem. Is there a solution to get the commodities list as observable and read the totalCount separately from the header? thank you for your response.
See https://angular.io/guide/http#reading-the-full-response
You might need more information about the transaction than is contained in the response body. Sometimes servers return special headers or status codes to indicate certain conditions that are important to the application workflow.
getPagedCommodities(pageSize: number, pageNumber: number): Observable<HttpResponse<CommodityForList[]>> {
// ...
return this.http.get<CommodityForList[]>(this.baseUrl + '/getPagedCommodities/', { headers, params });
}
You can access the body in the body attribute.
See maybe also Angular: HttpClient read full response with Observable Array.
Given your example, you could use the next configuration on your HTTP request: {responseType: 'json', observe: 'events' }. See a working example here on stackblitz - one request
commoditiesForList$!: BehaviourSubject<CommodityForList[]>;
totalCount$!: BehaviourSubject<any>;
constructor(commoditiesService: CommoditiesService) {
this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1).subscribe(res => {
this.commoditiesForList$.next(res.body)
this.totalCount$.next(headers.get('X-Pagination'))
})
}
Original Answer
Given your example, you could use the next configuration on your HTTP request: {responseType: 'json', observe: 'events' }. See a working example here on stackblitz - two requests - shared pipe
Edit: to avoid making two request, notice that GET request is using share operator from rxjs. Thanks Arber to notice it.
getPagedCommodities(pageSize: number, pageNumber: number): Observable<CommodityForList[]> {
let params: HttpParams = new HttpParams();
params = params.append('pageSize', pageSize);
params = params.append('pageNumber', pageNumber);
const headers = new HttpHeaders({
observe: 'response'
});
return this.http.get<CommodityForList[]>(this.baseUrl + '/getPagedCommodities/',
{ headers, params, responseType: 'json',observe: 'events'}).pipe(share());
}
then you will access data and headers in this way
commoditiesForList$!: Observable<CommodityForList[]>;
this.commoditiesForList$ = this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1).pipe(
map((res) => (res as any).body));
this.totalCount$ = this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1).pipe(
map((res) => (res as any).headers)), map(headers => headers.get('X-Pagination')));
Docs: https://angular.io/guide/http#requesting-data-from-a-server

Share data in files from a JS class

Is there a way i can use a JS Class to populate an object with data from an API and then re-use that object from the class in different files so i have access to the same data object? For example:
I have a class that fetches and stores some data.
export default class Api {
constructor() {
// Config from DOM element
this.config = JSON.parse( $( '.config' ).attr( 'data-config' ) );
// The object we send back to the API
this.payload = {
'data': {
'type': this.config.type,
'paged': 1,
'filters': []
}
};
// API response comes in here
this.response = {};
}
get() {
const self = this;
return new Promise( ( resolve ) => {
$.ajax( {
type: 'post',
contentType: 'application/json',
url: this.config.apiUrl,
data: JSON.stringify( this.payload ),
dataType: 'json',
success: ( response ) => {
self.response = response;
resolve();
}
} );
} );
}
}
file1.js:
import Api from '../helpers/api.js';
function fetchData () {
Api.get().then( () => {
// Do stuff after data fetched from class
// Api.response has data now.
} );
}
Now in file2.js i want to access the data stored in the this.response object that file1.js fetched.
file2.js
import Api from '../helpers/api.js';
console.log( Api.response ) // empty {}
I want the this.payload object in the Api Class to be updated from any file and then do a new call to the api.
I understand why it's empty because file2.js is using a new Class Api. But is there a way i can share this data without saving it in localStorage or the window object? Maybe i should not use a class for this but i'm kinda lost here. I sort of want the same thing that Vuex does but i'm not using Vue on this project.
Thanks in advance.
You're storing data in class instance, but trying to read it from class static property
If you want to you classes, you should instantiate class to get access to instance properties you've initialized in constructor. And you should use singleton pattern to get the same instance every time you call new App()
Or just export not a class, but a simple object with the same interface.

Values from web api are not being converted

I have a web api call. The property of checkNumber is a double on the web api side , however in my typescript model I need it to come in as a string. It is staying as a number even though my model clearly has it as a string variable.
Is there a way to get the conversion to automatically happen to string?
my web api call
public GetMyClass(myModel: MyClass): Observable<MyClass> {
let headers = new HttpHeaders();
headers.append("content-type", "application/json");
headers.append("accept", "application/json");
let options = { headers: headers };
return this.httpClient.post<MyClass>( url, myModel, options)
}
my model
export MyClass{
checkNumber?: string;
}
Typescript doesn't do auto conversion. It helps with type checking during development. At runtime, it just plain javascript.
You will need to define your own conversion.
public GetMyClass(myModel: MyClass): Observable<MyClass> {
let headers = new HttpHeaders();
headers.append("content-type", "application/json");
headers.append("accept", "application/json");
let options = { headers: headers };
return this.httpClient.post<MyClass>( url, myModel, options)
.pipe(
map(dataObject => {
let checkNumber = dataObject.checkNumber
return {
checkNumber: checkNumber ? dataObject.checkNumber.toString() : undefined,
...dataObject
}
})
)
}

Angular 6 - externalizing url

I am trying to externalize the url and proerties in angular 6.
Have a service which invokes 3rd party URL to get data.
weather-component.html -> weather.component.ts -> weather.service.ts
In my weather.service.ts,
public getWeather() {
// Here I have hardoded the URL and the api key.
}
I would like to externalize this so as to make it configurable.
Not sure how to move it to a configurable file and read from there.
Exactly the same manish's answer but when we started the project in Angular 2 , this blog had been quite helpful. Over the upgrades over http gateway has changed massively and it was very useful when angular changed the httpclient in 4 .
https://blog.sstorie.com/adapting-ben-nadels-apigateway-to-pure-typescript
Also if you are looking for a place to put base url etc , I would use the environment ts file in angular-cli
https://github.com/angular/angular-cli/wiki/stories-application-environments
I suppose you want to make generic service
you can have a baseHttp.service.ts and weather.service.ts will extend the baseHttpservice to make the api calls.
baseHttp.service.ts
#Injectable()
export abstract class BaseHttpService {
private baseUrl: string = Constants.BASEURL;
protected method: string;
protected serviceUrl: string;
protected headers: Headers;
constructor(private http: Http) {
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.headers.append('Accept', 'application/json');
}
call(params: any) {
let url: string = this.setUrl();
let options = new RequestOptions({ headers: this.headers });
options.url = url;
options.method = this.method;
options.body = params;
return this.http.request(url, options).toPromise()
.then((response: any) => this.extractData(response))
.catch((error: any) => this.handleError(error));
}
//complete the other functions
}
weather.service.ts
#Injectable()
export class DashboardService extends BaseHttpService {
constructor(private _http: Http) {
super(_http);
}
getWeatherReport(params:any) {
this.serviceUrl = 'some-url';
this.method = "GET";
return super.call(params);
}
}
so you can inject weather.service.ts and override the values in weather.service.ts and make http calls
so baseHttp.service.ts acts as a interceptor, so you can intercept all the Http calls there.

Angular base64 image pipe, Maximum call stack size exceeded

I have to convert Image urls to base64 encoded strings (because of this)
import { Pipe, PipeTransform } from '#angular/core';
declare var base64;
#Pipe({
name: 'tobase64'
})
export class Tobase64Pipe implements PipeTransform {
transform(value: any, args?: any): any {
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', value, true);
xmlHTTP.responseType = 'arraybuffer';
xmlHTTP.onload = (e) => {
console.log('e',e);
var arr = new Uint8Array(e.currentTarget.response);
var raw = String.fromCharCode.apply(null, arr);
var b64 = base64.encode(raw);
var dataURL = "data:image/png;base64," + b64;
return dataURL;
};
xmlHTTP.send();
return null;
}
}
Html
<image x="0" y="0" width="500" height="500" [attr.xlink:href]="'assets/hand.jpg' | tobase64" mask="url(#hole)" />
Error:
ERROR RangeError: Maximum call stack size exceeded
It is completely okay to do HTTP requests from a pipe. In your case you will eventually do just one. Consider this: if you bind a src attribute of an img to a property that can change dynamically, you will eventually make a server call any time the property changes; it's not really a big deal whether that call is an XHR call or a simple request for an image. The only two things I do not quite understand about your pipe is following:
Why use XMLHTTPRequest rather than Angular's Http or HttpClient services?
Why does your pipe return null? It seems that even if you did not get any errors, you won't still get the result
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: 'toBase64'})
export class ImagePipe implements PipeTransform {
constructor(private http: Http) {}
transform(url: string) {
const headers = new Headers({'Content-Type': 'image/*'}); /* tell that XHR is going to receive an image as response, so it can be then converted to blob */
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:
<image x="0" y="0" width="500" height="500" [attr.xlink:href]="('assets/hand.jpg' | toBase64) | async" mask="url(#hole)" />
We use our pipe to get an observable of a base64 string, and async to insert the actual emitted string inside the src tag.
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. But in your case I assume you serve images from your own app, so this should not really be a concern.

Categories

Resources