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

Issue

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.

Leave a Reply

(*) Required, Your email will not be published