Search code examples
javascriptreactive-programmingfrprxjs

How to correctly apply distinctUntilChanged for DOM operations minimization


I'm trying to do a popup menu using RxJS, here is where I stopped: http://jsbin.com/coqulamamo/1/edit?html,js,output

I've mapped to 2 main eventstreams, menuActivation and menuDeactivation:

  • menuActivation emits a popup container whenever any of its chidren emits mouseenter or focusin.
  • menuDeactivation emits the last menuActivation element after its first mouseleave or the next time any non-descendent element emits focusin.

For each popup container on menuActivation, active class is appended; For each on menuDeactivation, active class is removed from the element.

So far, so good. But now, how do I prevent too much DOM operations? There is no need to activate a menu when it is already active, the same applies to deactivations, but I don't want to keep states in a Rx.Subject, there can be any length of popup menus distributed on the page.

I tried .distinctUntilChanged() but when a popup container is emitted sequentially in menuActivation and then in menuDeactivation, the next time the same popup won't be emitted on menuActivation.

Is there a way to allow a popup container to surpass menuActivation.distinctUntilChanged() after be project on menuDeactivation?


Solution

  • distinctUntilChanged can only work on a single observable. To work around this you can create an observable which combines your two observables. Something like

    var menuAct2 = menuActivation.map(function(menu) { return { type: "act", menu: menu}; })
    
    var menuDeact2 = menuDeactivation.map(function(menu) { return { type: "deact", menu: menu}; })
    
    var actDeact = menuAct2.merge(menuDeact2)
      .distinctUntilChanged(function (a) {return a;}, function (a, b) {
      return a.menu === b.menu && a.type === b.type;
    });
    
    actDeact
    .filter(function (a) { return a.type === "act";})
    .map(function (a) {return a.menu;})
    .subscribe(function(menu) {
      console.log("Focou:", menu);
      menu.classList.add("MainMenu__ListContainer--Active");
    });
    
    actDeact
    .filter(function (a) { return a.type === "deact";})
    .map(function (a) {return a.menu;})
    .subscribe(function(menu) {
      console.log("Desfocou:", menu);
      menu.classList.remove("MainMenu__ListContainer--Active");
    });
    

    You basically create an observable by merging your two observables, do a distinctUntilChanged on that observable, and split it back into two observables to use in the rest of your code.

    Sorry if this is not proper javascript, I'm not too familiar with the language.