2017-05-26 70 views
12

我想用Angular 2+中的ControlValueAccessor接口创建一个自定义表单元素。该元素将成为<select>的包装。是否有可能传播formControl属性到包装元素?在我的情况下,验证状态没有传播到嵌套选择,你可以在附加的截图中看到。我可以在Angular 2+中访问我的自定义ControlValueAccessor的formControl吗?

enter image description here

我的组件可用如下:

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' 
    }) 
    export class OptionsComponent implements ControlValueAccessor, OnInit { 

    @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>>'); 
    } 
    } 

    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; 
    } 
    } 

这是我的组件模板:

<select 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> 
+0

您可以在重新生成它吗? – yurzui

回答

5

SAMPLE PLUNKER

我看到两个选项:

  1. 从部件FormControl传播错误<select>FormControl每当<select>FormControl值改变
  2. 从部件FormControl传播的验证到<select>FormControl

娄以下变量是可用的:

  • selectModel是的<select>
  • formControlNgModel是接收作为参数的组件的FormControl

选项1:传播错误

ngAfterViewInit(): void { 
    this.selectModel.control.valueChanges.subscribe(() => { 
     this.selectModel.control.setErrors(this.formControl.errors); 
    }); 
    } 

选项2:传播校验

ngAfterViewInit(): void { 
    this.selectModel.control.setValidators(this.formControl.validator); 
    this.selectModel.control.setAsyncValidators(this.formControl.asyncValidator); 
    } 

两者之间的差异在于传播错误意味着已经有错误,而秒选项涉及第二次执行验证程序。其中一些,如异步验证器可能执行成本太高。

传播所有属性?

传播所有属性没有通用的解决方案。各种性质由各种指令或其他手段设定,因此具有不同的生命周期,这意味着需要特定的处理。当前的解决方案涉及传播验证错误和验证器。那里有很多物业。

请注意,通过订阅FormControl.statusChanges(),您可能会从FormControl实例获得不同的状态更改。通过这种方式,您可以获取控件是否为VALID,INVALID,DISABLEDPENDING(异步验证仍在运行)。

验证如何在引擎盖下工作?

在引擎盖下,使用指令(check the source code)应用验证器。指令有providers: [REQUIRED_VALIDATOR]这意味着自己的分层注入器用于注册验证器实例。因此,根据元素上应用的属性,指令将在与目标元素关联的注入器中添加验证器实例。

接下来,通过NgModelFormControlDirective检索这些验证器。

校验以及值存取检索等:

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) 

和分别为:

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[]) 

注意@Self()被使用,因此自己的喷射器(这是该指令是作为元件的应用)被用来获得依赖关系。

NgModelFormControlDirective有一个实例FormControl它实际上更新值并执行验证程序。

因此,与之交互的要点是FormControl实例。

此外,所有验证程序或值存取程序​​都在它们所应用的元素的注入器中注册。这意味着父母不应该访问该注射器。因此,从当前组件访问由<select>提供的喷射器将是一种不好的做法。对于选项1

示例代码(由选项2容易更换)

下面的示例具有两个验证:其中一个是必需的,另一个是迫使选项,以匹配“选项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; 
    } 
    } 
} 

用法

定义FormControl实例:

export class AppComponent implements OnInit { 

    public control: FormControl; 

    constructor() { 
    this.control = new FormControl('', Validators.compose([Validators.pattern(/^option 3$/), Validators.required])); 
    } 
... 

绑定FormControl实例的组件:

<inf-select name="myName" [formControl]="control"></inf-select> 

虚拟设置服务

/** 
* 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' }, 
    ]; 
    } 
} 
+0

你好!谢谢您的回答。但是,如何在通过FormBuilder构造表单时访问'FormControl'?在你的例子中,组件将以这种方式调用:''。 –

+0

@SlavaFominII让我们说你使用FormBuilder创建一个FormGroup。然后你可以通过formGroup.controls ['someControl']'或formGroup.get('someControl')'来获得控制权。 – andreim

+0

是的,我知道,谢谢。我已经澄清了我的问题在这里:https://stackoverflow.com/questions/44731894/get-access-to-formcontrol-from-the-custom-form-component-in-angular –

相关问题