So i have pretty straight forward scenario. One subject and observable. When client logs in i publish success, when user logs out i publish false.
Problem is in subscribe method in LoginComponent
First time everything works great. User logs in i get one event, but after that when user logs out second time and logs in again i get 2 same events, again if user logs out and then logs in i get 3 duplicate events and so on.
AuthService.ts
public _loggedIn: Subject<LoggedInOrResetPassword> = new Subject();
public loggedId: Observable<LoggedInOrResetPassword> = this._loggedIn.asObservable();
obtainAccessToken(){
// ommitted
this.httpClient.post(environment.baseUrl + url, null, requestOptions)
.subscribe(data => {
this.saveToken(data);
this._loggedIn.next(LoggedInOrResetPassword.createTrue());
});
// ommitted
}
private logout(navigateTo?: string){
this._loggedIn.next(LoggedInOrResetPassword.createFalse());
}
LoginComponent.ts
ngOnInit() {
this.authservice.loggedId.subscribe( ( loggedInOrResetPassword: LoggedInOrResetPassword ) => {
// HERE I GET DUPLICATE VALUES
});
The reason is that you are NOT unsubscribing when LoginComponent is destroyed.
Your code should be changed as follows
First add an instance property to LoginComponent to store the subscription, such as
export class LoginComponent implements OnInit, OnDestroy {
.....
loginSubscription: Subscription;
.....
}
Then change ngOnInit so that you store the subscription in the newly added property
ngOnInit() {
this.loginSubscription = this.authservice.loggedId.subscribe( ( loggedInOrResetPassword: LoggedInOrResetPassword ) => {
// HERE I GET DUPLICATE VALUES
});
Eventually add ngOnDestroy to make sure you unsubscribe when the component gets destroyed
ngOnDestroy {
if (this.loginSubscription) {
this.loginSubscription.unsubscribe();
}
}
Take a look at the async pipe of Angular as an alternative method to subscribe to Observables and automatically unsubscribe.
Related
I have a table that needs to be refreshed each time a user delete a row, or edit data with a designate function called reloadUsersData();
I've created a BehaviorSubject that gets the table's data from a service - once the table is loaded - but i'm not sure that my implementation of reloadUsersData is good since i repeat the same code and resubscribe to the same observable ....
export class TableBasicExample implements OnDestroy, OnInit{
dataSource$ = new BehaviorSubject<any>([]);
private subs: Subscription[] = [];
constructor(private apiService: ApiService) {}
ngOnInit() {
this.subs.push(
this.apiService.getUsers().subscribe((res: any) => {
this.dataSource$.next(res);
}));
}
reloadUsersData(){
// how can i subscribe to the same api more effectively ?
this.subs.push(
this.apiService.getUsers().subscribe((res: any) => {
this.dataSource$.next(res);
}));
}
ngOnDestroy(): void {
this.subs.forEach((us) => us.unsubscribe());
}
}
Fortunately, this can be simplified! I'm assuming that .getUsers() emits one array of users and completes. With that in mind, you have the right idea with creating a subject to handle reload events.
One strategy you can use in RxJS is to create an observable that includes a subject in its .pipe(). This is because a Subject inherits all capabilities of an Observable.
Here's the code.
export class TableBasicExample {
private fetchEvent = new BehaviorSubject<'fetch'>('fetch');
public users$: Observable<User[]>;
constructor(private apiService: ApiService) {
this.users$ = this.fetchEvent.pipe(
switchMapTo(this.apiService.getUsers())
);
}
reloadUsersData() {
this.fetchEvent.next('fetch');
}
}
We create a BehaviorSubject (which is an extension of Subject) to handle our fetch event. We have it emit the string 'fetch', but it could honestly be any value.
Next, we declare the main Observable we use for our table rendering. It will subscribe to fetchEvent and will switchMap to an inner observable (.getUsers()) and emit its value.
Last, we have our public method that will emit a new value in our private BehaviorSubject.
In your component's template file, you can subscribe to this observable using the async pipe.
<table *ngIf="users$ | async as users">
<tr *ngFor="user of users">
<!-- table row data -->
</tr>
</table>
<button (click)="reloadUsersData()">Reload</button>
The async pipe handles the subscribe/unsubscribe events for you, so you no longer need onInit and onDestroy in your component's TS file.
And that's it! When reloadUsersData() is invoked, it causes fetchEvent to emit a new value. Because users$ has an active subscription, it will receive that new value, and again subscribe to the inner switchMap observable.
Note: The switchMapTo(obs$) operator is similar to switchMap((value)=>$obs) operator. It's just a little less code because we don't actually need the value from our BehaviorSubject.
Im using BehaviourSubject from RxJS:
private rights = new BehaviorSubject<Array<string>>([]);
updateRights(rights: Array<string>) {
this.rights.next(rights);
}
getRights(): Observable<any> {
return this.rights.asObservable();
}
I'm updating the rights in the root component and im subscribing to it in another component like:
this.configService.getRights().subscribe(res => {
console.log(res);
})
This subscription is firing twice. Once when the data is empty and then again when the data is received.
I want the subscription to fire only once and get only the latest data. What should be done?
BehaviourSubject emits the value on subscription by default, and it is intended design. If you do not want this behaviour, use Subject instead.
Do it this way:
private currnetRightsSubject: BehaviorSubject<string[]>;
public currentRights: Observable<string[]>;
constructor() {
this.currnetRightsSubject= new BehaviorSubject<string[]>(/*.......*/);
this.currentRights= this.currnetRightsSubject.asObservable();
}
public get currentRightsValue(){
return this.currnetRightsSubject.value;
}
updated
fill the BehaviorSubject like this:
this.currnetRightsSubject.next(someValue);
I have a form and I allow the user to click as many times as he wants on a refresh button. Of course, I use debounceTime operator but I don't know how to:
either cancel the previous http requests
or indicate to my service to return the value of the latest emission.
For example:
t1: click => received data in 2000ms
t2: click => received data in 200ms
Therefore, I will get the data from t1 moment whereas the latest one is at t2.
I've tried with pipe(last()), switchMap but I don't return data.
My component:
this.filtersForm.valueChanges.pipe(debounceTime(500)).subscribe(
form => {
this.service.setFilters(form); // Set private field in service (1)
this.onSubmit();
}
);
onSubmit() {
if (this.filtersForm.valid) {
this.service.notifFiltersHasChanged();
}
}
Service:
ctor(...) {
this.filters$.subscribe(f => this.getData());
}
notifFiltersHasChanged() {
this.filters$.next(this._filters); // (1) _filters is set by setFilters method
}
getData(): void {
// ...
this.backEndService.getAll(this._filters).subscribe(data => this._data = data);
}
BackEndService:
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
The main trick is to use a single subscription (or even zero, if you'll use | async pipe in your template). So you source from an Observable and chain through your services.
Heres an updated example of yours:
Component
onDestroy$ = new Subject<void>();
constructor(){
this.filtersForm.valueChanges.pipe(
// accept only valid values
filter(() => this.filtersForm.valid),
// debounce them
debounceTime(500),
// when a value comes in -- we switch to service request
// subsequent values would cancel this request
switchMap(formValues => this.service.getData(formValues)),
// this is needed to unsubscribe from the service
// when component is destroyed
takeUntil(this.onDestroy$)
)
.subscribe(data=>{
// do what you need with the data
})
}
ngOnDestroy() {
this.onDestroy$.next(void 0);
}
Service
// service becomes stateless
// its only responsible for parsing and passing data
getData(filters): Observable<Data> {
return this.backEndService.getAll(filters);
}
BackEndService
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
Another way would be to have a Subject, that you would push to. Otherwise it would be the same chaining on top of that Subject.
Hope this helps
An Observable - a collection over time - is a useful thing to be able to request over the web. A feed is best modeled as an Observable, not a static array that you must poll and diff to request.
My question - if I wanted to create a web endpoint that would let you do
web-tail -f http://somewhere.com/biz-quotes
This service, queried by a fictional utility web-tail, would every 5 seconds a new pithy business quote like "Custom departmental synergy" would be returned. I could write such a web-tail utility with WebSockets, and establish a convention for what field of emitted objects would be emitted to the console. But what language would I write a consumable specification in?
Is the Observable specification mature enough to be referenced?
If your goal is to write a client which consumes messages sent by a server over websockets, you can definitely use RxJs on top of, say, socket.io.
This nice article explains you how this can work.
In a nutshell this is the TypeScript code you need.
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { Observer } from 'rxjs';
import * as socketIoClient from 'socket.io-client';
export class SocketObs {
private socket: SocketIOClient.Socket;
private connect = new Subject<any>();
private disconnect = new Subject<any>();
constructor(url: string);
constructor(input: any) {
this.socket = socketIoClient(input);
this.socket.on('connect',
() => {
this.connect.next();
// complete to make sure that this event is fired only once
this.connect.complete();
}
);
this.socket.on('disconnect',
() => {
this.disconnect.next();
// complete to make sure that this event is fired only once
this.disconnect.complete();
}
);
}
send(event, message?) {
this.socket.emit(event, message);
}
onEvent(event): Observable<any> {
return new Observable<any>((observer: Observer<any>) => {
this.socket.on(event, data => observer.next(data));
});
}
onDisconnect() {
return this.disconnect.asObservable();
}
onConnect() {
return this.connect.asObservable();
}
close() {
this.socket.close();
}
}
SocketObs class offers you the API you need in form of Observable, in particular onEvent returns an Observable which emits any time a certain event is received from the server.
I'm doing a paginator with RxJS, I use a subject to centralize any time a new page is called. And on each event, I use exhaustMap to retrieve the page. That's prevent the getPage http call to be fired several time for the same page.
this._nextPage$.pipe(
exhaustMap(nextPageNumber => this.getPage(nextPageNumber))
).subscribe();
But I'd like to also show a spinner on each http pending.
With this code, how to reach the subscription of the merged http Observable ?
(in order to bind a pending component to the subscription)
Thx in advance !
[EDIT]
I need/prefer using a Subscription that hold the pending state itself for 2 main reasons:
I already use several custom components/directives based on Subscription
As it's used in many different places I hopped to manage the pending state without too much boilerplate code ...
Here is a simple example of component displaying pending action
#Component({
selector: 'anie-busy',
templateUrl: './busy.component.html',
styleUrls: ['./busy.component.scss']
})
export class BusyComponent implements OnChanges {
#Input() subscription;
isPending = false;
constructor() { }
ngOnChanges() {
if (this.subscription) {
this.isPending = true;
this.subscription.add(() => this.isPending = false);
} else {
this.isPending = false;
}
}
}
You can pipe do operator in between to accomplish that.
this._nextPage$.pipe(
tap(()=>{//loading},
exhaustMap(nextPageNumber => this.getPage(nextPageNumber),
tap(()=>{// stop loading})
).subscribe();