I've a service, which returns an Observable, on which I map to parse the response and a catch clause to catch errors if any. The problem however is that I need to update a data member in the handler method and it's shared to other components for use.
import { stuff } from 'stuffs';
class AppService() {
private errorMsg = new Subject<string>();
// ... More methods here
private handleError(error) {
// Problem - `this` here doesn't point to AppService
this.errorMsg.next('Sharable error message to component!');
return Observable.throw(error.message);
}
callLoginApi(userDetails: {email: string, password: string}):
Observable<any> {
return this.http.post(this.postURI, userDetails)
.map(this.extractData)
.catch(this.handleError);
}
}
.catch(this.handleError.bind(this));
or
.catch(err => this.handleError(err));
Related
I currently have a service that do a HTTP request to an API to fetch data. There are some logic that I want to do to the observable from within the service, but I still want to also subscribe to the Observable in my Component so that I can return any errors to the Component and to the user.
Currently I do:
// service.ts
getData(): Observable<any> {
return this.http.get(url).pipe(catchError(this.handleError)
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my logic that belong to the service.ts
// ...
// Do my logic that belongs in the component.ts
// ...
}, err => this.errors = err)
}
What I would like to do is to refactor this so that I handle the logic related to the subscription and the service.ts within the getData() method, and then return an Observable with the HTTP response and any errors to the Component so that I can continue doing things there.
What's the pattern to do this?
I feel like multiple of the patterns and solutions posted is "ugly" or does not follow the Observable pattern (Like doing callbacks).
The cleanest, most "RxJS"-like solution I came up with was to wrap the service method's return value in a second Observable.
So the following:
// service.ts
getData(): Observable<any> {
return new Observable(subscriber => {
this.http.get(url)
.pipe(catchError(this.handleError)
.subscribe(res => {
// Do my service.ts logic.
// ...
subscriber.next(res)
subscriber.complete()
}, err => subscriber.error(err))
})
}
// component.ts
ngOnInit() {
this.service.getData().subscribe(res => {
// Do my component logic.
// ...
}, err => this.errors = err)
}
Use map:
// service.ts:
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url).pipe(
map(res => {
/* Your processing here */
return res;
}),
catchError(this.handleError)
)
}
Try this way
service.ts
getData(): Observable<any> {
return this.http.get(url).map(res=> <any>(res['_body']));
}
component.ts
this.service.getData().subscribe(response=>{
var res1 = JSON.stringify(response);
var res2 = JSON.parse(res1);
var res3 = JSON.parse(res2);
}); //parse response based on your response type
Option 1
If you subscribe Observable in component then only component will have that subscription and it must be passed back to service.
Option 2
Use this pattern.
service.ts
getData(doer: Function) {
let subscriptions = Observable.of({ data: 'response', isError: false })// request
.catch(error => Observable.of({ data: error, isError: true })) //handle error
.do(data => doer(data))
.subscribe();
this.handleSubscription(subscriptions); //subscription handling in service
}
component.ts
ngOnInit() {
this.getData(response => {
if (response.isError) {
///
} else {
let data = response.data;
// Process
}
})
}
Be careful: All the answers are for <= Angular 4. In Angular 5, you don't need a map() anymore, so just leave that out. just return this.http.get() as it returns an Observable, where you can subscribe on.
Furthermore, be aware you have to import HttpClient instead of Http.
You can directly use "map" and "catch" function on Observable returned by http.get method.
import { catchError, map } from 'rxjs/operators';
getData(): Observable<any> {
return this.http.get(url)
.map(res => {
/* Your processing here */
return res;
})
.catch(this.handleError);
}
You can remove this, and use map. In subscribe error, you can get error event.
If you use HttpClient, just use get!
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
This is done in angular 2 and typescript.
I am trying to return data from the server and pass it back to a component in angular.
getUsers(): Promise<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.toPromise()
//.then(resp => resp.json() as User[])
.then(this.returnData)
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
private returnData(data: any): User[] {
var rtn = data.json() as User[];
return rtn;
}
Data is returned from the server fine and is casted to User[] fine also.
However, its not returned to the javascript method.
ngOnInit(): void {
this.userService.getUsers().then(users => this.users = users);
}
My this.users is just a bunch of promise related stuff and not the users array.
Explanation: You already call .then in getUsers() so calling it twice wil not have the data it's about chaining a promise, so for your code you can set returnData to a Promise too and can chain it like you do.
private returnData(data: any): Promise<User[]> {
var rtn = data.json() as User[];
return Promise.resolve(rtn);
}
now you can use:
this.userService.getUsers().then(users => this.users = users);
But, but the simplest way is using the observable map operator :
getUsers(): Promise<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.map(users => this.returnData(users))
.toPromise()
}
and in ngOnInit:
ngOnInit(): void {
this.userService.getUsers().then(users => {
this.users = users
});
}
Or just observable:
getUsers(): Observable<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.map(users => this.returnData(users))
}
and
ngOnInit(): void {
this.userService.getUsers().subscribe(users => {
this.users = users
});
The promise returns immediately, so this won't work.
The success and error events need to be handled up on the model level so you can do what you want with the data (not the service's responsibility). The service just gets data and sends it along to the callbacks.
Something like this:
loadUsers(successCallback, errorCallback): Promise<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers')
.toPromise()
.then(successCallback)
.catch(errorCallback);
}
// MOVE METHODS TO MODEL CODE
var self = this;
self.users = [];
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
private loadData(data: any): User[] {
self.users = data.json() as User[];
}
ngOnInit(): void {
this.userService.loadUsers(loadData, handleError);
}
How do I write the received data in tmp? Through return, thepromise object is returned from the function, and in the function tmp it is not visible
tmp: string;
constructor(private http: Http) {
this.tmp = "load";
this.GetUsers();
}
ngOnInit() {
setTimeout(console.log("Hello"), 2000);
}
GetUsers() {
this.http.get('http://localhost:1337/api/users')
.toPromise()
.then(function(response) {
this.tmp = "success"
})
.catch(this.handleError);
Also, setTimeout does not work. That is, it only works once and all.
constructor(private http: Http) {
this.tmp = "load";
this.GetUsers();
}
ngOnInit() {
}
GetUsers() {
setTimeout(console.log("Hello"), 2000);
}
Try this:-
Add import for Response from #angular/http
this.http.get('http://localhost:1337/api/users')
.toPromise()
.then((response:Response)=> { this.tmp = response.json() })
.catch(this.handleError);
The ngOnInit() life cycle hook in which you have written the setTimeout() will execute only once after constructor. If the written code is in component then it will execute every time component object is created but if it in service then it will execute once when you provide the service since service object is singleton object
Explanation of Observable Post
setup.component.ts
import { Component, EventEmitter, OnInit, Output } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { Post } from './model/post';
import { PostService } from './service/post.service';
#Component({
selector: 'setup',
templateUrl: './setup.component.html',
styleUrls: ['./setup.component.scss']
})
export class SetupComponent implements OnInit {
#Output()
change: EventEmitter<string> = new EventEmitter();
postUsers(input){
this.postService.postUser(input)
.subscribe(
post => {
this.post = post
},
err => {
console.log(err);
});
}
clicked(value) {
console.log(value);
this.postUsers(this.input)
// this.change.emit(value);
}
complexForm : FormGroup;
constructor(private postService: PostService) {}
post: Post[];
ngOnInit() {}
}
post.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Post } from '../model/post';
import { Observable } from 'rxjs/Rx';
// Import RxJs required methods
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class PostService {
constructor (private http: Http) {}
private registerUrl = 'http://localhost:2001/api/users/register';
postUser(user:Post) : Observable<Post[]>{
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this.registerUrl, user, options)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: Response | any) {
// In a real world app, you might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
model/post.ts
export class Post {
constructor(
public id: number,
public name: string,
public email: string,
public password: string
){}
}
I understand what the model/post.ts does, it defines the property types, what I need is clarification on the subscribe method within setup.components.ts. The Observable gets invoked inside clicked() but what I want to understand is how does this because accessible to the application so that I can proceed after the action has run its course this.postUsers(this.input)
Typically with a Promise I would have done the following
this.postUsers(this.input)
.then(function(){
});
I'd really like it if someone could explain how it works and how to achieve a confirmation that the post has completed to then run the next function
ie I have this
clicked(value) {
console.log(value);
this.postUsers(this.input)
// this.change.emit(value);
}
but in promise I'd do
clicked(value) {
console.log(value);
this.postUsers(this.input)
.then(function(){
this.change.emit(value);
});
}
How can I get this to work with Observables? I tried to see what was returned by doing
clicked(value) {
console.log(value);
const runThis = this.postUsers(this.input);
console.log(runThis);
// this.change.emit(value);
}
but it returns undefined.
Similar with promises make your postUsers method return an observable (not a subscription)
postUsers(input){
return this.postService.postUser(input);
}
Then you can subscribe to this method like using a then in promises.
clicked(value) {
console.log(value);
this.postUsers(this.input).subscribe((response)=> {
this.change.emit(value);
});
}
You can also convert observables into promises. Then you wouldn't get confused.
import 'rxjs/add/operator/toPromise';
postUsers(input){
return this.postService.postUser(input).toPromise();
}
clicked(value) {
console.log(value);
this.postUsers(this.input)
.then((res)=>{
this.change.emit(value);
});
}
Note that I haven't used the function keyword in my callbacks. If I did this.change would refer to the callbacks change because the this wouldn't refer to the component.
how to achieve a confirmation that the post has completed
When the stream triggers complete callback you will know the request has completed. This is the third callback you pass to the subscribe method or the complete method if you pass observer object instead of callbacks.
I think you will benefit from knowing how the operator toPromise work. You can then use it directly or emulate its logic. You can use it:
this.postUsers(this.input).toPromise()
.then(function(){
...
});
Basically what it does is creates a promise and returns it. Then it subscribes the the observable returned by the this.postUsers and waits until this observable emits completed event. After that, it resolves the promise with the last event it returned. Something along these lines:
toPromise(observable) {
let lastReturnedValue = null;
return new Promise() {
observable.subscribe(
(v)=>{
lastReturnedValue = v;
},
(e)=>{
reject(e);
},
(c)=>{
resolve(lastReturnedValue);
});
}
}
So just as with toPromise you can subscribe to the observable and listen for complete event:
clicked(value) {
console.log(value);
this.postUsers(this.input).subscribe(
(v)=>{ // here you get the value and can save it },
(e)=>{ // here you know that observable has error, similar to `catch` callback },
()=>{ // here you know that observable has completed, similar to `then` callback}
);
console.log(runThis);
// this.change.emit(value);
}
Or you can return the observable for someone else to listen:
clicked(value) {
return this.postUsers(this.input)
}
...
clicked().subscribe( // all the code from previous example)
Is it possible for a base class to catch certain errors before allowing the subclass to subscribe to the observable in Angular2.
e.g.
export class SomeBaseClass {
constructor(private _http: Http, private _location: Location) {}
protected _fetchData(url): Observable<any> {
const headers = new Headers();
headers.append('Authorization', 'Token foo');
return this._http.get(url, {headers})
.map(response => response.json())
.catch(response => this._handle401(error));
}
private _handle401(response: Response) {
if(response.status === 401) {
this._location.go('/login');
}
// What should this return?
}
}
export class SomeClass extends SomeBaseClass {
constructor( _http: Http, _location: Location) {
super(_http, _location);
}
doTheThing() {
this._fetchData('/someUrl')
.subscribe(
response => this._handleResponse(response),
error => this._handleErrorThatIsNot401(error));
}
private _handleResponse(response) {
// ...
}
private _handleErrorThatIsNot401(error) {
// ...
}
}
Is catch what I am looking for? Should I be using map (or something else)? Or am I going about this the wrong way entirely?
Update
Both answers (so far) put me on the right track, ultimately - I solved it like this:
protected _get(url: string, data?: any): Observable<any> {
return super._get(url, data, this._authorizationHeader)
.map(response => response.json())
.catch(response => this._handle401(response));
}
private _handle401(response: Response): Observable<any> {
try {
if(response.status === 401) {
this._router.navigateByUrl('/login');
return Observable.throw(response.status);
}
} catch(err) {
console.warn('AuthenticatedHttpService._handle401');
console.error(err);
}
return Observable.of(response);
}
Using catch alone does not help much since you have client code subscribed and you must return Observable from catch.
I would implement it as follows:
Rx.Observable.of(42)
.do(v=>{throw new Error('test')})
.catch(Rx.Observable.of(undefined))
.filter(v=>{return v !== undefined})
.subscribe(
(e)=>{console.log('next', e)},
(e)=>{console.log('error', e)},
()=>{console.log('complete')}
);
.catch() is the right one.
Observable is lazy, so there are no errors before you subscribe. Not sure if you mean this kind of "before" therefore I mention it just to be sure.