Angular observable post and subscribe explanation / How it works - javascript

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)

Related

How do I completely mock an imported service class

I am looking to test my next API route which uses the micro framework (similar enough to express when using next-connect).
I have a service:
export class UserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
...
}
}
My API endpoint:
import { NextApiRequest, NextApiResponse } from 'next';
import nc from 'next-connect';
import { UserService } from './UserService'; // Mock and prevent execution
const userService = new UserService();
export default nc().post(async (req: NextApiRequest, res: NextApiResponse) => {
try {
userService.findUser({ email: 'john.doe#example.com' });
return res.status(200).send({ done: true });
} catch (error: any) {
return res.status(500).end(error.message);
}
});
I would like to completely mock the import and prevent any dependent code from executing.
import { UserService } from './UserService';
For example if there is a console.log() in the UserService constructor it should not be run because the import is a completely mocked import.
Update:
I've attempted to use jest.mock but they didn't seem to actually mock my imports. I've added a console.log in the UserService constructor and it continues to be triggered when using jest.mock.
import signup from './signup';
jest.mock('./UserService');
describe('signup', () => {
it('should complete the happy path', async () => {
const req: IncomingMessage = {} as unknown as IncomingMessage;
const res: ServerResponse = {
end: jest.fn(),
} as unknown as ServerResponse;
const actual = await signup(req, res);
expect(actual).toBe(...);
});
});
If you mean mocking in a jest test then you can just use jest.mock('./userService'); or jest.spyOn(UserService, 'findUser') if you want to mock one method.
If you want a mock for a specific use case, you would create a mock service and conditionally import based on some flag.
Ex:
// UserService.js
export class UserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
// calls real API
}
}
// UserService.mock.js
export class MockUserService {
constructor(...) {}
async findUser({ email }: Pick<IUserModel, 'email'>) {
// calls fake API or just returns a promise or anything you want
}
}
// Your Endpoint
import { NextApiRequest, NextApiResponse } from 'next';
import nc from 'next-connect';
import { UserService } from './UserService';
import { MockUserService } from './UserService.mock'; // Mock
let userService;
if (someCondition) {
userService = new UserService();
} else {
userService = new MockUserService();
}
...
// The idea is you want to dynamically change what you're importing
const Something = process.env.NODE_ENV === 'development' ?
require('./FakeSomething') : require('./RealSomething');
jest.mock doesn't support mocking dependencies for imports outside of the actual test file (.spec.ts & .test.ts). In my case I have a lot of dependencies in my Next API endpoint that cannot be mocked.
You'll have to find a pattern to inject the dependency into the endpoint and test it in isolation instead. I made use of the Nextjs middleware example here but there are likely other patterns that will work just as well.
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: Function) {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
// Testable function for DI
export function post(
_userService: UserService,
) {
return async function(req: NextApiRequest, res: NextApiResponse) {
...
}
}
// Endpoint
export default async function(req: NextApiRequest, res: NextApiResponse) {
try {
await runMiddleware(req, res, post(userService));
return res.status(200).send({ done: true });
} catch (error: any) {
return res.status(500).end(error.message);
}
};
// Test usage
describe('signup', () => {
let userService: UserService;
beforeAll(() => {
userService = new UserService({} as unknown as UserRepository);
});
it('should complete the happy path', async () => {
const findSpy = jest
.spyOn(userService, 'find')
.mockImplementation(async () => null);
const req: NextApiRequest = {
body: {
email: 'user#test.com',
password: '12345678',
},
} as unknown as NextApiRequest;
const res: NextApiResponse = {} as unknown as NextApiResponse;
await post(userService)(req, res);
...
});
});

Return a Observable from a Subscription with RxJS

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!

Access class member function from catch clause?

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));

Fetch data once with Observables in Angular 2

I have a service, what is used several times from a lot of my Angular 2 components. It fetches customer data from a Web API and returns an Observable:
getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((r: Response) => {
let a = r.json() as Customer[];
return a;
});
}
I inject this service in my root component, and in every component that wants to access the customers I just subscribe to that Observable:
this.customerService.getCustomers().subscribe(v => this.items = v);
However, every component who subscribes to my Observable causes another execution of the HTTP-request. But to fetch the data only once is enough.
If I try share(), it does not solve my problem:
getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((r: Response) => {
let a = r.json() as Customer[];
return a;
}).share();
}
Still the same issue. Any proposals which operators I have to use to only fetch data once?
1) You can simply save downloaded data in your service:
export class CustomersService {
protected _customers: Array<Customer>;
constructor(public http: Http) {}
public getCustomers(): Observable<Array<Customer>> {
return new Observable(observer => {
if (this._customers) {
observer.next(this._customers);
return observer.complete();
}
this.http
.get(this.baseURI + this.url)
.map((r: Response) => (r.json() as Array<Customer>))
.subscribe((customers: Array<Customer>) => {
this._customers = customers;
observer.next(this.customers);
observer.complete();
});
});
}
}
2) Shorter approach taking refresh parameter:
export class CustomersService {
protected _customers: Array<Customer>;
constructor(public http: Http) {}
public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
if (!refresh && this._customers) {
return Observable.of(this._customers);
}
return this.http
.get(this.baseURI + this.url)
.map((c: Response) => (c.json() as Array<Customer>))
.do((customers: Array<Customer>) => {
this._customers = customers;
});
});
}
}
3) Taking advantage of ReplaySubject:
export class CustomersService {
protected _customers$: ReplaySubject<Array<Customer>> = new ReplaySubject(1);
protected _customersInitialized: boolean;
constructor(public http: Http) {}
public getCustomers(refresh?: boolean): Observable<Array<Customer>> {
if (refresh || !this._customersInitialized) {
this._customersInitialized = true;
this.http
.get(this.baseURI + this.url)
.map((c: Response) => (c.json() as Array<Customer>))
.subscribe((customers: Array<Customer>) => {
this._customers$.next(customers);
});
}
return this._customers$.asObservable().skip(+refresh).distinctUntilChanged();
}
}
And then:
this.customersService.getCustomers()
.subscribe(customers => this.customers = customers);
You can also expose the always up-to-date customers field from SomeService for read only purposes (like displaying in the templates) this way:
public get customers(): ReadonlyArray<Customer> {
return this._customers;
}
I would create a parent container, fetch the data once, and pass it to child components using #Input.
Parent:
#Component({
selector: 'BarFooHttpCaller',
template: ´<child *ngIf="data.length > 0" [data]></child>´
})
class BarFooHttpCaller {
private data: any;
constructor(private foobar:Foobar) {
this.data = {};
}
ngOnInit() {
this.foobar.getCustomers().subscribe(() => {
console.log('httpdone')
});
this.foobar.dataStream.subscribe((data) => {
console.log('new data', data);
this.data = data;
})
}
}
Child:
import { Component, Input } from '#angular/core';
#Component({
selector: 'child',
template: ´<div>{{data}}</div>´
})
export class Child {
#Input() data: any;
}
If you want multiple children to subscribe to the same observable, but only execute the observable once you can do the following.
Note that this does adhere to the design of observables since we are executing the observable in the service layer (Observable.fromPromis(stream.toPromise()) when execution should be done from the component subscribing. View https://www.bennadel.com/blog/3184-creating-leaky-abstractions-with-rxjs-in-angular-2-1-1.htm for more.
//declare observable to listen to
private dataObservable: Observable<any>;
getData(slug: string): Observable<any> {
//If observable does not exist/is not running create a new one
if (!this.dataObservable) {
let stream = this.http.get(slug + "/api/Endpoint")
.map(this.extractData)
.finally(() => {
//Clear the observable now that it has been listened to
this.staffDataObservable = null;
});
//Executes the http request immediately
this.dataObservable = Observable.fromPromise(stream.toPromise());
}
return this.staffDataObservable;
}
the share operator give the possibility to use the same stream's result with multiple observers. It could be good but you generate a new observable stream each time you call getCustomers(), there is no point to call share() since you didn't subscribe multiple times to this stream.
If you wanna share the data with multiple observers but make only one http call you simply have to create a second stream, feed by the http one, containing the data. After that, all your components could subscribe to it.
The code could be something like that
#Injectable()
class FooBar {
public dataStream:Subject<any> = new Subject();
constructor(private http:Http) {}
public getCustomers() {
return this.http
.get(this.baseURI + this.url)
.map((response:Response) => response.json())
.map((data) => {
this.dataStream.next(data);
return data;
})
}
}
#Component({})
class BarFooHttpCaller {
constructor(private foobar:Foobar) {}
ngOnInit() {
this.foobar.getCustomers().subscribe(() => { console.log('http done') });
this.foobar.dataStream.subscribe((data) => {
console.log('new data', data);
})
}
}
#Component({})
class OtherBarFoo {
constructor(private foobar:Foobar) {}
ngOnInit() {
this.foobar.dataStream.subscribe((data) => {
console.log('new data', data);
})
}
}
No need for custom implementations. A pipe will do the trick:
getCustomers$(): Observable<Customer> {
return this.http
.get<Customer>(this.baseURI + this.url)
.pipe(shareReplay(1));
}
Couple of things I did here:
Add shareReplay(1) pipe, to make sure the request is only done once (only thing needed to answer the question)
Remove map and made the get call typed
Postfix method name with $ to indicate an Observable is returned

Can I catch certain errors before "subscribe()" in an RXJS observable in Angular2?

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.

Categories

Resources