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.
Related
Hi I am developing a web app which makes frequent get calls to a remote API. The response from the calls will always be the same so to speed up the performance of the site, I want to save the response in a JSON file locally so that the get requests are only called whenever the json file is empty.
You can not save JSON data to local file and load data because of security reason.
You can use localStorage or sessionStorage to save JSON object.
#Injectable()
export class YourService{
constructor(private http: HttpClient){
}
getData(){
var cached = JSON.parse(localStorage.setItem('yourkey'));
if(cached == null || cached == undefined){
this.http.get('yoururl').subscribe(data=>{
localStorage.setItem('yourkey', JSON.stringify(data);
return data;
});
}else{
return cached;
}
}
}
I have written a library called ngx-rxcache which is exactly for this sort of thing.
https://github.com/adriandavidbrand/ngx-rxcache
I have written an article on it here https://medium.com/#adrianbrand/angular-state-management-with-rxcache-468a865fc3fb
#Injectable()
export class DataService{
get data$(): Observable<DataType> {
return this.dataCache.value$;
}
private dataCache = this.cache.get<DataType>({
id: 'some unique identifer',
construct: () => this.http.get<DataType>('/dataUrl'),
autoload: true
});
constructor(private http: HttpClient, private cache: RxCacheService) {}
}
and in a component use the data$ observable
data$ = this.dataService.data$;
and in the template use the async pipe.
{{ data$ | async }}
How are you doing?
I'm trying to render a value acquired from http in app.component.ts to the HTML but it isn't working.
The value appears on console.log as a string but when I try to use it with this.date = dateObj it returns undefined.
When I access it from inside parseString() it doesn't work, but before it, it works. See on example for date = 'today'.
The HTML renders 'today' but not the value of dateObj. The value of dateObj is a string, just to remember.
HTML
<h2>{{date}}</h2>
<input type="submit" value="test" (click)="onClickMe()"/>
APP.COMPONENT.TS
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { HttpErrorResponse } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
import { parseString } from 'xml2js';
#Component({
'...'
})
export class AppComponent implements OnInit {
title = 'testing';
date: string;
constructor() {}
ngOnInit(): void{}
onClickMe(){
** removed for brevity. HTTP REQUEST code **
//retrieve http to data
this.http.post(url, body, {
headers: new HttpHeaders()
.set('Content-Type', 'text/xml')
.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS')
.append('Access-Control-Allow-Origin', '*')
.append('Access-Control-Allow-Headers', "Access-Control-Allow-Headers, Access-Control-Allow-Origin, Access-Control-Request-Method")
, responseType:'text'}).subscribe(data => {
// Read the result field from the response in XML and transform to JSON.
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(data,"text/xml");
//today appears in HTML
this.date = 'today';
parseString(data, function (err, result) {
var stringified = JSON.stringify(result);
console.log(stringified);
let jsonObject = JSON.parse(stringified);
let dateObj = jsonObject['date'];
//dateObj works fine on console.log but doesn't appear in HTML when rendering with {{date}}
this.date = dateObj;
});
},
(err: HttpErrorResponse) => {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.log('An error occurred:', err.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
}
}
);
}
}
The question is, there is a way to do that? How should I do it so I could render the value in HTML?
assign it as this.date = dateObj; instead of date = dateObj;.
If that didn't fix, trigger a change detection by injecting the ChangeDetectorRef through constructor and calling the detectChanges() method.
import { ChangeDetectorRef } from '#angular/core';
constructor(private ref: ChangeDetectorRef){}
// call detectChanges() after this.date = dateObj;
this.date = dateObj;
this.ref.detectChanges();
I am attempting to build an Angular 4 based service (backed by a C#-based RESTful API) which will allow for storing and retrieval of web-application wide settings. Something like a key-value pair based lookup for all common application settings.
The idea is this:
Retrieve all settings on start of the application from the C# WebApi based RESTful service into a client-side JavaScript array and stored in the Angular 4 service.
If any specific setting is needed, first look in the locally retrieved array for said setting and return that.
If said setting is not found, make a call to the previously mentioned WebAPI service for that specific setting to see if it is available and retrieve that. Also, push said retrieved setting in the client-side array so I don't have to make the call again until needed.
The problem I am having is this:
I want to return an Observable, even if I have the setting in the array locally, so that I can handle the situation of the web application having to wait for the setting to be retrieved.
I also want to handle situations where the API call for the specific setting fails.
See below for what I have now, any help appreciated.
'use strict';
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { EmptyObservable } from 'rxjs/observable/EmptyObservable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/share';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { SettingAPIURLs } from '../global.urls';
import * as Interfaces from "../interfaces/models.interface";
import * as Classes from "../classes/models.classes";
#Injectable()
export class SettingsService {
private _settings: BehaviorSubject<Classes.Setting[]>;
private settingDataStore: {
settings: Classes.Setting[]
}
constructor(private http: Http) {
this.settingDataStore = { settings: [] }
}
loadSettings() {
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.subscribe(data => {
this.settingDataStore.settings = data;
this._settings.next(Object.assign({}, this.settingDataStore).settings);
}, error => {
console.log("There were errors in attempting to retrieve the web application's settings: " + error);
});
}
get CurrentSettings() {
return this._settings.asObservable().share();
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
/*
I am lost as to what to do here.
*/
let returnedObservable: Observable<Classes.Setting> = new Observable<Classes.Setting>();
if (typeof (SettingName) === "undefined" || SettingName === null) {
return new EmptyObservable();
}
this.http.get(SettingAPIURLs.GetSetting + "?SettingName=" + SettingName)
.map(response => response.json())
.first();
}
}
Angular has a built in solution for your problem called resolve. This article explains how to use it:
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
The idea is that you have the data for the page loaded before even running the logic for the page.
The basic implementation is that you need to write a resolver that will go ahead and make your settings api call. Then you just need to hookup your routing to start using the resolver.
If I understood correctly , you want to send the setting if it exists else do http call . I am assuming your settings object is a key-value pair object. I am not sure why you have it is an array.
You can do something like this
// extract fetch part separately . If settings exist return it or do http call
fetchSettings(){
return this._settings.asObservable
.flatMap(settings => settings ?
Observable.of(settings) :
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.do(data => {
this.settingDataStore.settings = data;
this._settings.next(
Object.assign(
{},
this.settingDataStore).settings);
})
.catch(error => {
console.log("..... " + error);
});
}
loadSettings(){
this.fetchSettings.subscribe(data => {
console.log('load Settings success', data);
});
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
this.fetchSettings()
.flatMap(settings => settings[SettingName]?
Observable.of(settings[SettingName]) :
this.http.get(SettingAPIURLs.GetSetting +
"?SettingName=" + SettingName)
.map(response => response.json())
.do(res=> this._settings.value[SettingName] = res)
);
}
If any of the above HTTP calls fail you will get an exception which you can handle in the component like this :
this.settingsService.retrieveSetting().subscribe(successFunction, failureFunction);
You can either show error message to user or consider it as blank based on your site requirements.
I'm trying a simple component that has to pull data from a JSON file. I'm almost copying the functionality from generated Fountain App, but for some reason I can't get the desired results. I have a component like:
import {Component, Inject} from "#angular/core";
import {Http} from '#angular/http';
#Component({
selector: 'body-selector',
template: require('./body.html')
})
#Inject(Http)
export class BodyComponent {
constructor(http: Http) {
this.http = http;
this.getText('app/texts/main.json').subscribe(result => {
console.log(result);
this.texts = result;
});
console.log(this.texts);
}
getText(url) {
return this.http.get(url).map(response => response.json());
}
}
on the first console.log I have [Object object] [Object object] [Object object], which is correct as I have three entries in the JSON. On the second however I've got undefined, which turns into an error in the browser.
Error in ./BodyComponent class BodyComponent - inline template:3:6 caused by: Cannot read property 'title' of undefined
I'm looking at the example generated from the fountain app, but I can't get what I'm doing wrong.
You have multiple problems:
The first console.log is inside the callback, where this.texts has just been set. However the second one is outside the callback, so it won't have been. Therefore you'll always see undefined for that, because...
...you never set a default value for this.texts, and your template apparently doesn't have any e.g. *ngIf to handle it being null or undefined, causing errors prior to the callback being called.
Below is your code, refactored to start with an empty this.texts (assuming it should be an array; please adapt to taste) and simplifying the injection of Http. Also note the comments, and that I've used templateUrl to avoid the require and OnInit to trigger the HTTP call slightly later in the component lifecycle rather than doing it in the constructor.
import {Component, OnInit} from '#angular/core'; // note consistent quotes
import {Http} from '#angular/http';
#Component({
selector: 'body-selector',
templateUrl: './body.html',
})
export class BodyComponent implements OnInit {
texts: any[] = []; // start with an empty array
constructor(private http: Http) { } // inject Http here, no need to assign to this
ngOnInit() {
this.http
.get('app/texts/main.json')
.map(response => response.json())
.subscribe(result => {
console.log(result); // only log *inside* the callback
this.texts = result;
});
// outside the callback, the HTTP call hasn't yet finished
}
}
You could also solve this by having an ngIf in your HTML to prevent the element from being loaded before the data is.
<div class="main-container" *ngIf="texts">
...
</div>
I'd strongly recommend running through the basic Angular 2 tutorials to get on top of this stuff, see e.g. the Tour of Heroes.
You get undefined because this:
this.getText('app/texts/main.json')
Is an asynchronous call that gets the data and when it's done, it executes the code in the 'subscribe' block. So this.text is empty until that executes. That is the expected behavior.
Better way to use data or making API call use service:
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
#Injectable()
export class BodyService {
private _requestOptions: RequestOptions;
private static handleError(error: Response): ErrorObservable {
return Observable.throw(error.json().error || 'Server error');
}
constructor(private http: Http) {
const headers = new Headers({ 'Accept': 'application/json' });
this._requestOptions = new RequestOptions({ headers: headers});
}
/**
* [getJsonData will get data of json file: main.json ]
*/
getJsonData() {
return this.http.get('app/texts/main.json', this._requestOptions)
.map(res => res.json()) //needs to add map for converting data in json format
.catch(BodyService.handleError);
}
}
Now Inject this service in your component:
import {Component, OnInit} from '#angular/core';
import { BodyService } from './adminModules.service';
#Component({
selector: 'body-selector',
templateUrl: './body.html',
providers: [BodyService]
})
export class BodyComponent implements OnInit {
texts: any = []; // start with an empty array
errorMessage: any;
constructor(private _bodyService: BodyService) {}
ngOnInit() {
this._bodyService.getJsonData()
.subscribe(data => {
this.texts = data;
console.log('data', this.texts);
}, error => {
this.errorMessage = <any> error;
})
}
}
For calling service, you can call directly in constructor or create one method and call either in constructor or any place where you want to call.
Hope it will helpful for you :)
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();