Search code examples
angularngrx

How to dynamically add formgroup controls to formarray in angular while the state is managed by ngrx-forms?


I am using an ngrx-forms library for managing states of my form.

This is the library : https://ngrx-forms.readthedocs.io/

It works well with simple inputs for me. But when it comes to dynamic controls, I am not sure how to use it.

For example, let's say we have a form:

myform = this.fb.group({
  topic: '',
  books: [''],
  languages: [''],
})

now the languages controls looks like this :

{language: '', code: ''}

How can I dynamically add the above control to the languages array of form builder when the user clicks add languages button? I can do it with a regular FormBuilder. No problem.

but when it comes to managing state using ngrx coupled with ngrx-forms how can I create a reducer function to add language controls dynamically?


Solution

  • Author of ngrx-forms here.

    Take the following component that builds a form as you describe above with @angular/forms.

    export class ExampleComponent {
      private formBuilder = new FormBuilder();
      private form = this.formBuilder.group({});
    
      buildForm() {
        this.form = this.formBuilder.group({
          topic: '',
          languages: this.formBuilder.array([]),
        });
    
        this.addLanguageControlGroup();
      }
    
      addLanguageControlGroup(lang?: string, code?: string) {
        const newControl = this.formBuilder.group({
          language: lang || '',
          code: code || '',
        });
    
        (this.form.get('languages') as FormArray).push(newControl);
      }
    }
    

    With ngrx-forms the code would be a reducer like this (using ngrx v8+):

    interface MyFormValue {
      topic: string;
      languages: LanguageFormValue[];
    }
    
    interface LanguageFormValue {
      language: string;
      code: string;
    }
    
    const INITIAL_FORM_VALUE: MyFormValue = {
      topic: '',
      languages: [
        {
          language: '',
          code: '',
        },
      ],
    };
    
    const myFormReducer = createReducer(
      {
        formState: createFormGroupState('MY_FORM', INITIAL_FORM_VALUE),
      },
      onNgrxForms(),
    );
    

    In your component, you could then have something like this:

    export class NgrxFormsExampleComponent {
      @Input() formState: FormGroupState<MyFormValue>;
    
      constructor(private actionsSubject: ActionsSubject) { }
    
      addLanguageControlGroup(lang?: string, code?: string) {
        this.actionsSubject.next(
          new AddArrayControlAction<LanguageFormValue>(
            this.formState.controls.languages.id,
            {
              language: lang || '',
              code: code || '',
            },
          )
        );
      }
    }
    

    Instead of using the built-in AddArrayControlAction from ngrx-forms you could also create your own action and then add the control in the reducer, like this:

    const addLanguageControlGroup = createAction(
      'MY_FORM/addLanguageControlGroup',
      (lang?: string, code?: string) => ({ lang, code }),
    );
    
    const myFormReducer = createReducer(
      {
        formState: createFormGroupState('MY_FORM', INITIAL_FORM_VALUE),
      },
      onNgrxForms(),
      on(addLanguageControlGroup, ({ formState }, { lang, code }) => ({
        formState: updateGroup(formState, {
          languages: addArrayControl({
            language: lang || '',
            code: code || '',
          }),
        }),
      })),
    );
    
    export class NgrxFormsExampleComponent {
      @Input() formState: FormGroupState<MyFormValue>;
    
      constructor(private actionsSubject: ActionsSubject) { }
    
      addLanguageControlGroup(lang?: string, code?: string) {
        this.actionsSubject.next(addLanguageControlGroup(lang, code));
      }
    }
    

    I hope this helps.