Melbourne, Australia
11 January 2020
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 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.
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.
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
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
});
}
};
}
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.
You can grab full source code from github