0
votes

[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?

1

1 Answers

2
votes

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.