Skip to content

Instantly share code, notes, and snippets.

@blueskyfish
Last active May 25, 2018 16:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blueskyfish/cad8cc225fa9c206451cda26379b90e5 to your computer and use it in GitHub Desktop.
Save blueskyfish/cad8cc225fa9c206451cda26379b90e5 to your computer and use it in GitHub Desktop.
Redux Trigger

ReduxTrigger

A small angular service for trigger action when certain conditions occur.

Using the Angular module @angular-redux/store.

Example

Trigger a list of Redux Action

// pure selector function
export const shouldLoadResourcesAfterLogin: ConditionTriggerFunc<IAppState> = (state: IAppState): boolean => {
  return !!state.user && !!state.user.id && state.user.id > 0;
}

@NgModule({
...
})
export class AppComponent implements OnInit {

  constructor(private trigger: TriggerService) {}
  
  ngOnInit() {
    this.triggerBulk(shouldLoadResourcesAfterLogin)
      .withActions(ACTION_LOAD_WORKSPACE, ACTION_RESTORE_WORKSPACE, ...);
  }
}

Trigger Action

export const loadUnreadedMessage: SelectorTriggerFunc<IAppState, number[]> = (state: IAppState) => {
  const messageIdList: number[] = state.messagePool
    .filter((m: IMessage) => m.status === MessageStatus.NEW)
    .map((m: IMessage) => m.id);
  return messageIdList.length === 0 ? null : messageIdList;
};

@NgModule({
...
})
export class AppComponent implements OnInit, OnDestroy {

  private subscription: Subscription = null;

  constructor(private trigger: TriggerService) {}
  
  ngOnInit() {
    this.subscription = this.trigger(loadUnreadedMessage)
      .withAction(ACTION_LOAD_AND_UNREAD_MESSAGE, false); // false -> null values are ignored
  }
  
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}
import { NgRedux } from '@angular-redux/store';
import { Injectable } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { IAppState } from '../store/app.models';
/**
* The selector callback with the redux store that must return an boolean value.
*
* If the value is true, then it trigger the action
*/
export type ConditionTriggerFunc<T> = (state: T) => boolean;
/**
* The selector callback with the redux store that returns the value from type `R`.
*
* If the selector observable is trigger, the action for the redux store is trigger.
*/
export type SelectorTriggerFunc<T, R> = (state: T) => R;
/**
* Interface for the service call `#triggerIf(...)`.
*/
export interface IReduxTriggerCondition {
/**
* Add the action with the optional payload being triggered if the condition is return true.
*
* @param {string} action the redux action
* @param {*} [payload] the payload for the redux action
* @return {Subscription | null} the subscription fur unsubscribe or the null value.
*/
withAction(action: string, payload?: any): Subscription | null;
}
/**
* Interface for the service call `#trigger<R>(...)`
*/
export interface IReduxTriggerNullAllow {
/**
* Add the action that is trigger when the observable is execute.
*
* If the parameter `withNullAllow` is true (the default), then it trigger as well, otherwise it trigger only
* if a value is not null is returns
*
* @param {string} action the redux action
* @param {boolean} [withNullAllow] the flag for trigger the action (default is true).
* @return {Subscription | null} the subscription fur unsubscribe or the null value.
*/
withAction(action: string, withNullAllow?: boolean): Subscription | null;
}
export interface IReduxTriggerBulk {
/**
* Add the list of actions if the condition selector is returns true.
*
* The redux actions can not have a payload value.
*
* @param {string} actions the list of action.
* @return {Subscription | null} the subscription fur unsubscribe or the null value.
*/
withActions(...actions: string[]): Subscription | null;
}
@Injectable()
export class ReduxTriggerService {
private timeout: number = 100;
constructor(private store: NgRedux<IAppState>) {
}
/**
* Trigger a redux action if the condition selector is returns a true value.
*
* @param {ConditionTriggerFunc<IAppState>} condition
* @return {IReduxTriggerCondition}
*/
triggerIf(condition: ConditionTriggerFunc<IAppState>): IReduxTriggerCondition {
return {
withAction: (action: string, payload: any = null) => {
if (!action || action === '') {
throw new Error('triggerIf requires an action');
}
return this.store.select(condition)
.subscribe((value: boolean) => {
if (value === true) {
setTimeout(() => {
this.store.dispatch({
type: action,
payload
},);
}, this.timeout);
}
});
}
};
}
/**
* Trigger the given action with the result of the selection with the redux store.
*
* @param {SelectorTriggerFunc<IAppState, R>} selector
* @return {IReduxTriggerNullAllow}
*/
trigger<R>(selector: SelectorTriggerFunc<IAppState, R>): IReduxTriggerNullAllow {
return {
withAction: (action: string, withNullAllow?: boolean): Subscription => {
if (!action || action === '') {
throw new Error('trigger requires an action');
}
// adjust the parameter "withNullAllow
withNullAllow = typeof withNullAllow === 'boolean' ? withNullAllow : false;
return this.store.select(selector)
.subscribe((value: R) => {
const running = withNullAllow === true || !!value;
if (running) {
setTimeout(() => {
this.store.dispatch({
type: action,
payload: value
});
}, this.timeout);
}
});
}
};
}
/**
* Trigger the condition selector callback if there returns true.
*
* **Note**: The return value of the method `withAction` is always `null`.
*
* @param {ConditionTriggerFunc<IAppState>} condition
* @return {IReduxTriggerCondition}
*/
triggerOnce(condition: ConditionTriggerFunc<IAppState>): IReduxTriggerCondition {
return {
withAction: (action: string, payload: any = null) => {
if (!action || action === '') {
throw new Error('triggerOny requires an action');
}
let isRunning: boolean = false;
const subscribe = this.store.select(condition)
.subscribe((value: boolean) => {
if (isRunning) {
return;
}
if (value === true) {
isRunning = true;
// trigger is running
setTimeout(() => {
this.store.dispatch({
type: action,
payload
});
}, this.timeout);
// unsubscribe
setTimeout(() => {
if (subscribe && !subscribe.closed) {
subscribe.unsubscribe();
}
}, this.timeout);
}
});
return null;
}
};
}
/**
* Trigger an bulk of redux actions once if the condition selector subscribes the value is true.
*
* **Note**: The return value of the method `withAction` is always `null`.
*
* @param {ConditionTriggerFunc<IAppState>} condition
* @param {boolean} [asyncCalling] the flag manages calling of the redux store dispatch (Default is `true`)
* @return {IReduxTriggerBulk}
*/
triggerBulk(condition: ConditionTriggerFunc<IAppState>, asyncCalling: boolean = true): IReduxTriggerBulk {
return {
withActions: (...actions: string[]) => {
if (!actions || actions.length === 0) {
throw new Error('triggerBulk requires a list of action.');
}
let isRunning: boolean = false;
const subscribe = this.store.select(condition)
.subscribe((value: boolean) => {
if (isRunning) {
return;
}
if (value === true) {
// trigger running
isRunning = true;
if (asyncCalling) {
actions.forEach((action: string) => {
setTimeout(() => {
this.store.dispatch({type: action});
}, this.timeout);
});
} else {
// sync calling
actions.forEach((action: string) => {
this.store.dispatch({type: action});
});
}
// unsubscribe
setTimeout(() => {
if (subscribe && !subscribe.closed) {
subscribe.unsubscribe();
}
}, this.timeout);
}
});
return null;
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment