Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping angular reactive form component with validator

Working with angular 7 and Bootstrap 4, I want to wrap my bootstrap 4 inputs in a custom component in order to reduce the boilerplate in my templates.

I want that the final main component look like:

<form [formGroup]="myForm" (submit)="submit(myForm.value)">

    <app-form-control label="Lastname" placeholder="Lastname" formControlName="lastName"></app-form-control>
    <app-form-control label="Firstname" placeholder="Firstname" formControlName="firstName"></app-form-control>

    <button class="pull-right" type="submit">
        SUBMIT
    </button>
    <button (click)="reset()">
        RESET
    </button>
</form>

Where my formGroup is created like this:

public createFormGroup() {
    return this.fb.group({
        firstName: [null, Validators.required],
        lastName: [null, Validators.required],
    });
}

The template of app-form-control should look like this:

<div class="form-group row">
  <label class="col-2 col-form-label">{{label}}</label>
  <div class="col-10">
    <input class="form-control" placeholder="{{placeholder}}" [formControlName]="formControlName" autocomplete="nope"/>
  </div>
</div>

But I don't know how to write the component (in TypeScript). How do I bind the outer formControlName attribute to the inner input field? How to make validation work?

like image 654
fluminis Avatar asked Dec 09 '18 13:12

fluminis


People also ask

How do I add validation to reactive form?

In a reactive form, the source of truth is the component class. Instead of adding validators through attributes in the template, you add validator functions directly to the form control model in the component class. Angular then calls these functions whenever the value of the control changes.

How do I add custom validators dynamically in reactive form?

We can add Validators dynamically using the SetValidators or SetAsyncValidators. This method is available to FormControl, FormGroup & FormArray. There are many use cases where it is required to add/remove validators dynamically to a FormControl or FormGroup.


2 Answers

The "key" is using viewProvider. You use a @Input set to give value to a formControl, see stackblitz. The "magic" is that if equal refered to formControl in the "children" or form.get('input1') in the parent

@Component({
  selector: 'app-form-control',
  template: `
  <div class="form-group row">
  <label class="col-2 col-form-label">{{label}}</label>
  <div class="col-10">
    <input class="form-control" placeholder="{{placeholder}}"
    [formControl]="formControl" autocomplete="nope"/>
  </div>
  </div>
  <!--you can control the properties of formControl-->
  {{formControl.valid}}{{formControl.touched}}}
`,
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]})
export class HelloComponent {

  formControl: FormControl;

  constructor(private parentF: FormGroupDirective) { }

  @Input() 
  set controlName(value) {
    this.formControl = this.parentF.form.get(value) as FormControl
  }

  @Input() label: string;
  @Input() placeholder: string;

}

And call the component this way:

<form [formGroup]="myForm" (submit)="submit(myForm.value)">
    <app-form-control label="Lastname" placeholder="Lastname" controlName="lastName"></app-form-control>
</form>

Update well, (after a year) take account the stackblitz was erroneous. when you (click) in buttons create a new Form:

 this.form=this.createForm({note:'lll'})

This "break" the relationship between the component and the form because the relation is about the older form -only change if change the @Input "nameControl". So the correct is use a setValue to give a new value to the form.

like image 113
Eliseo Avatar answered Oct 06 '22 18:10

Eliseo


You can implement by implementing ControlValueAccessor. Lets do the step by step process by building a TextBoxComponent.

step 1: Creating NG_VALUE_ACCESSOR for textfield as TEXTBOX_VALUE_ACCESSOR.

const TEXTBOX_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => TextBoxComponent),
  multi: true,
}; 

step 2: implemnting ControlValueAccessor to our component TextBoxComponent.

export class TextBoxComponent implements ControlValueAccessor{
...
...
}

step 3: define unimplemnted methods of ControlValueAccessor. The detailed code of TextBoxComponent as below.

@Component({
  selector: "text-box",
  template: `

    <div class="form-group row">
      <label class="col-2 col-form-label">{{label}}</label>
      <div class="col-10">
        <input class="form-control" placeholder="{{placeholder}}" [(ngModel)]="inputValue" />
      </div>
    </div>
  `,
  providers: [TEXTBOX_VALUE_ACCESSOR],
})

export class TextBoxComponent implements ControlValueAccessor {
  private _inputValue: any = '';
  private _onTouchedCallback: () => {};
  private _onChangeCallback: (_:any) => {};

  @Input("label") label: string = "Your Label";
  @Input("placeholder") placeholder: string = "Your Placeholder";


  get inputValue(): any {
    return this._inputValue;
  }
  set inputValue(value: any) {
    if (value !== this._inputValue) {
      this._inputValue = value;
      this._onChangeCallback(value);
    }

    this._onTouchedCallback();

  }

  //From ControlValueAccessor interface
  writeValue(value: any) {
    this._inputValue = value;
  }

  //From ControlValueAccessor interface
  registerOnChange(fn: any) {
    this._onChangeCallback = fn;
  }

  //From ControlValueAccessor interface
  registerOnTouched(fn: any) {
    this._onTouchedCallback = fn;
  }
}

How to use :

<form [formGroup]="formGroup">
  <text-box formControlName="textboxControl" label="My Label" placeholder="My Placeholder"></text-box>

  <pre>{{formGroup.value | json}}</pre>
</form>

The complete code is available in stackblitz.

like image 28
RANJIT PATRA Avatar answered Oct 06 '22 17:10

RANJIT PATRA