Unsubscribe from Firestore stream - Angular - javascript

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

Related

how to use angular pipe and subscribe correctly in request call

In my angular application I am sending a request to my backend to check credentials, after success the backend sends an token which I read. So far this works, but I had to use an pipe to make it map to a method and then make it work. But my problem now it even though I am getting 200 from the server my page will not navigate to the protected page automatically. If I enter the url manually it works this is what I tried:
authenticateUser(login: LoginModel){
this.http = new HttpClient(this.handler)
return this.http.post<JwtToken>(environment.rootUrl + 'api/authenticate', {
username: login.username,
password: login.password,
}).pipe(map(response => this.authenticateSuccess(response)))
.subscribe({
next: () => {
this.isAuthenticated = true;
this.router.navigate(['/dashboard'])
}, error: (error) => {
this.isAuthenticated = false;
console.log(error)
}
})
}
It does not enter the subscribe part after the pipe. Is there any way to make this work? I still want to have an error handling like if no error then navigate to url if error do not navigate.
EDIT:
AuthenticateSuccess method:
isUserLoggedIn(){
return !! localStorage.getItem('authenticationToken')
}
private authenticateSuccess(response: JwtToken): void {
const jwt = response.id_token;
localStorage.setItem('authenticationToken' , jwt)
this.localStorageService.store('authenticationToken', jwt);
console.log(this.localStorageService.retrieve('authenticationToken'))
this.sessionStorageService.clear('authenticationToken');
}
Authguard:
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private auth: AuthenticationService,
private router: Router
) {
}
canActivate(): Promise<boolean> {
return new Promise(resolve => {
if (this.auth.isUserLoggedIn()) {
resolve(true)
} else {
this.router.navigate(['authenticate'])
resolve(false)
}
})
}
}
SOLUTION:
authenticateUser(login: LoginModel) {
this.http = new HttpClient(this.handler)
return this.http.post<JwtToken>(environment.rootUrl + 'api/authenticate', {
username: login.username,
password: login.password,
}).subscribe({
next: response => {
this.isAuthenticated = true;
this.authenticateSuccess(response)
this.router.navigate(['/dashboard'])
}, error: (err) => {
console.log(err)
}, complete: () => {
console.log("finished without worry")
}
})
}
RxJs map operator is supposed to modify the content of an observable. The map operator however needs to return the same observable or another observable, for the next subscribe operation to be able to function.
In your case your map operator does not return any observable at all and therefore the subscribe method has no reason to be triggered.
You could simple return the response again in your method here
private authenticateSuccess(response: JwtToken): any {
const jwt = response.id_token;
localStorage.setItem('authenticationToken' , jwt)
this.localStorageService.store('authenticationToken', jwt);
console.log(this.localStorageService.retrieve('authenticationToken'))
this.sessionStorageService.clear('authenticationToken');
return response;
}
but I think all the code of the map method matches better directly inside the subscribe method.

Displaying toastr messages sequentially in Angular

I update multiple records using the following update method and receive the updated and failed record count from result. Then I want to display related toastr message sequentially using Angular Material toastr. However, the following approach skip the success in #1 (or it is displayed back of the error) and display the error in #2. So, how can I display them sequentially for this method? Maybe I need to use RxJs for this purpose.
update() {
this.demoService.update(...).toPromise()
.then(result => {
if(result.success.count > 0){
// #1 display success toastr
}
if(result.failed.count > 0) {
// #2 display error toastr
}
}).catch(err => {
// #3 display error toastr related to other errors
});
}
definitelly you need to use Rxjs Observables instead of Promises if you work with Angular :d
So, your code will become:
constructor(private alertService: AlertService) {}
update() {
this.demoService.update(...).subscribe(result => {
if(result.success.count > 0) {
result.success.forEach(item => {
this.alertService.info(item);
await this.delay(2000);
}
}
if(result.failed.count > 0) {
result.failed.forEach(item => {
this.alertService.error(item);
await this.delay(2000);
}
}
}, err => {
this.alertService.error(err.message);
});
}
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
For each message, to not be override by the next, you need to await a time as the popup to be hidden. For this delay() function is used.
and the service class as an alert provider (and inject into your component):
import { Injectable } from '#angular/core';
import { ToastrService } from 'ngx-toastr';
#Injectable({
providedIn: 'root',
})
export class AlertService {
constructor(private toastr: ToastrService) {}
public success(message: string): void {
this.toastr.success(message, '', {
timeOut: 2000
});
}
public error(message: string): void {
this.toastr.error(message, '', {
timeOut: 2000
});
}
}

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

RxJs and switchMap

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

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