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);
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.
I have this link that changes the final of the url with a time stamp:
getAvatar(channelId): BehaviorSubject<string> {
return new BehaviorSubject(`${link}?${new Date().getTime()}`);
}
And in the Avatar Component, that is a child of at least 10 other components i call this subscribe:
getAvatar() {
this.userService.getAvatar(this.channelId)
.subscribe(res => {
this.avatar = res;
this.cdr.detectChanges();
this.cdr.markForCheck();
});
}
OBS: Im using the OnPush changeDetection strategy
And in another component i have a function that changes this profile picture inside the link:
this.userService.changeProfilePicture(picture, this.myChannelId)
.subscribe(
() => {
this.loading.hide();
this.userService.getAvatar(this.id);
this.screenService.showToastMessage('Foto de perfil alterada.', true);
}
As you can see, im recalling the getAvatar() function that returns a BehaviorSubject to generate another link and the AvatarComponent doenst detect the change of the behaviorSubject, what im doing wrong ?
And theres another way to recall the getAvatar() function of all the AvatarComponent instances to reload each avatar instance ?
OBS2: I tried to use of rxjs operator, creating a new Observable(), tried the Subject class, all of those seems to not get detected by the subscribe inside the AvatarComponent
OBS3: I tried to get the AvatarComponent with #ViewChild() and call this this.avatarCmp.getAvatar(); to reload the avatar, but reloads just one instance of the Avatar Component
your service needs to be more like this:
private avatarSource = new BehaviorSubject(`${link}?${new Date().getTime()}`);
avatar$ = this.avatarSource.asObservable();
setAvatar(channelId): BehaviorSubject<string> {
this.avatarSource.next(`${link}?${new Date().getTime()}`);
}
then you need to subscribe to avatar$ and update with setAvatar
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.
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.
My Service class contains code as :
Service.ts
//all imports are done
#Injectable()
export class Service{
constructor(private http: Http) { }
getGoogle():Observable<any> {
console.log("Inside service");
return this.http.get('https://jsonplaceholder.typicode.com/posts/1');
}
}
My page component where I make use of service.
Page.ts
//all imports are done
export class Page1{
constructor(private service: Service, private navCtrl: NavController) { }
async get() {
console.log("inside get method");
const data = await this.service.getGoogle().toPromise();
console.log('The response is' , data);
}
}
I have got the required result, but as to understand the concept of Observables and Observer, my Observable should have an Observer sitting to subscribe.Then why should'nt the code const data = await this.service.getGoogle.subscribe().toPromise() does not work here and shows error that property toPromise() does not exists on type Subscription.
I saw the official resource of toPromise() where I found that it is used with .just().toPromise().Then I found .just() API which states that
The just() method emits its parameter(s) as OnNext notifications, and
after that, it emits an OnCompleted notification.
So It is using the features of subscribe here then why it is not used with .subscribe()?
To get the values from an observable, you'll subscribe() on the observable. This starts the observable and it will send you the values it produces.
If you rather want to use a Promise you can call toPromise() on the observable instead. This will make the observable behave like a regular promise.
Under the covers, toPromise() calls subscribe() on the observable and waits for it to send complete(). When the complete() signal is received, the promise will resolve the last emitted next() signal.
toPromise() looks a bit like this:
myObservable.takeLast().subscribe(value => resolve(value));