自定义验证程序未被调用

问题描述:

无法通过烹饪手册获得使用 方法的Angular 2自定义验证程序。自定义验证程序未被调用

我的主要目标是学习Angular(也就是说,我是一个noob,总是为 )。

我想要一个文本输入字段,只允许在 特定范围(1-26)中的整数。我想我会变得聪明,并写一个验证器 ,它需要一个任意的数字和范围列表(例如, “1,3,5,7,11-19,100-200”),并检查以确定给定的值是 的允许值之一。

我的问题是,自定义验证器(由allowedNumericValuesValidator?返回的匿名函数 ?)永远不会被调用。 (但是请注意,内置的required验证程序运行得很好。)对于 而言,也不会调用 AllowedNumericValuesDirective中定义的任何方法。验证器源码 代码本身被加载,但这就像事情一样。

使用Angular 2.2.3,angular-cli 1.0.0-beta.22-1。 浏览器是Chrome 55.0.2883.95(64位)

来源是https://github.com/JohnL4/Diaspora,但我会尽量把 的相关部分放在这里。

这里是我做了什么:

我的验证程序是这样的:

import { Directive, Input, OnChanges, SimpleChanges } from '@angular/core'; 
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } from '@angular/forms'; 

const SELECTOR: string = 'allowedNumericValues'; // <---------------- breakpoint here 

class Range 
{ 
    constructor (public Low: number, public High: number) {} 
} 

export function allowedNumericValuesValidator(anAllowedValuesSpec: string): ValidatorFn 
{ 
    let errors: string[] = []; // (<---- breakpoint here) Errors from parsing allowed values specification 
    let ranges : Range[] = []; // Allowed ranges, used in validation. 
    let rangeSpecs = anAllowedValuesSpec.split(/\s*,\s*/); 
    for (let r of rangeSpecs) 
    { 
     let ends : string[] = r.split(/\s*-\s*/); 
     if (ends.length == 1) 
     { 
     let end : number = Number(ends[0]); 
     if (isNaN(end)) 
      errors.push(r + " is NaN"); 
     else 
      ranges.push(new Range(end, end)); 
     } 
     else if (ends.length == 2) 
     { 
     let low:number = Number(ends[0]); 
     let high:number = Number(ends[1]); 
     if (isNaN(low) || isNaN(high)) 
      errors.push(r + " has NaN"); 
     else 
      ranges.push(new Range(low, high)); 
     } 
     else 
     errors.push(r + " has bad syntax"); 
    } 
    if (errors.length > 0) 
     throw new Error(errors.join("; ")); 

    return (control: AbstractControl): {[key: string]: any} => { 
     const numberToBeValidated = control.value; // <---------------- breakpoint here 
     const num = Number(numberToBeValidated); 
     if (isNaN(num)) 
     return {SELECTOR: {numberToBeValidated}}; 
     let isGood: boolean = false; 
     for (let r of ranges) 
     { 
     if (r.Low <= num && num <= r.High) 
     { 
      isGood = true; 
      break; 
     } 
     } 
     return isGood ? null : {SELECTOR: {numberToBeValidated}}; 
    }; 
} 

@Directive({ 
    selector: '[' + SELECTOR + ']', // Note: not using extra '[ngForm]' or '[ngModel]' here because the cookbook example doesn't. 
    providers: [{provide: NG_VALIDATORS, useExisting: AllowedNumericValuesDirective, multi: true}] 
}) 
export class AllowedNumericValuesDirective implements Validator, OnChanges 
{ 
    @Input() allowedNumericValues: string; 
    private valFn = Validators.nullValidator; // <---------------- breakpoint here 

    ngOnChanges(changes: SimpleChanges): void 
    { 
     const change = changes[ SELECTOR]; 
     if (change) 
     { 
     const val: string = change.currentValue; 
     this.valFn = allowedNumericValuesValidator(val); 
     } 
     else 
     this.valFn = Validators.nullValidator; 
    } 

    validate(control: AbstractControl): {[key: string]: any} 
    { 
     return this.valFn(control); 
    } 
} 

如果我把一个断点在const SELECTOR分配,它被击中 (调用堆栈是半打__webpack_require__ ),但是 之后没有任何东西被调用(其他断点都没有命中, 也没有任何console.log()我发送的语句被调用。

shared.module.ts,在同一shared目录中 验证,看起来是这样的:

import { NgModule } from '@angular/core'; 
import { CommonModule } from '@angular/common'; 
import { SharedComponent } from './shared.component'; // angular-cli stuck this in here; I'm not sure I need it. 
import { AllowedNumericValuesDirective } from './allowed-numeric-values.directive'; 

@NgModule({ 
    imports: [ 
    CommonModule 
    ], 
    declarations: [SharedComponent, AllowedNumericValuesDirective] 
}) 
export class SharedModule { } 

app.module.ts看起来像这样(我有4个组成部分,但我只 关心“PARAMS”一个与其他三个工作正常):

import { BrowserModule } from '@angular/platform-browser'; 
import { NgModule } from '@angular/core'; 
import { FormsModule } from '@angular/forms'; 
import { HttpModule } from '@angular/http'; 
import { RouterModule } from '@angular/router'; 

import { SharedModule } from './shared/shared.module'; 
import { AppComponent } from './app.component'; 
import { ClusterDetailsComponent } from './cluster-details/cluster-details.component'; 
import { DotComponent } from './dot/dot.component'; 
import { GeneratorParamsComponent } from './generator-params/generator-params.component'; 
import { TabsComponent } from './tabs/tabs.component'; 
import { XmlComponent } from './xml/xml.component'; 

@NgModule({ 
    declarations: [ 
     AppComponent, 
     ClusterDetailsComponent, 
     DotComponent, 
     GeneratorParamsComponent, 
     TabsComponent, 
     XmlComponent 
    ], 
    imports: [ 
     BrowserModule, 
     FormsModule, 
     HttpModule, 
     SharedModule, // I don't need to put the validator in the `declarations` property above, do I? 
     RouterModule.forRoot([ 
     { 
      path: '',   // Initial load. 
      redirectTo: '/params', 
      pathMatch: 'full' 
     }, 
     { 
      path: 'params', 
      component: GeneratorParamsComponent 
     }, 
     { 
      path: 'details', 
      component: ClusterDetailsComponent 
     }, 
     { 
      path: 'xml', 
      component: XmlComponent 
     }, 
     { 
      path: 'dot', 
      component: DotComponent 
     } 
     ]) 
    ], 
    providers: [], 
    bootstrap: [AppComponent] 
}) 
export class AppModule { } 

generator-params.component.html看起来是这样的:

<p></p> 

<form #parmsForm="ngForm" class="form-horizontal"> <!-- "form-horizontal" is Bootstrap class --> 
    <div class="form-group">       <!-- "form-group" is Bootstrap class --> 
    <label for="numSystems" class="col-sm-3 control-label"> <!-- "control-label" is the class for labels in HORIZONTAL forms. --> 
     Number of systems in cluster 
    </label> 
    <div class="col-sm-2"> 
     <input id="numSystems" name="numSystems" type="text" class="form-control" 
      required maxlength="2" allowedNumericValues="1-26" 
      [(ngModel)]="numSystems"> 
    </div> 
    <div *ngIf="formErrors.numSystems" class="col-sm-6 alert alert-danger"> 
     {{ formErrors.numSystems }} 
    </div> 
    </div> 
    <div class="form-group"> 
    <div class="col-sm-offset-3 col-sm-9"> 
     <div class="checkbox"> <!-- "checkbox" is Bootstrap class --> 
     <label for="slipstreamsHighLow"> 
      <input id="slipstreamsHighLow" name="slipstreamsHighLow" type="checkbox" /> 
      Slipstreams Differentiated Between High & Low Slipknots 
     </label> 
     </div> 
    </div></div> 
    <div class="form-group"> 
    <div class="col-sm-offset-3 col-sm-9"> 
    <button id="goBtn" (click)="generateCluster()" class="btn btn-default btn-warning" 
      title="Obviously, this will hammer your existing cluster. Be sure you have it backed up or otherwise saved, or that you don't care." 
      > 
     Go! 
    </button> 
    <button id="revertBtn" class="btn btn-default" (click)="revertParams()">Revert</button> 
    </div> 
    </div> 
</form> 

最后,generator-params.component.ts看起来是这样的:

import { Component, OnInit, ViewChild } from '@angular/core'; 
import { FormsModule, NgForm } from '@angular/forms'; 

import { Cluster } from '../cluster'; 

@Component({ 
    selector: 'app-generator-params', 
    templateUrl: './generator-params.component.html', 
    styleUrls: ['./generator-params.component.css'] 
}) 
export class GeneratorParamsComponent implements OnInit { 

    private numSystems: string; // = "6"; 
// get numSystems() : string { return this._numSystems; } 
// set numSystems(value: string) { this._numSystems = value; } 

    parmsForm: NgForm; 
    @ViewChild('parmsForm') currentForm: NgForm; 

    formErrors = { 
     'numSystems': '' 
    }; 

    validationMessages = { 
     'numSystems': { 
     'required': "A number of systems is required", 
     'allowedNumericValues': "Value must be one of the allowed numeric values" 
     } 
    }; 

    private _cluster: Cluster; 

    constructor(aCluster: Cluster) 
    { 
     this._cluster = aCluster; 
     if (aCluster && aCluster.numSystems) 
     this.numSystems = aCluster.numSystems.toString(); 
     // this.strSystems = this.numSystems.toString(); 
     // this.numSystems = "6"; // aCluster.numSystems.toString(); 
    } 

    ngOnInit() 
    { 
    } 

    /** See form validation cookbook "recipe" 
    */ 
    ngAfterViewChecked() 
    { 
     this.formChanged(); 
    } 

    public generateCluster() 
    { 
     // this._cluster = new Cluster(this.numSystems); // Don't new up, just update in place? 
     this._cluster.numSystems = Number(this.numSystems); 
     // this.cluster.generateSystems(); 
    } 

    public revertParams() 
    { 
     this.numSystems = this._cluster.numSystems.toString(); 
    } 

    formChanged() 
    { 
     if (this.currentForm === this.parmsForm) return; 
     this.parmsForm = this.currentForm; 
     if (this.parmsForm) 
     this.parmsForm.valueChanges.subscribe(data => this.onValueChanged(data)); 
    } 

    onValueChanged(data?: any) 
    { 
     if (!this.parmsForm) return; 
     const form = this.parmsForm.form; 

     for (const field in this.formErrors) 
     { 
     this.formErrors[field] = ''; 
     const control = form.get(field); 
     if (control && control.dirty && !control.valid) 
     { 
      const messages = this.validationMessages[field]; 
      for (const key in control.errors) 
      { 
       this.formErrors[field] += messages[key] + ' '; 
      } 
     } 
     } 
    } 
} 

认为我按照食谱正确迷上了一切,但显然 我错过了什么,因为自定义验证是不是 射击。

谁能告诉我我错过了什么?

感谢。

+0

在编辑:我故意把一个产品名称(“Angular”)以及该主题中的特定版本,因为当询问新问题并提示可能类似的问题列表时,这些主题行是询问者是否具有关于问题是否相关的唯一指示。当我问到的时候,我看到了Symfony(无论那是什么)和Angular 2 RC2的问题,但是直到我钻完为止我才知道。任何强烈的反对将编辑推回? – JohnL4

所有你想与其他模块共享件,管道,指示您在模块声明的,你还需要将它们添加到exports

@NgModule({ 
    declarations: [ MyDirective ], 
    exports: [ MyDirective ] 
}) 
export class SharedModule {}