Search code examples
angularangular-reactive-formsatomic-design

Angular9's reactive forms & atomic design


I am implementing atomic design in my Angular 9 application. This means that I will build my pages with atoms, molecules & organisms. All is going fine, except for the ReactiveFormsModule.

I want to convert the <input /> to its own component, so that I don't have to duplicate the associated HTML all the time. However, reactive forms is not having any of it.

The sample below is returns an error onload: ERROR Error: No value accessor for form control with name: 'field2'

I made a StackBlitz example with the full code.

app.component.ts

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({selector: 'app-root',templateUrl: './app.component.html'})
export class AppComponent {
  form: FormGroup;

  constructor(fb: FormBuilder) {
    this.form = fb.group({
      field1: ['value1', [Validators.required]],
      field2: ['value2', [Validators.required]],
    });
  }

  onSubmit() {console.log(this.form.value);}
}

app.component.html Here I tried replacing the second input with the atom.

<form [formGroup]="form" (ngSubmit)="onSubmit();">
  <label>
    Field 1 <input formControlName="field1" />
  </label>

  <label>
    Field 2 <app-input formControlName="field2"></app-input>
  </label>

  <button type="submit">Submit</button>
</form>

input.component.ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-input',
  template: '<input [formControlName]="formControlName" />',
})
export class InputComponent implements OnInit {
  @Input() formControlName: string;

  constructor() { }

  ngOnInit(): void {
  }

}

I have tried to implement a ControlValueAccessor, through this tutorial but this resulted in weird behaviour.

Can anyone show me how to achieve this?


Solution

  • if u want to make ur life easy then use FormControl as input from ur custom component this is code from my app

    // custom component 
    @Input() set control(value: FormControl) {
        if (this._control !== value) {
          this._control = value;
        }
      }
    // tempalte
    <input [formControl]="_control">
    

    input parent control i not using formBuilder but fromControl and formGroup directly.

    name = new FormControl('');
    
    constructor(){
    let name = this.name;
    this.formGroup = new FromGroup({name });
    
    
    // template
            <custom-control [control]="name">
    

    Updated solution into the samples from the question:

    app.component.ts

    import { Component } from '@angular/core';
    import { FormGroup, FormBuilder, Validators } from '@angular/forms';
    
    @Component({selector: 'app-root',templateUrl: './app.component.html'})
    export class AppComponent {
      form: FormGroup;
    
      constructor(fb: FormBuilder) {
        this.form = fb.group({
          field1: ['value1', [Validators.required]],
          field2: ['value2', [Validators.required]],
        });
      }
    
      onSubmit() {console.log(this.form.value);}
    }
    

    app.component.html

    <form [formGroup]="form" (ngSubmit)="onSubmit();">
      <label>
        Field 1 <input formControlName="field1" />
      </label>
    
      <label>
        Field 2 <app-input [control]="form.controls.field2"></app-input>
      </label>
    
      <button type="submit">Submit</button>
    </form>
    

    input.component.ts

    import { Component, OnInit, Input } from '@angular/core';
    
    @Component({
      selector: 'app-input',
      template: '<input [formControl]="formControl" />',
    })
    export class InputComponent implements OnInit {
      @Input() set control(value: FormControl) {
        if (this.formControl !== value) {
          this.formControl = value;
        }
      }
    
      formControl: FormControl;
    
      constructor() { }
    
      ngOnInit(): void { }
    }