Mike Ryan is one of the author of NgRx. He has a YouTube video “You might not need NgRx”, which showcases when you can use NgRx. Some people just don’t care about those use cases, and use it in the wrong scenarios, writing unnecessary complex code, which make others hate NgRx more. While it is important to document when to use it, and how to use it, it is equally important to document when you should not to use it. In this post, I am trying to document some of the anti-pattern in using NgRx.

All the source code of the post can be found here. The live demo is here

1. Use NgRx store for temporary variable.

NgRx store is for state management. But sometimes people use it for non-state management.So what is state in terms of Single Page Application. I can’t find a definite answer. So Here is my definition.

If a variable last after a function execution is finished, it is a state.

In terms of Angular application, the state can be a property of component, a property of services. But the bottom line is that it should be shared and lasting.

Here is a use case. User enter a token in a text box, application validate it by calling remote service, and later display the result. This is very simple use case. But it NgRx is so cool, let’s use it here. First let’s write the following code implement NgRx store.

export const antiPatternActions = createActionGroup({
  source: "anti pattern",
  events: {
    "token updated": props<{ token: string }>(),
    validate: props<{ token: string }>(),
    "validate success": props<{ isValid: boolean }>(),
  },
});

export const antiPatternFeature1 = createFeature({
  name: "antiPattern",
  reducer: createReducer(
    {
      token: "",
      isValid: false,
    },
    on(antiPatternActions.tokenUpdated, (state, { token }) => {
      return { ...state, token };
    }),
    on(antiPatternActions.validateSuccess, (state, { isValid }) => {
      return { ...state, isValid };
    })
  ),
});

@Injectable({
  providedIn: "root",
})
export class AntiPatternEffects {
  constructor(private actions$: Actions, private api: XApi) {}

  validate$ = createEffect(() =>
    this.actions$.pipe(
      ofType(antiPatternActions.validate),
      concatMap(({ token }) => {
        return this.api.validate(token);
      }),
      map((result) => {
        return antiPatternActions.validateSuccess({ isValid: result });
      })
    )
  );
}

export const antiPatternModule1 = [
  StoreModule.forFeature(antiPatternFeature1),
  EffectsModule.forFeature(AntiPatternEffects),
];

After that we can consume the state in compoment

@Component({
  selector: "app-anti-pattern1",
  template: `
    <div>
      <h1>anti pattern: use store for temporary value</h1>
      <p>token: <input [formControl]="txtToken" /> (valid value is 'super')</p>

      <p>isValid: {{ isValid$ | async }}</p>
    </div>
  `,
  styles: [],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AntiPattern1Component implements OnDestroy {
  private destroy$ = new Subject<void>();

  constructor(private store: Store) {
    // when input change, save it to token slot in store
    this.txtToken.valueChanges
      .pipe(takeUntil(this.destroy$))
      .subscribe((token) => {
        this.store.dispatch(antiPatternActions.tokenUpdated({ token }));
      });

    // when token slot change, trigger to side effect to validate it
    this.store
      .select(antiPatternFeature1.selectToken)
      .pipe(takeUntil(this.destroy$))
      .subscribe((token) => {
        this.store.dispatch(antiPatternActions.validate({ token }));
      });
  }
  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  txtToken = new FormControl();

  // subscribe isValid slot in store
  isValid$ = this.store.select(antiPatternFeature1.selectIsValid);
}

Here is what we want to do

  1. subscribe to user’s input, when it changed, save to state.token
  2. subscribe to state.token, when it changed, trigger an effect, which will call api method, which will update state.isValid
  3. display state.isValid in the page when it is updated.

It looks like we have achieve a lot by writting lots of code. But the token, and isValid are some private state of the the component. They are not meant shared at all. In fact, we don’t need an extra variable state to hold the token, because user’s input is the token.

Yeah, yeah, yeah. You make laugh at the code now. You may say, NgRx is over-engineered. No. We simply use it in the wrong use case. How much code you can save if we don’t use NgRx. Here is the code.

@Component({
  selector: "app-anti-pattern1-fix",
  template: `
    <div>
      <h1>fix for anti pattern: use store for temporary value</h1>
      <p>token: <input [formControl]="txtToken" /> (valid value is 'super')</p>
      <p>isValid: {{ isValid$ | async }}</p>
    </div>
  `,
  styles: [],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AntiPattern1FixComponent {
  constructor(private api: XApi) {}

  txtToken = new FormControl();

  isValid$ = this.txtToken.valueChanges.pipe(
    switchMap((value) => this.api.validate(value))
  );
}