Vova Bilyachat

Melbourne, Australia

Angular + NGXS action executing.

11 January 2020

Problem

My project is as any other project where we need to show loading state, it doesn’t matter is it spinner or disabling a button most important I want to do it in a simple way with less as possible code since I don’t like to write much code ;)

NGXS

NGXS logo

NGXS is a state manager for angular more information can be found at official website. I like simplicity of NGXS and the way how actions are handled. Each action can be in Dispatch, Success, Error or Cancel state which means that if I need to show spinner I should be looking for my actions to be in dispatch state.

NGXS lifecycle

In many samples of how to do solve my problem people are putting variable into state to indication that action is loading and by doing this store become polluted, imagine thouthands of properties in a store which I dont need. At a time of writing NGXS labs has alpha version of a plugin which is solving my problem, but looking into code I found it a bit complex so I thought how can I make it simpler and learn something new.

Implementation

I started from looking in NGXS source code, because I’ve never done any decorators my self so why not to make a little lab. First thing I needed was access to actions, but how to inject them into decorator? Well I found my answer in sources of NGXS and have implemented ActionExecutingFactory which will keep reference to actions stream. This factory must be inject into root module so it will set static property.

@Injectable({ providedIn: "root" })
export class ActionExecutingFactory {
  static actions: Actions | undefined = undefined;

  constructor(actions: Actions) {
    ActionExecutingFactory.actions = actions;
  }
}

No decorator. Devorator it self will create two extra properties

  • First property is observable with stream of boolean which indicate if actions are executing or not.
  • Second property will keep state of actions I am watching

Component

export function ActionExecuting(actions: ActionType | ActionType[]) {
  return function(target: any, name: string) {
    const selectorFnName = "__" + name + "__action_executing$";
    const propertyActionState = "__" + name + "__actions_state";
    const createObs = self => {
      const allowedTypes = (Array.isArray(actions)
        ? actions
        : [actions]
      ).reduce((filterMap: Map<string, boolean>, klass: any) => {
        filterMap[getActionTypeFromInstance(klass)!] = true;
        return filterMap;
      }, new Map<string, boolean>());

      return ActionFactoryFactory.actions.pipe(
        filter((ctx: ActionContext) => {
          return allowedTypes[getActionTypeFromInstance(ctx.action)];
        }),
        tap(c => {
          return (self[propertyActionState][
            getActionTypeFromInstance(c.action)
          ] = c.status);
        }),
        map(
          c =>
            !!Object.values(self[propertyActionState]).find(
              ac => ac === ActionStatus.Dispatched
            )
        )
      );
    };
    if (delete target[name]) {
      Object.defineProperty(target, selectorFnName, {
        writable: true,
        enumerable: false,
        configurable: true
      });
      //Property to keep state of states we wish to watch
      Object.defineProperty(target, propertyActionState, {
        writable: true,
        enumerable: false,
        configurable: true,
        value: {}
      });
      Object.defineProperty(target, name, {
        get: function() {
          return (
            this[selectorFnName] ||
            (this[selectorFnName] = createObs.apply(null, [this]))
          );
        },
        enumerable: true,
        configurable: true
      });
    }
  };
}

Two line of code or how to use it

Now when everything is ready to go I can use my decorator inside of my components so I can display spinner when ection or multiple actions are executing.

  @ActionExecuting([FeedAnimals, CountAnimals])
  zooInProgress$: Observable<boolean>;

Each time when I dispatch async actions

    this.store.dispatch([new FeedAnimals(), new CountAnimals()]);

My zooInProgress observable will update with status if actions are still executing or not.

Source code

You can grab full source code from github