【问题标题】:Custom validator not getting called自定义验证器没有被调用
【发布时间】:2016-12-31 01:32:26
【问题描述】:

在使用 Angular 2 自定义验证器时遇到问题 食谱中的方法。

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

我正在尝试有一个文本输入字段,它只允许在 一定范围(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 个组件,但我只有 与“参数”有关,其他三个工作正常):

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] + ' ';
            }
         }
      }
   }
}

认为我已经按照食谱正确连接了所有内容,但是 显然我错过了一些东西,因为自定义验证器不是 开火。

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

谢谢。

【问题讨论】:

  • 在编辑中:我故意在主题中加入产品名称(“Angular”)和特定版本,因为当提出新问题并提示可能类似问题的列表时,这些主题行是提问者关于问题是否相关的唯一指示。当我问起时,我看到了有关 Symfony(不管是什么)和 Angular 2 RC2 的问题,但直到我深入研究时我才知道。对回滚编辑有强烈反对吗?

标签: angularjs validation angular


【解决方案1】:

您在模块中声明并希望与其他模块共享的所有组件、管道、指令,您还需要将它们添加到exports

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

【讨论】:

    猜你喜欢
    • 2015-01-02
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    • 2021-07-28
    • 1970-01-01
    • 2016-12-04
    • 2013-11-15
    • 2023-03-10
    相关资源
    最近更新 更多