问题
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:
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
来源:https://stackoverflow.com/questions/46413347/how-to-implement-automatic-form-validation-in-form-with-nested-component