Reputation: 23
I will try use async pipe for show/hide some waiting message.
app.component.html
<ng-template #isWait>
<h1>Please wait</h1>
</ng-template>
<div *ngIf="wait | async; else isWait">
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
</div>
<button (click)="toggle()">Toggle</button>
<!-- Doesn't change -->
<div style="margin:1rem;">
Doesn't change here
<span style="color:red;">Value is {{wait | async}}</span>
(AppComponent)
</div>
app.component.ts
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name: string = 'Angular 5';
wait: Observable<boolean>;
constructor(public waitService: WaitService) {
this.wait = this.waitService.wait;
}
toggle() {
this.waitService.toggle();
}
}
wait.service.ts
export class WaitService {
wait: Observable<boolean>;
private _wait: boolean = false;
private _onChange: EventEmitter<boolean> = new EventEmitter();
constructor() {
this.wait = Observable.create((obs: any) => {
obs.next(this._wait);
this._onChange.subscribe((w: boolean) => {
if (this._wait !== w) {
this._wait = w;
obs.next(this._wait);
}
});
});
}
toggle() {
this._onChange.emit(!this._wait);
}
}
I have WaitService with wait property and method toggle for switching wait. When I try toggle wait, it works in one case, but doesn't work for each others (as I expected).
So, It confused me. I will try figure out, why it happens.
Example: https://stackblitz.com/edit/angular-fwghfj
If you click on Toggle button nothing happen with wait message, but subscription in Wait2Component will be emitted and and will be write to console output each time when you clicked on toggle.
But after commented line with subscription, show and hide wait message will be work ok, but in other places wait still have no change.
Sure I can change wait to boolean and don't care about this situation with async, but this unexpected behavior for me.
Upvotes: 2
Views: 6601
Reputation: 1513
app.component.ts
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name: string = 'Angular 5';
wait: Observable<boolean>;
constructor(public waitService: WaitService) {
this.wait = this.waitService.waitingStatus();
}
toggle() {
this.waitService.toggle();
}
}
Your service
@Injectable()
export class WaitService {
private currentWaitingStatus: boolean = false;
private waitingStatus$: Subject<boolean> = new BehaviorSubject<boolean>(this.currentWaitingStatus)
waitingStatus(): Observable<boolean> {
return this.waitingStatus$
}
toggle() {
this.currentWaitingStatus= !this.currentWaitingStatus
this.waitingStatus$.next(this.currentWaitingStatus)
}
}
I prefer to have a method which return my property than to put it public.
Because, your component must not know how is the inside of your service. If your service is use by n clients, tomorrow you want to change the name of your property you must change it everywhere
Otherwise, if you abstract this behind a method you change juste at one place in your service
Upvotes: 0
Reputation: 96891
The problem is that for each subscriber the callback for Observable.create
is called and that's the place where you modify the state of this._wait
. In other words, when you call toggle()
the this._onChange.subscribe()
's next
handle is called eg. five times or maybe even more. But only the first call notifies its observer because of this._wait !== w
.
One simple way to avoid this is to share the same source Observable with the share()
operator.
this.wait = Observable.create((obs: any) => {
// ...
}).share();
But even better solution would be not calling subscribe()
inside Observable.create
and just using an operator chain with do()
to perform any side effects:
this.wait = this._onChange
.filter((w: boolean) => w !== this._wait)
.do((w: boolean) => this._wait = w)
.share();
This should produce the same results.
See you updated demo: https://stackblitz.com/edit/angular-nbgmz7?file=app%2Fservices%2Fwait.service.ts
Upvotes: 1
Reputation: 3783
You can write WaitService like this instead:
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
@Injectable()
export class WaitService
{
private readonly isWaiting = new BehaviorSubject(false);
public readonly wait$ = this.isWaiting.asObservable();
public toggle()
{
this.isWaiting.next(!this.isWaiting.value);
}
}
Upvotes: 3