1

I'm creating a chat application in Angular 4.

I need to execute a method (markMessageAsRead) after 3 conditions have become true (Chrome tab is active, the sidebar is opened and sidebar-tab is 'chat').

Subscribing to each individual function won't fix this problem since you have to check the other 2 conditions again when 1 condition changed.

I can't find a solution to wait for all of these 3 conditions to be true. Any ideas?

  • I can add an eventlistener to document.hasFocus(), to watch for changes.
  • The other 2 conditions are BehaviorSubjects where I could subscribe to, to watch for changes.
Sibiraj
  • 4,486
  • 7
  • 33
  • 57
Kevin
  • 2,760
  • 3
  • 15
  • 30

2 Answers2

3

I would use the Observable.zip operator:

About zip operator

What you need to do first is to convert your focus event to an observable using Observable.fromEvent, then use the zip operator along with filter like this:

Observable.zip(Observable.fromEvent(focusEvent),
               behaviourSubjectOne.asObservable(),
               behaviourSubjectTwo.asObservable())
  .filter((arrayOfResults) => !arrayOfResults.some((val) => val === false))
  .subscribe(() => {
      // all three conditions are fulfilled, do what you need here
  });

EDIT:

After some revision, I came to this plunk:

https://plnkr.co/edit/spNldiSb1WwgmrHrB6u1

Posting the code if plunker ever is lost(omitting HTML):

export class App {
  name:string;
  output: string = '';

  subjectOne: BehaviorSubject <boolean> = new BehaviorSubject <boolean>(false);
  subjectTwo: BehaviorSubject <boolean> = new BehaviorSubject <boolean>(false);

  constructor() {
    this.name = `Angular! v${VERSION.full}`;
    Observable.combineLatest(Observable.merge(Observable.fromEvent(window, 'focus').map((ev) => 'focus'),
                   Observable.fromEvent(window, 'blur').map((ev) => 'blur')).startWith('blur'),
                   this.subjectOne.asObservable(),
                   this.subjectTwo.asObservable())
              .map((state) => [state[0] === 'focus', state[1], state[2]])
              // filter would go here
              .subscribe((array) => {
                this.output = JSON.stringify(array);
              });
  }
}

This is a complicated observable chain, but I will try to explain what I did:

First of all I used the combineLatest operator to enable listening to changes of state of the three booleans. As the first parameter I passed the merged observables of the focus and the blur event. Since only one can fire at a time this always gives the current state of the window. StartWith was given for an initial state. Then the subjects were passed in.

Then I mapped it to an array on which the OP can apply the filter above.

EldarGranulo
  • 1,575
  • 1
  • 14
  • 37
  • Seems that this should be able to do the job. Still it does not always function as expected. For example in this case: Receive message when window has no focus and a tab, other than chat, is selected. Then focus on the window and select the correct tab and it logs nothing. Code so far: `Observable.zip( Observable.fromEvent(window, 'focus'), this.layoutService.quickview.isOpen.asObservable(), this.layoutService.quickview.activeTabIsChat.asObservable()) .filter((arrayOfResults) => !arrayOfResults.some((val) => val === false)); .subscribe(() => { console.log('Complete.'); });` – Kevin Aug 23 '17 at 09:27
  • I think you should first add a map operator to fromEvent. Observable.fromEvent(...).map(() => true) because the filter expects booleans in the arrayOfResults. Secondly, this does not take different tabs into consideration. You should be able to add another observable to zip that would give you a boolean that indicates which tab is selected('chat' or not). – EldarGranulo Aug 23 '17 at 09:37
  • I'll try that first thing what you've mentioned (.map). The 3rd condition (.activeTabIsChat) returns a BehaviorSubject which is true when the active tab is 'chat' and false when another tab is active. So I've already got that covered I guess? – Kevin Aug 23 '17 at 10:02
  • Hmm the BehaviorSubjects are where it fails here. I've disabled the focus() check (only enabled the activeTab and isOpen check) which are behaviorSubjects. The condition does never evaluate to true. `const observable = Observable.zip( this.layoutService.quickview.isOpen.asObservable(), this.layoutService.quickview.activeTabIsChat.asObservable()).filter((arrayOfResults) => !arrayOfResults.some((val) => val === false));` – Kevin Aug 23 '17 at 14:41
  • @Kevin Can you make a small plunker with the necessary elements so I can test and debug this? Also try without the filter block and just log the output of the arrayOfResults – EldarGranulo Aug 23 '17 at 14:45
  • I've subscribed on that observable.zip to log the 'result array'. The BehaviorSubject change seems to not always trigger an update on the observable.zip. (see: http://imgur.com/a/IRwRr, where I've subscribed to both behavior subjects and on the observable.zip. The observable.zip logs (boolean, boolean) where boolean 1 = 'isOpen' and boolean 2 = 'chatIsActiveTab'. – Kevin Aug 24 '17 at 08:44
  • The video might be more clear than the picture :) https://www.dropbox.com/s/wakbdo76bash5jq/video_008_a3cf2rR423_weird_behavior_observable_zip.mov?dl=0 – Kevin Aug 24 '17 at 08:53
  • Can you try with the combineLatest operator instead of zip? – EldarGranulo Aug 24 '17 at 09:20
  • @Kevin I believe combineLatest is the solution. Please check with this plunker: https://plnkr.co/edit/jNoc1c7NmPNeyaidgdXL – EldarGranulo Aug 24 '17 at 10:31
  • Thanks man! You're officially awesome. Works perfect:) Don't fully understand all of it, but I'm gonna dive into it. I have expanded your code with an if on newStates.every() to check for all the conditions, and this works like a charm. Thanks again! – Kevin Aug 24 '17 at 14:44
0

You asked me about whether to use a BehaviourSubject vs an Observable. For that info I'd recommend you read the following quote:

BehaviorSubject is a type of subject, a subject is a special type of observable so you can subscribe to messages like any other observable. The unique features of BehaviorSubject are:

  • It needs an initial value as it must always return a value on subscription even if it hasn't received a next()
  • Upon subscription it returns the last value of the subject. A regular observable only triggers when it receives an onnext
  • at any point you can retrieve the last value of the subject in a non-observable code using the getValue() method.

Unique features of a subject compared to an observable are:

  • It is an observer in addition to being an observable so you can also send values to a subject in addition to subscribing to it.

In addition you can get an observable from behavior subject using the asobservable() method on BehaviorSubject.

Taken from: BehaviorSubject vs Observable?

After reading that you should be able to answer your own question.

Your problem

You do not have to "watch 3 values" (yourself), you just have to watch 1. This is due to the way booleans work.

e.g

$scope.something = $scope.first && $scope.second && $scope.third

This can produce a couple of results:

| First     | Second    | Third     | Result    |
|-------    |--------   |-------    |--------   |
| True      | True      | True      | True      |
| True      | False     | False     | False     |
| False     | True      | False     | False     |
| False     | False     | True      | False     |
| False     | False     | False     | False     |

So what I'd do personally is just watch a single value, namely the combination of all 3.

Your new question of watching a variable altogether

You can use a subscribeto fire an event once a condition is reached:

.subscribe(() => {
    DoFuctionCall();
})

Read the quote (and that question) again to figure it out :D

Rick van Lieshout
  • 2,276
  • 2
  • 22
  • 39