Koobz866
Koobz866

Reputation: 196

How to prevent Angular store dispatch from creating infinite loop

I came across a problem recently and have written a simplified version of it. I was wondering what the best approach was to solve an issue like this. Say you have a simple State with an updateState action and a selector which selects that value from the state like so

const defaults: AppStateModel = {
  test: 'test'
};

@State<AppStateModel>({
  name: 'app',
  defaults
})
@Injectable()
export class AppState {

  @Selector()
  static testSelector(state: AppStateModel): string {
    return state.test;
  }

  @Action(UpdateTest)
  updateState(ctx: StateContext<AppStateModel>, { nextTest }: UpdateTest) {
    ctx.patchState({
      test: nextTest
    });
  }
}

I then call a function from a component which takes in as an input the async pipe from the selector and then dispatches the action like so.

@Component({
  selector: 'app-root',
  template: `
    <input [(ngModel)]="name">
    <p>Test: {{testFunction(testSelector$ | async)}}</p>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @Select(AppState.testSelector)
  testSelector$: Observable<string>;
  name: string;

  constructor(private store: Store) {
  }

  testFunction(test: string): string {
    console.log("testprint");
    this.store.dispatch(new UpdateTest(this.name));
    return test;
  }
}

Now this is going to create an infinite loop as the state updates and the component keeps receiving the updated state value, but how would you prevent this from happening? What if I really need the component to call a function with an input from the selector but that function also needs to change the same state?

Upvotes: 0

Views: 853

Answers (1)

Garth Mason
Garth Mason

Reputation: 8011

You can use the output of the selector as an action input no worries. I know it's a contrived example for SO but its a bit unclear as to when you want to dispatch the action to mutate the state - it'd be driven off an event or another Observable typically - if you have it in the template then it'll be called on every change detection cycle (which is most certainly not what you want).

Usually you want to trigger the change off some input - then mutate the state via an action.

@Component({
  selector: 'app-root',
  template: `
    <input [ngModel]="name" (ngModelChange)="nameChanged($event)>
    <p>Test: {{ testSelector$ | async }}</p>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @Select(AppState.testSelector) testSelector$: Observable<string>;

  name: string;

  constructor(private store: Store) {
  }

  nameChanged(newName: string): void {
    this.store.dispatch(new UpdateTest(newName));
  }
}

Upvotes: 1

Related Questions