SAMPLE PLUNKER
widzę dwie opcje:
- propagować błędów w składniku
FormControl
do <select>
FormControl
gdy wartość <select>
FormControl
zmienia
- propagować weryfikacje składnika
FormControl
do <select>
FormControl
Poniżej następujących zmiennych są dostępne:
selectModel
jest NgModel
z <select>
formControl
jest FormControl
składnika otrzymanego jako argument
Wariant 1: propagacji błędów
ngAfterViewInit(): void {
this.selectModel.control.valueChanges.subscribe(() => {
this.selectModel.control.setErrors(this.formControl.errors);
});
}
Wariant 2: propagują Walidatorów
ngAfterViewInit(): void {
this.selectModel.control.setValidators(this.formControl.validator);
this.selectModel.control.setAsyncValidators(this.formControl.asyncValidator);
}
Różnica między nimi polega na tym, że propagowanie błędów oznacza posiadanie już błędów, natomiast opcja sekund polega na ponownym uruchomieniu walidatorów. Niektóre z nich, podobnie jak walidatory asynchroniczne, mogą być zbyt kosztowne do wykonania.
Propagowanie wszystkich właściwości?
Nie istnieje ogólne rozwiązanie do propagowania wszystkich właściwości. Różne właściwości są ustalane na podstawie różnych dyrektyw lub innych środków, a zatem mają inny cykl życia, co oznacza, że wymagają szczególnej obsługi. Obecne rozwiązanie dotyczy propagowania błędów walidacji i walidatorów. Tam jest wiele dostępnych nieruchomości.
Należy zauważyć, że można uzyskać różne zmiany stanu z instancji FormControl
, subskrybując numer FormControl.statusChanges()
. W ten sposób można uzyskać informacje o tym, czy kontrola jest VALID
,, DISABLED
lub PENDING
(sprawdzanie poprawności asynchronicznej jest nadal uruchomione).
W jaki sposób walidacja działa pod maską?
Pod maską weryfikatory są stosowane za pomocą dyrektyw (check the source code). Dyrektywy mają providers: [REQUIRED_VALIDATOR]
co oznacza, że własne hierarchiczne wtryskiwacz jest używany do rejestracji tej instancji walidatora. Tak więc w zależności od atrybutów zastosowanych w elemencie, dyrektywy doda instancje validatora na wtryskiwacza powiązanego elementu docelowego.
Następnie te weryfikatory są pobierane przez NgModel
i FormControlDirective
.
Validators oraz akcesorów wartości są pobierane jak:
constructor(@Optional() @Host() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
i odpowiednio:
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[])
Zauważ, że @Self()
jest używany, więc własnego wtryskiwacza (elementu, do którego dyrektywa jest Stosowana) stosuje się w celu otrzymania zależności.
NgModel
i FormControlDirective
mają instancję FormControl
, która faktycznie aktualizuje wartość i wykonuje weryfikatory.
Dlatego głównym punktem interakcji jest instancja FormControl
.
Również wszystkie zatwierdzającym lub Akcesory wartości są zarejestrowane w wtryskiwacza elementu, do których są stosowane. Oznacza to, że rodzic nie powinien uzyskać dostęp do wtryskiwacza. Więc byłoby to złe praktyki w celu uzyskania dostępu z bieżącego składnika wtryskiwacz dostarczonego przez <select>
.
Kod próbki dla wariantu 1 (łatwo wymieniony przez opcja 2)
Poniższy przykład ma dwa weryfikatorów: jedną, która jest wymagana, a inny, który to wzór, który wymusza na możliwość, aby dopasować opcję „3”.
The PLUNKER
options.component.ts
import {AfterViewInit, Component, forwardRef, Input, OnInit, ViewChild} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, NgModel} from '@angular/forms';
import {SettingsService} from '../settings.service';
const OPTIONS_VALUE_ACCESSOR: any = {
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => OptionsComponent)
};
@Component({
providers: [OPTIONS_VALUE_ACCESSOR],
selector: 'inf-select[name]',
templateUrl: './options.component.html',
styleUrls: ['./options.component.scss']
})
export class OptionsComponent implements ControlValueAccessor, OnInit, AfterViewInit {
@ViewChild('selectModel') selectModel: NgModel;
@Input() formControl: FormControl;
@Input() name: string;
@Input() disabled = false;
private propagateChange: Function;
private onTouched: Function;
private settingsService: SettingsService;
selectedValue: any;
constructor(settingsService: SettingsService) {
this.settingsService = settingsService;
}
ngOnInit(): void {
if (!this.name) {
throw new Error('Option name is required. eg.: <options [name]="myOption"></options>>');
}
}
ngAfterViewInit(): void {
this.selectModel.control.valueChanges.subscribe(() => {
this.selectModel.control.setErrors(this.formControl.errors);
});
}
writeValue(obj: any): void {
this.selectedValue = obj;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
options.component.html
<select #selectModel="ngModel"
class="form-control"
[disabled]="disabled"
[(ngModel)]="selectedValue"
(ngModelChange)="propagateChange($event)">
<option value="">Select an option</option>
<option *ngFor="let option of settingsService.getOption(name)" [value]="option.description">
{{option.description}}
</option>
</select>
options.component.SCSS
:host {
display: inline-block;
border: 5px solid transparent;
&.ng-invalid {
border-color: purple;
}
select {
border: 5px solid transparent;
&.ng-invalid {
border-color: red;
}
}
}
Zastosowanie
Określenie wystąpienie FormControl
:
export class AppComponent implements OnInit {
public control: FormControl;
constructor() {
this.control = new FormControl('', Validators.compose([Validators.pattern(/^option 3$/), Validators.required]));
}
...
Bind wystąpienie składowej FormControl
:
<inf-select name="myName" [formControl]="control"></inf-select>
Dummy SettingsService
/**
* TODO remove this class, added just to make injection work
*/
export class SettingsService {
public getOption(name: string): [{ description: string }] {
return [
{ description: 'option 1' },
{ description: 'option 2' },
{ description: 'option 3' },
{ description: 'option 4' },
{ description: 'option 5' },
];
}
}
można odtworzyć go w plunker? – yurzui