Reputation: 7360
I'm trying to implement a notification widget. Every now and then, a message (a "toast") will be displayed in a corner of the browser window. I would like to have the message auto-hide after, say, 30 seconds. What I would also like is that, if more messages arrive in the meantime, the widget will display each one over the other, in a stack, progressively hiding the expiring messages.
I could implement everything with just setTimeout
, but I'm pretty sure RxJS can make use of a nicer approach, and I find the documentation very difficult to navigate. (I'm struggling with http://reactivex.io/documentation/operators.html, http://rxmarbles.com/, blog posts etc etc)
Does anyone know of one or more operators that could help in fulfilling the requirement?
Thanks!
Edit: an example timeline
0 - empty window
1 - show "A"
5 - show "B" (A and B are both shown)
29 - show "C" (A, B and C are stacked)
31 - A disappears (only B and C remain)
35 - B disappears (C shown)
59 - C disappears
Upvotes: 0
Views: 500
Reputation: 7360
Finally I found the kind of operator I was looking for: the scan
operator (documentation here and here).
I based my implementation on a Subject
emitting "hide" and "show" events, and on a BehaviourSubject
accumulating the events onto an array.
The elements relevant for RxJS are as follows:
export class Message {
id: string;
text: string;
type: MessageType;
}
export class MessageAction {
action: 'show' | 'hide';
msg: Message;
}
...
export class NotificationComponent {
...
messages = new BehaviorSubject<Message[]>([]);
msgsSource = new Subject<MessageAction>();
constructor() {
this.msgsSource.scan((arr: Message[], msgAction: MessageAction) => {
return (msgAction.action === 'show') ?
arr.concat(msgAction.msg) :
arr.filter(msg => msg.id !== msgAction.msg.id);
}, []).subscribe(this.messages);
}
show(text: string, type: MessageType, autoHideAfter: number = DEFAULT_MESSAGE_TIMEOUT) {
const msg: Message = {
id: '' + (this.msgIdx++),
text: text,
type: type
};
this.msgsSource.next({ action: 'show', msg: msg });
setTimeout(() => {
this.msgsSource.next({ action: 'hide', msg: msg });
}, autoHideAfter);
}
hide(id: string) {
this.msgsSource.next({ action: 'hide', msg: { id: id } as Message });
}
...
}
Upvotes: 0
Reputation: 11370
You can use subject as a queue to push toaster to it. delay() lets you adjust the duration of toaster staying on screen
let a=Rx.Observable.of('a')
let b=Rx.Observable.of('b')
let c=Rx.Observable.of('c')
let queue=new Rx.Subject()
queue.mergeMap(res=>res)
.do(res=>{
// you can make toast appear now
console.log(res,':appearing')
})
.delay(2000)
.do(res=>{
// you can make toast disappear now
console.log(res,':disappearing')
}).subscribe()
setTimeout(()=>queue.next(a))
setTimeout(()=>queue.next(b),2000)
setTimeout(()=>queue.next(c),4000)
https://jsfiddle.net/7jcsLd4o/
Upvotes: 3