RxJs and switchMap - javascript

i want create search engine in my website.
I want to use switchMap to cancel previous request, because this function run async.
I getting data from input by keyup, example:
<input type="text" (keyup)="subject.next($event.target.value)">
TypeScript
subject = new Subject<string>();
ngOnInit() {
this.subject.asObservable().pipe(debounceTime(500)).subscribe(res => {
console.log(res);
});
}
I would like to use switchMap and timer here, but what will not change, it always does not work, does anyone have idea how to refactorize this code to work with switchMap and timer from RxJs?
My example in stackblitz:
https://stackblitz.com/edit/angular-playground-53grij?file=app%2Fapp.component.ts

You can try with something like this (assuming you are using RxJS 6):
subject = new Subject<string>();
subscription: Subscription;
ngOnInit() {
this.subscription = this.subject
.pipe(
debounceTime(500),
switchMap((query: string) => {
return this.http.get('http://url?q=' + query);
})
)
.subscribe((res: any) => {
console.log(res);
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}

Related

How to wait for new data from service before proceeding in Angular

I have two components that share data using a service:
export class ShareDataService {
msg: BehaviorSubject<string>;
constructor(
) {
this.msg = new BehaviorSubject("Default Message")
}
changeMessage(message: string) {
this.msg.next(message)
}
}
First component works as a router, when i click a name it redirects to the profile of that name using its id. I import the share-data service i created above, when clicking a name it triggers de sendData() function which gets the id of the clicked element and passes it as a parameter on the service changeMessagge() function:
constructor(
private _data: ShareDataService
) { }
message: string = "";
ngOnInit(): void {
this._data.msg.subscribe(new_msg => this.message = new_msg)
}
sendData() {
var self = this
$("ul.users-list").on("click", ".user", function(event) {
self._data.changeMessage(event.target.id)
});
}
}
After that, when it redirects to the new component, this new component also imports data from the service:
message: string = "";
constructor(
private _http: HttpClient,
private _data: ShareDataService,
private _router: Router
) { }
async ngOnInit() {
this._data.msg.subscribe(new_msg => this.message = new_msg)
await new Promise(r => setTimeout(r, 1)); // Sleeps 1ms so it can load message
if (this.message === "Default Message") { // Returns to landing if no user loaded
this._router.navigateByUrl("/landing");
return
}
this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${this.message}`)
.subscribe((data: UserInterface) => this.user = data)
}
As you can see, it subscribes to the service and gets the value of messagge, which should have been changed when i clicked a name on the other component. The issue is, it fristly doesn't detect the change, and i have to use the new Promise() function to make it sleep for 1ms so it can load the new value of messagge from the service and then continue. Is there any way i can make it wait for the subscribe to finish loading the new messagge without using the Promise for 1ms? already tried putting the code inside the subscribe after it assigns messagge.
I see multiple issues
The variable this.message is assigned asynchronously. By the time the HTTP request is sent, it isn't assigned a value yet. You need to use higher order mapping operator like switchMap to map from one observable to another.
import { switchMap } from 'rxjs/operators';
this._data.msg.pipe(
switchMap(message =>
this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${message}`)
)
).subscribe((data: UserInterface) => this.user = data)
You could see my post here for brief info on handling multiple co-dependent observables.
await new Promise(r => setTimeout(r, 1)); looks inelegant however small the timeout be. With the above mapping operator it shouldn't be needed anymore.
Avoid using jQuery in Angular. Almost everything from jQuery could be accomplished directly in Angular. Mixing them might lead to maintenance later.
Update: returning observable conditionally
Based on your condition, you could use RxJS iif function with new Observable() construct to either return the HTTP request or do something else and close the observable based on a condition.
Try the following
import { iif, EMPTY, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
this._data.msg.pipe(
switchMap(message =>
iif(
() => message === "Default Message",
new Observable(subscriber => {
this._router.navigateByUrl("/landing");
subscriber.complete();
}),
this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${message}`)
)
)
).subscribe((data: UserInterface) => this.user = data)

Best way for multiple HTTP Request in Angular

I am trying to send 2 HTTP requests one by one; if the first one is succeeds, send the second one, if not display the corresponding error message regarding to the first request.
I am planning to use something like that, but not sure if it is the best option for this scenario:
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
loadedCharacter: {};
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get('/api/people/1').subscribe(character => {
this.http.get(character.homeworld).subscribe(homeworld => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
});
}
}
I have different requests e.g. PUT and CREATE also using this approach. I know there are other ways e.g. forkjoin, mergemap, but if this one solves my problem seems to be more readable. Any idea?
First of all, your code works and that's great - you can leave it as is and everything will be fine.
On the other hand, there is a way for multiple improvements that will help you and your colleagues in future:
try to move http-related logic to the service instead of calling http in the components - this will help you to split the code into view-related logic and the business/fetching/transformation-related one.
try to avoid nested subscribes - not only you ignore the mighty power of Observables but also tie the code to a certain flow without an ability to reuse these lines somewhere in the application. Returning the Observable might help you with "sharing" the results of the request or transforming it in some way.
flatMap/mergeMap, concatMap and switchMap work in a different way, providing you an ability to control the behaviour the way you want. Though, for http.get() they work almost similar, it's a good idea to start learning those combining operators as soon as possible.
think about how you'll handle the errors in this case - what will happen if your first call will result an error? Observables have a powerful mechanism of dealing with them, while .subscribe allows you to handle an error only in one way.
An example using the switchMap:
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
loadedCharacter: {};
constructor(private http: HttpClient) {}
ngOnInit() {
const character$ = this.http.get('/api/people/1').pipe(
tap(character => this.characterWithoutHomeworld = character), // setting some "in-between" variable
switchMap(character => {
return this.http.get(character.homeworld).pipe(
map(homeworld => {
return {
...character,
homeworld: homeworld
}
}
)
)
}),
catchError(errorForFirstOrSecondCall => {
console.error('An error occurred: ', errorForFirstOrSecondCall);
// if you want to handle this error and return some empty data use:
// return of({});
// otherwise:
throw new Error('Error: ' + errorForFirstOrSecondCall.message);
})
);
// you can either store this variable as `this.character$` or immediately subscribe to it like:
character$.subscribe(loadedCharacter => {
this.loadedCharacter = loadedCharacter;
}, errorForFirstOrSecondCall => {
console.error('An error occurred: ', errorForFirstOrSecondCall);
})
}
}
2 nested subscriptions are never a way to go. I recommend this approach:
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.homeworld).pipe(
map(homeworld => ({ ...character, homeworld })),
)),
).subscribe(character => this.loadedCharacter = character);
Edit: For your university
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.university).pipe(
map(university => ({ ...character, university})),
)),
).subscribe(character => this.loadedCharacter = character);
Or even chain university and homeworld requests
this.http.get('/api/people/1').pipe(
switchMap(character => this.http.get(character.homeworld).pipe(
map(homeworld => ({ ...character, homeworld })),
// catchError(err => of({ ...character, homeworld: dummyHomeworld })),
)),
switchMap(character => this.http.get(character.university).pipe(
map(university => ({ ...character, university})),
)),
).subscribe(character => this.loadedCharacter = character);
You can try a solution using switchmap and forkJoin for easier chaining and error handling. this will help keep the code clean in case the chain keeps growing into a deep nest.
this.http
.get("/api/people/1'")
.pipe(
catchError((err) => {
// handle error
}),
switchMap((character) => {
return forkJoin({
character: of(character),
homeworld: this.http.get(character.homeworld)
});
})
)
.subscribe(({ character, homeworld }) => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
EDIT: Scenario 2
this.http
.get("/api/people/1")
.pipe(
catchError((err) => {
console.log("e1", err);
}),
switchMap((character) => {
return forkJoin({
character: of(character),
homeworld: this.http.get(character.homeworld).pipe(
catchError((err) => {
console.log("e2", err);
})
)
});
})
)
.subscribe(({ character, homeworld }) => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
You can chain a catch error or add a separate function for error handling without it invoking the next API call. but I would recommend abstracting the backend logic to an angular service and using this method. which would help retain an easy to read structure.
You can check if the first request was successful or not by checking the status code:
ngOnInit() {
this.http.get('/api/people/1').subscribe((character: HttpResponse<any>) => {
// here you should look for the correct status code to check, in this example it's 200
if (character.status === 200) {
this.http.get(character.homeworld).subscribe(homeworld => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
} else {
// character is gonna contain the error
console.log(character)
}
});
}

Unsubscribe from Firestore stream - Angular

Recently I implemented Firebase into my Angular project and I have a question about unsubscribing from data stream.
When you use classic HTTP calls, observables from this are finite, but with the firestore, these are infinite, so you have to unsubscribe. But when I do so (after destroying component and log out), I still get an error in console and I can see that requests are still sending (or persisting).
In the network tab I can see this in the request timing:
Caution: request is not finished yet
This error pops up after I log out (Its probably caused because of my rules that I set)
FirebaseError: Missing or insufficient permissions
And also, I still get an error even if I use async pipe in angular.
Here is my current solution:
data.service.ts
items: Observable<any[]>;
constructor(db: AngularFirestore) {
this.items = db.collection("data").valueChanges();
}
getItems() {
return this.items;
}
home.component.ts
req: Subscription;
ngOnInit(): void {
this.req = this.dataService.getItems().subscribe(
res => {
console.log(res);
},
err => console.log(err),
() => console.log("completed")
);
}
ngOnDestroy(): void {
console.log("ya");
this.req.unsubscribe();
}
Thanks for your advices!
home.component.ts
import { takeWhile } from "rxjs/operators";
#Component({...})
export class HomeComponent implements OnInit, OnDestroy {
isAlive: boolean = true;
...
ngOnInit(): void {
this.dataService.getItems()
.pipe(takeWhile(() => this.isAlive))
.subscribe(res => {
console.log(res);
});
}
ngOnDestroy(): void {
this.isAlive = false;
}
}
takeWhile is what u needed
Use one of the rxjs operators, eg take (1) after taking the value, it unsubscribe by itself
this.dataService.getItems().pipe(take(1)).subscribe(
res => {
console.log(res);
},
err => console.log(err),
() => console.log("completed")
);

How to block a subscription call in constructor until completed?

How may I block a constructor, to wait for an Http call to return data?
constructor() {
this.initRestData().subscribe();
// wait for data to be read
this.nextStep();
}
The data retreived by the initRestData() call is needed by other services/components in the application. I only have to do this at startup. If there is a better way to handle this then Observable that would be ok too.
You could chain the calls either inside the subscribe or in a do-operator:
constructor() {
this.initRestData()
.do(receivedData => this.nextStep(receivedData)})
.subscribe();
}
For the case that other services rely on this.nextStep() as well, you should implement this as a stream as well:
private initialData$ = new BehaviorSubject<any>(null);
constructor() {
this.initRestData()
.do(data => this.initialData$.next(data))
.switchMap(() => this.nextStep())
.subscribe();
}
nextStep(): Observable<any> {
return this.initialData$
.filter(data => data != null)
.take(1)
.map(data => {
// do the logic of "nextStep"
// and return the result
});
}

How to synchronise Angular2 http get?

I understand using observable I can execute a method when the request is completed, but how can i wait till a http get is completed and return the response using in ng2 http?
getAllUser(): Array<UserDTO> {
this.value = new Array<UserDTO>();
this.http.get("MY_URL")
.map(res => res.json())
.subscribe(
data => this.value = data,
err => console.log(err),
() => console.log("Completed")
);
return this.value;
}
the "value" will is null when its returned because get is async..
your service class: /project/app/services/sampleservice.ts
#Injectable()
export class SampleService {
constructor(private http: Http) {
}
private createAuthorizationHeader() {
return new Headers({'Authorization': 'Basic ZXBossffDFC++=='});
}
getAll(): Observable<any[]> {
const url='';
const active = 'status/active';
const header = { headers: this.createAuthorizationHeader() };
return this.http.get(url + active, header)
.map(
res => {
return res.json();
});
}
}
your component: /project/app/components/samplecomponent.ts
export class SampleComponent implements OnInit {
constructor(private sampleservice: SampleService) {
}
ngOnInit() {
this.dataset();
}
dataset(){
this.sampleservice.getAll().subscribe(
(res) => {
// map Your response with model class
// do Stuff Here or create method
this.create(res);
},
(err) => { }
);
}
create(data){
// do Your Stuff Here
}
}
By looking at the angular source (https://github.com/angular/angular/blob/master/packages/http/src/backends/xhr_backend.ts#L46), it is apparent that the async attribute of the XMLHttpRequest is not getting used. The third parameter of XMLHttpRequest needs to be set to "false" for synchronous requests.
Please find code for your problem
Below is component and service file.And Code is Working fine for synchornize
import { Component, OnInit } from '#angular/core';
import { LoginserviceService } from '../loginservice.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
model:any={};
constructor(private service : LoginserviceService) {
}
ngOnInit() {
}
save() {
this.service.callService(this.model.userName,this.model.passWord).
subscribe(
success => {
if(success) {
console.log("login Successfully done---------------------------- -");
this.model.success = "Login Successfully done";
}},
error => console.log("login did not work!")
);
}
}
Below is service file..
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { UserData } from './UserData';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/toPromise'
import {Observable} from 'rxjs/Rx'
#Injectable()
export class LoginserviceService {
userData = new UserData('','');
constructor(private http:Http) { }
callService(username:string,passwrod:string):Observable<boolean> {
var flag : boolean;
return (this.http.get('http://localhost:4200/data.json').
map(response => response.json())).
map(data => {
this.userData = data;
return this.loginAuthentication(username,passwrod);
});
}
loginAuthentication(username:string,passwrod:string):boolean{
if(username==this.userData.username && passwrod==this.userData.password){
console.log("Authentication successfully")
return true;
}else{
return false;
}
}
}
Another solution would be to implement a priority queue of sort.
From what I understand http requests do not get executed until you add subscribers. Therefore, you can do something like this:
Observable<Response> observable = http.get("/api/path", new RequestOptions({}));
requestPriorityQueue.add(HttpPriorityQueue.PRIORITY_HIGHEST, observable,
successResponse => { /* Handle code */ },
errorResponse => { /* Handle error */ });
This assumes that requestPriorityQueue is a service injected into your component. The priority queue would store entries in an array in the following format:
Array<{
observable: Observable<Response>,
successCallback: Function,
errorCallback: Function
}>
You would have to decide how the elements are added to your array. Finally, the following will happen in the background:
// HttpPriorityQueue#processQueue() called at a set interval to automatically process queue entries
The processQueue method would do something like this:
protected processQueue() {
if (this.queueIsBusy()) {
return;
}
let entry: {} = getNextEntry();
let observable: Observable<Response> = entry.observable;
this.setQueueToBusy(); // Sets queue to busy and triggers an internal request timeout counter.
observable.subscribe()
.map(response => {
this.setQueueToReady();
entry.successCallback(response);
})
.catch(error => {
this.setQueueToReady();
entry.errorCallback(error);
});
}
If you are able to add new dependencies you could try using the following NPM package: async-priority-queue
I looked and I couldn't find any way to make an HTTP call sync instead of async.
So the only way around this: wrap your call in a while loop with a flag. Don't let the code continue until that flag has "continue" value.
Pseudo code as follows:
let letsContinue = false;
//Call your Async Function
this.myAsyncFunc().subscribe(data => {
letsContinue = true;
};
while (!letsContinue) {
console.log('... log flooding.. while we wait..a setimeout might be better');
}
as you see, first callback waiting for a data from request and
there you can go on with your logic (or use the third one)
example:
.. subscribe( data => {
this.value = data;
doSomeOperation;
},
error => console.log(error),
() => {console.log("Completed");
or do operations here..;
}
});
How about to use $.ajax(of jQuery) or XMLHttpRequest.
It can use as asynchornize.
You should not try to make http calls behave synchronously. Never a good idea.
Coming to your getAllUser implementation it should return an observable from the function and the calling code should subscribe instead of you creating a subscription inside the method itself.
Something like
getAllUser(): Observable<UserDTO> {
return this.http.get("MY_URL")
.map(res => res.json());
}
In you calling code, you should subscribe and do whatever you want.

Categories