Alberto Chiesa
Alberto Chiesa

Reputation: 7360

RxJS - managing an array of expiring observables

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

Answers (2)

Alberto Chiesa
Alberto Chiesa

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

Fan Cheung
Fan Cheung

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

Related Questions