How to implement automatic form validation in form

2019-04-16 02:44发布

Consider the following simple example in a component of an Angular 4 application. It shows a simple HTML form with two input fields. One input field is directly implemented, the second is within a child component:

<form #personForm="ngForm">
    <input type="text" required name="firstname [(ngModel)]="firstname"/>
    <app-smart-input required [(model)]="lastname"></app-smart-input>
    <button [disabled]="personForm.valid === false">Send</button>
</form>

The child component is defined as follows:

import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";

@Component({
    selector: "app-smart-input",
    templateUrl: "./smart-input.component.html",
    styleUrls: ["./smart-input.component.css"]
})
export class SmartInputComponent {

    ////////////////
    // PROPERTIES //
    ////////////////

    @Input() model: string;
    @Output() modelChange: EventEmitter<string> = new EventEmitter<string>();

    @Input("required") required: boolean = false;

    /////////////
    // METHODS //
    /////////////

    updateChanges() {
        this.modelChange.emit(this.model);
    }

}

with the following html:

<input type="text" [required]="required" [(ngModel)]="model" (ngModelChange)="updateChanges()" />

Now updating the models work perfectly fine (firstname and lastname are defined by the user input as expected).

What I would like to achieve is that the button gets disabled unless both fields are filled in. Note the required flag in the <input> implementations, so the values should not be null/undefined.

But unfortunately, the button is now only disabled if the firstname is not well defined. But the form doesn't care about the lastname.

How would I achieve this?

Note: Angular 2 creating reactive forms with nested components is simular, but I use a template driven form, not a reactive form. But it can be probably adapted somehow?

1条回答
叼着烟拽天下
2楼-- · 2019-04-16 03:30

You'll need to implement ControlValueAccessor if you want your SmartInputComponent to participate as part of an Angular Form.

This means providing a way for your custom component to have changes propagate between it and the parent form. Here's an implementation using your SmartInputComponent as a basis.

import { Component, OnInit, Input, Output, EventEmitter, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'

@Component({
  selector: 'app-smart-input',
  templateUrl: './smart-input.component.html',
  styleUrls: ['./smart-input.component.css'],
  providers:[ { 
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => SmartInputComponent),
    multi: true
  }]
})
export class SmartInputComponent implements ControlValueAccessor {

  @Input() model: any;

  onChange = (_: any) => {};
  onTouched = () => {};

  writeValue(obj: any): void {
    if (obj !== undefined) { 
      this.model = obj;
    }
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    //TODO: enabled/disable your input field if you need to
  }

  updateChanges(val) {
     this.onChange(val);
  }

  updateBlur(){
    this.onTouched();
  }
}

with a template like:

<input type="text" (input)="updateChanges(myTextBox.value)" (blur)="updateBlur()" #myTextBox [value]="model"/>

Then when consuming your component, have it participate in form like the standard controls (which Angular provides the ControlValueAccessor implementation for you).

<form #personForm="ngForm">
  <input type="text" required name="firstname" [(ngModel)]="firstname" name="firstName" />
  <app-smart-input required [(ngModel)]="lastname" name="secondName"></app-smart-input>
  <button [disabled]="personForm.valid === false">Send</button>

  {{personForm.value | json}}

</form>

If run this form you'll see the personForm now has both the first and second name values captured.

Also see the Angular documentation on ControlValueAccessor

查看更多
登录 后发表回答