угловой 5 вложенная форма дочерний компонент
У меня есть ряд форм (каждая управляется одним компонентом).
В этих формах есть шаблон входных данных (например, многие из них требуют разрешить ввод адреса), который я должен рефакторировать в повторно используемые компоненты, поскольку они используются в нескольких формах, и я не хочу дублировать ни их логику, ни их шаблоны.
Каждый повторно используемый компонент должен был бы
- имеют свою логику
- имеет свой шаблон, содержащий входные теги и нет
<form>
tag - имеют свои ограничения проверки клиента
- возможно получение начальных значений от своего родителя
- иметь возможность возвращать значения своих полей родительскому объекту (например,
address: {street: "...", "city": "...", ...}
) - сделать родительскую форму недействительной, если ее ограничения проверки не выполняются
- сделайте родительскую форму "тронутой", как только ее значения были изменены пользователем
Из этого урока для Angular2 я понимаю, как достичь целей 1, 2 и еще 4.
Решение в учебнике позволяет достичь и других целей, но он делает это, делая все от родителя (см. app.component.ts#initAddress
).
Как я могу достичь 3, 5, 6 и еще 7, при объявлении контроля и его ограничений внутри ребенка?
2 ответа:
Если вы хотите предоставить все в дочернем компоненте, вы можете попробовать что-то вроде этого.
import { Component, Input } from '@angular/core'; import { FormGroupDirective, ControlContainer, Validators, FormGroup, FormControl } from '@angular/forms'; @Component({ selector: 'address', template: ` <div formGroupName="address"> <input formControlName="city" placeholder="city" (blur)="onTouched" /> <input formControlName="country" placeholder="country" (blur)="onTouched" /> <input formControlName="zipCode" placeholder="zipCode" (blur)="onTouched" /> </div> `, styles: [`h1 { font-family: Lato; }`], viewProviders: [ { provide: ControlContainer, useExisting: FormGroupDirective } ] }) export class AddressComponent { private form: FormGroup; constructor(private parent: FormGroupDirective) { } ngOnInit() { this.form = this.parent.form; const city = new FormControl('', Validators.required); const country = new FormControl('', Validators.required); const zipCode = new FormControl('', Validators.required); const address = new FormGroup({ city, country, zipCode }); this.form.addControl('address', address); } }
Использование:
Пожалуйста, обратите внимание, что я используюimport { Component } from '@angular/core'; import { FormGroup } from '@angular/forms'; @Component({ selector: 'my-app', template: ` <form [formGroup]="form"> <address></address> </form> {{ form.value | json }} `, styleUrls: ['./app.component.css'], }) export class AppComponent { form: FormGroup; constructor() { this.form = new FormGroup({}); } }
ReactiveFormsModule
.Также не забудьте проверить угловые формы - Кара Эриксон. В презентации показано, как создать повторно используемый компонент адреса с реализациями как для управляемых шаблоном, так и для реактивных форм.
Вы не должны использовать такую реализацию. Он гораздо более чистый, чтобы использовать ControlValueAccessor .
ControlValueAccessor-это интерфейс, который позволяет модулю угловой формы (классический или реактивный) записывать значение или состояние и регистрировать обратный вызов для получения изменений и событий.writeValue(value: Address): void { } // Allows angular to set a default value to the component (used by FormControl or ngModel) registerOnChange(fn: (_: any) => void): void {} // Callback to be called when the component value change. registerOnTouched(fn: (_: any) => void): void { } // Callback to be called when a "touch" event occurs on the component setDisabledState(isDisabled: boolean): void { } // Allows angular to update the component disable state.
Но вам также нужно предоставить NG_VALUE_ACCESSOR
Я написал этот быстрый и грязный пример для компонента адреса:
import { Component, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Address } from './address'; @Component({ selector: 'app-address-input', template: ` <label for="number">Num: </label> <input type="number" [disabled]="disabled" name="number" id="number" (change)="numberUpdate($event)" value="{{value.num}}"/><br /> <label for="street">Street: </label> <input type="text" [disabled]="disabled" (change)="streetUpdate($event)"name="street" id="street" value="{{value.street}}" /><br /> <label for="city">City: </label> <input type="text" [disabled]="disabled" name="city" id="city" value="{{value.city}}" (change)="cityUpdate($event)" /><br /> <label for="zipCode">Zip Code: </label> <input type="text" [disabled]="disabled" name="zipCode" id="zipCode" value="{{value.zipCode}}" (change)="zipCodeUpdate($event)" /><br /> <label for="state">State: </label> <input type="text" [disabled]="disabled" name="state" id="state" value="{{value.state}}" (change)="stateUpdate($event)" /><br /> <label for="country">Country: </label> <input type="text" [disabled]="disabled" name="country" id="country" value="{{value.country}}" (change)="countryUpdate($event)" />`, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AddressInputComponent) // forward the reference, multi: true // allow multiple component in the same form }] }) export class AddressInputComponent implements ControlValueAccessor { private _onChange = (_: any) => {}; private _onTouched = (_: any) => {}; disabled = false; private _value: Address = {num: undefined, street: undefined, city: undefined, state: undefined, zipCode: undefined, country: undefined}; // current value (Address is just an interface) set value(value: Address) { // interceptor for updating current value this._value = value; this._onChange(this._value); } get value() { return this._value; } writeValue(value: Address): void { if (value && value !== null) { this._value = value; } } registerOnChange(fn: (_: any) => void): void { this._onChange = fn; } registerOnTouched(fn: (_: any) => void): void { this._onTouched = fn; // didn't used it but you should for touch screen enabled devices. } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } numberUpdate(event: any) { // additional check or process this._updateValue(event.target.value, 'num') } streetUpdate(event: any) { // additional check or process this._updateValue(event.target.value, 'street') } cityUpdate(event: any) { // additional check or process this._updateValue(event.target.value, 'city') } zipCodeUpdate(event: any) { // additional check or process this._updateValue(event.target.value, 'zipCode') } stateUpdate(event: any) { // additional check or process this._updateValue(event.target.value, 'state') } countryUpdate(event: any) { // additional check or process this._updateValue(event.target.value, 'country'); } private _updateValue(value: any, field: string) { const newValue = this._value; newValue[field] = value; this.value = newValue; } }
Тогда в форме используйте его, как и любую другую форму элемент:
<form [formGroup]="registerForm"> <app-address-input formControlName="address"></app-address-input> </form>
Вы можете добавить больше логики в компонент. Вот рабочий пример. Пожалуйста, имейте в виду, что это быстрый пример и должен быть переработан для более чистого кода.