Search code examples
angularformsngrxangular-forms

How to stop ngrx-forms reinitializing actions?


[this question is based on https://ngrx-forms.readthedocs.io/]

I've an array of topics(strings) inside my angular component. I'm using ngrx-store to manage state along with ngrx-forms for managing forms. During the initialization of the component, I dispatch some actions for each topic inside the component.


 ngOnInit(): void {
    this.formState$ = this.store.pipe(select(s => s.filterByTopics.formState))
    this.topicsOptions$ = this.store.pipe(select(s => s.filterByTopics.topicsOptions))
    Object.keys(this.topics).forEach(topic => this.store.dispatch(new CreateTopicControlAction(topic)))
  }

That works fine and the ngrx-form controls are getting added.

But the real issue is that if I again visit the same component again it reinitializes the actions (since ngOnInit contains all the actions) and spits out an error:

Uncaught Error: Group 'filterByTopicsForm.topics' already has child control '0'!

image.png

How can I prevent this?

Is there any other workaround?


Solution

  • Author of ngrx-forms here.

    This is not an issue with ngrx-forms per se, but more a general question of how to prevent double initialization when state is in the store, and therefore not coupled to a component's lifetime.

    I see multiple options here:

    1. reset the form in ngOnDestroy by dispatching a SetValueAction with an initial value as well as a ResetAction (or create your own custom action to recreate the form state in the reducer); this emulates the behaviour of @angular/forms where a form lives only as long as the owning component; here's how that could look like with a custom action with ngrx v8+:

      const resetMyForm = createAction('MY_FORM/reset');
      
      const myFormReducer = createReducer(
        {
          formState: createFormGroupState('MY_FORM', INITIAL_FORM_VALUE),
        },
        onNgrxForms(),
        on(resetMyForm, ({ formState }, { lang, code }) => ({
          formState: createFormGroupState('MY_FORM', INITIAL_FORM_VALUE),
        })),
      );
      
    2. Check if the form is already initialized in the component

      ngOnInit(): void {
        this.formState$ = this.store.pipe(select(s => s.filterByTopics.formState))
        this.topicsOptions$ = this.store.pipe(select(s => s.filterByTopics.topicsOptions))
        Object.keys(this.topics).forEach(topic => {
          this.formState$.pipe(take(1)).subscribe(formState => {
            // how exactly this `if` looks like depends on your concrete form state shape
            if (!formState.controls.topics.controls[topic]) {
              this.store.dispatch(new CreateTopicControlAction(topic))
            }
          })
        })
      }
      
    3. Check if the form is already initialized in the reducer

      const createTopicControl = createAction('MY_FORM/createTopicControl', (topic: string) => ({ topic }));
      
      const myFormReducer = createReducer(
        {
          formState: createFormGroupState('MY_FORM', INITIAL_FORM_VALUE),
        },
        onNgrxForms(),
        on(createTopicControl, (state, { topic }) => {
          if (state.formState.controls.topics.controls[topic]) {
            return state
          }
      
          // add form control ...
        }),
      );
      

    I hope this helps.