2017-04-11 98 views
6

我创建了一个衍生自this blog post的角度分量。我使用反应形式,我想在窗体控件上获取错误,组件本身会有一个程式化的错误消息,当控件出现错误时它会呈现。但是,当我尝试将NgControl类注入组件时,我得到循环引用问题,那么如何访问控件上的错误?如何从实现ControlValueAccessor的组件获取对FormControl的引用?

下面是当前的代码,它尚未完成,但是它应该给的什么,我试图完成的基本思想:

import { Component, Output, EventEmitter, Input, forwardRef } from '@angular/core'; 
import { 
    NgControl, 
    NG_VALUE_ACCESSOR, 
    ControlValueAccessor, 
    Validator, 
    AbstractControl, 
    FormControl, 
    NG_VALIDATORS 
} from '@angular/forms'; 

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

    private propagateChange = (_: any) => { }; 
    private propagateTouch = (_: any) => { }; 

    @Input('label') label: string; 
    @Input('type') type: string; 
    @Input('id') id: string; 
    @Input('formControlName') formControlName: string; 
    @Input('error') error: string; 
    @Input('classes') classes: any; 

    private value: string; 
    private data: any; 

    constructor() { 
     debugger; 
    } 

    private onChange(event) { 
     this.data = event.target.value; 
     this.propagateChange(this.data); 
     this.propagateTouch(this.data); 
    } 

    writeValue(obj: any): void { 
     this.data = obj; 
    } 
    registerOnChange(fn: any): void { 
     this.propagateChange = fn; 
    } 
    registerOnTouched(fn: any): void { 
     this.propagateTouch = fn; 
    } 
} 

模板文件:

<div class="form-field-input-component"> 
    <input id="{{id}}" 
      type="{{type}}" 
      class="form-field-input" 
      [value]="data" 
      (change)="onChange($event)" 
      (keyup)="onChange($event)" /> 
    <span class="context-icon fa fa-lock"></span> 
    <span class="info-icon fa fa-info-circle"></span> 
    <!-- I will have an NGIF here to check for errors before rendering the error --> 
    <div class="form-error"> 
     {{ error }} 
    </div> 
</div> 

回答

1

我希望通过某种依赖注入或者声明式的方式来做到这一点。由于我找不到使用这些方法的任何东西,我将分享我如何解决这个问题。

我刚刚添加formGroup作为组件的输入参数,以及formControlName开始传递我能够获得对控件的引用。

这里是我的组件

//Typescript code file for component 
/// ... necessary imports 
@Component({ 
    selector: 'form-field-input', 
    templateUrl: './form-field-input.component.html', 
    styleUrls: ['./form-field-input.component.less'], 
    providers: [{ 
     provide: NG_VALUE_ACCESSOR, 
     useExisting: forwardRef(() => FormFieldInputComponent), 
     multi: true 
    }] 
}) 
export class FormFieldInputComponent implements ControlValueAccessor { 

    private propagateChange = (_: any) => { }; 
    private propagateTouch = (_: any) => { }; 

    @Input('label') label: string; 
    @Input('type') type: string; 
    @Input('id') id: string; 
    @Input('contextIconName') contextIconName: string; 

    //Here I take in both the parent form and the form control name 
    //in ngOnInit I throw if there is no parent form passed 
    @Input('formControlName') formControlName: string; 
    @Input('parentForm') parentForm: FormGroup; 

    @Input('classes') classes: any; 
    @Input('errorDefs') errorDefs: any; 

    private error: string; 
    private value: string; 
    private data: any; 
    private control: AbstractControl; 

    constructor() {} 

    ngOnInit() { 
     if (!this.parentForm) { 
      throw "Form Field input component must be a part of a form group" 
     } 

     //It ain't pretty but here we get access to the control and all of it's errors 
     this.control = this.parentForm.get(this.formControlName); 
     if (!this.control) { 
      throw "Form Field input component must be a part of a form group" 
     } 
    } 

    private setError() { 
     if (this.errorDefs && this.control.errors) { 
      var errorKeys = Object.keys(this.control.errors).filter(x => !!x); 
      if (errorKeys) { 
       var errorKey = errorKeys[0]; 
       var error = this.errorDefs[errorKey] || null; 
       this.error = error; 
       return; 
      } 
     } 
     this.error = null; 
    } 

    //Now on our on change event we can propagate the events 
    //To the registered handlers, which should set the form field errors 
    //and at the end we can check the reference to the control for those errors 
    //so that we can display the appropriate messages 
    private onChange(event) { 
     this.data = event ? event.target.value : this.data; 
     this.propagateChange(this.data); 
     this.propagateTouch(this.data); 
     this.setError(); 
    } 

    writeValue(obj: any): void { 
     this.data = obj; 
    } 
    registerOnChange(fn: any): void { 
     this.propagateChange = fn; 
    } 
    registerOnTouched(fn: any): void { 
     this.propagateTouch = fn; 
    } 
} 

//HTML template file 
<div class="form-field-input-component"> 
    <input id="{{id}}" 
      type="{{type}}" 
      class="form-field-input {{class}}" 
      [value]="data" 
      (change)="onChange($event)" 
      (keyup)="onChange($event)" 
      (blur)="onChange($event)" /> 
    <span class="context-icon fa {{contextIconName || 'fa-cog'}}"></span> 
    <span class="info-icon fa fa-info-circle" *ngIf="error"></span> 
    <div class="form-field-error" *ngIf="error"> 
     {{ error }} 
    </div> 
</div> 

//EXAMPLE USAGE: 
<form novalidate [formGroup]="myFormGroup"> 
    <form-field-input 
         formControlName="firstName" 
         [parentForm]="myFormGroup" 

         <!-- example: When the Validators.required sets it's error message we can map that to a user friendly error --> 
         [errorDefs]="{ 
          'required': 'this field is required' 
         }" 
         <!-- Other inputs and stuff--> 
         > 
    </form-field-input> 
</form> 
+0

如果您正在过formGroup您的自定义控制的最终结果,为什么你需要ControlValueAccessor和自定义控制所有的执行? 只需将FormGroup的引用传递给自定义控件中模板的父级潜水并完成。不需要任何ControlValueAccessor和实现。 –

相关问题