Search code examples
angularangular-reactive-formsangular-formsangular-formbuilder

Angular 6 reactive form. How to build form for multiple languages


I am working on a CMS with Angular 6, I have a form to create a page with two languages or more depending on supported locales from the settings. Firstly I am getting the supported locales from api and let's say the response has 2 locales (en - fr). So for each language I want to build a tab inside the form with its own locale like en['title'], en['body'], fr['title'], fr['body']. I tried to build the form like this:

let forms = [];
for(let lang of this.supportedLocales) {
    forms.push(this.fb.group({
      title: ['', [Validators.required]],
      body: ['', [Validators.required]]
    }))
  }

  this.form = this.fb.group({
    template: ['default', [Validators.required]],
    is_home: [0],
    translatable: this.fb.array(forms)
  });
}

And in HTML:

<div class="tab-content">
    <div *ngFor="let lang of supportedLocales"
         class="tab-pane {{lang.locale === currentLang ? 'active' : ''}}"
         id="{{ 'tab_'+ lang.locale}}">

      <div class="form-group">
        <label [attr.for]="'title'+lang.locale">{{ translateField('page::pages.title') }}</label>
        <input formControlName="?" [attr.id]="'title'+lang.locale" class="form-control">
      </div>
    </div>
  </div>

How to define the formControlName for title field ? I have tried to use FormArray but it causes a problem and the browser not responding!

What should I do or what is the best approach for this case?


Solution

  • I'll just use .map on this.supportedLocales and then generate FormGroups using the getFormGroupForLocale method. It's private as it won't be used in your Template.

    Now once the form is ready, first I'll bind the whole form to a form tag using [formGroup]="form". After that, since my form has a FormArray, I'll first have to create a wrapping div for it. To this div I'll assign formArrayName="translatable" which will map this div to my translatable FormArray in the form FormGroup.

    Inside this, I'll use *ngFor="let group of localeFormArray; let i = index;" to loop through all the FormGroups in my FormArray and bind them to a wrapping div using <div [formGroupName]="i">. Notice how I'm using formGroupName as property binding syntax and assigning it the index i of the FormGroup in my FormArray

    Finally inside each input tag of this div, I can then use formControlName="title" and formControlName="body" to bind to the FormControls in each FormGroups in the FormArray.

    Here's how:

    <form [formGroup]="form">
      template
      is_home
      translatable
      <label for="template">Template</label>
      <input type="text" id="template" formControlName="template">
    
      <br><br>
    
      <label for="is_home">Is Home</label>
      <input type="text" formControlName="is_home" id="is_home">
    
      <br><br>
    
      <h1>translatable</h1>
      <div formArrayName="translatable">
        <div *ngFor="let group of localeFormArray; let i = index;">
          <div [formGroupName]="i">
            <label 
              [for]="'title'+supportedLocales[i].lang.locale">
              {{ translateField('page::pages.title') }}
            </label>
            <input 
              formControlName="title" 
              [id]="'title'+supportedLocales[i].lang.locale" 
              class="form-control">
    
            <br><br>
    
            <label 
              [for]="'title'+supportedLocales[i].lang.locale">
              {{ translateField('page::pages.title') }}
            </label>
            <input 
              formControlName="body" 
              [id]="'title'+supportedLocales[i].lang.locale" 
              class="form-control">
    
          </div>
        </div>
      </div>
    
    
    </form>
    

    And for the Component Class:

    import { Component } from '@angular/core';
    import { FormGroup, FormControl, FormBuilder, Validators, FormArray } from '@angular/forms';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      form: FormGroup;
      supportedLocales = [
        { lang: { locale: 'en-US' } },
        { lang: { locale: 'en-FR' } },
      ];
    
      constructor(private fb: FormBuilder) {}
    
      ngOnInit() {
        this.form = this.fb.group({
          template: ['default', [Validators.required]],
          is_home: [0],
          translatable: this.fb.array(this.supportedLocales.map(locale => this.getFormGroupForLocale(locale)))
        });
      }
    
      private getFormGroupForLocale(language) {
        return this.fb.group({
          title: [language.lang.locale + 'Title', [Validators.required]],
          body: [language.lang.locale+'Body', [Validators.required]]
        });
      }
    
      ...
    
      get localeFormArray() {
        return (<FormArray>this.form.get('translatable')).controls;
      }
    
    }
    

    Here's a Sample StackBlitz for your ref.