如何使用第三方JS组件进行Angular2/4验证

如何使用第三方JS组件进行Angular2/4验证,angular,validation,Angular,Validation,当谈到Angular 2验证时,我看到了不同的方法。基本的方法是使用HTML5和模板/模型绑定,或者使用带有指定验证器的表单 然而,当涉及到特殊规则时,它需要大量的编码,并且使用模板绑定(因此没有表单)会导致验证分散在HTML和TS代码中 关于验证的另一个恼人的事情是,您需要在TS中更改验证规则,并添加额外的HTML代码以在页面上呈现这些值(尽管这可以通过指令自动实现) 无论如何,理想的解决方案是使用规则对属性建模,然后生成错误消息给控件。类似于.NET fluent验证和ModelState,

当谈到Angular 2验证时,我看到了不同的方法。基本的方法是使用HTML5和模板/模型绑定,或者使用带有指定验证器的表单

然而,当涉及到特殊规则时,它需要大量的编码,并且使用模板绑定(因此没有表单)会导致验证分散在HTML和TS代码中

关于验证的另一个恼人的事情是,您需要在TS中更改验证规则,并添加额外的HTML代码以在页面上呈现这些值(尽管这可以通过指令自动实现)

无论如何,理想的解决方案是使用规则对属性建模,然后生成错误消息给控件。类似于.NET fluent验证和ModelState,或在JS world中类似于Aurelia.io验证:

  • Angular2+中是否可能存在类似的情况
  • 如何/在何处将检查(基于型号更改)连接到第三方(JS)库?如fluent ts验证程序或flient验证程序
  • 如何设置控件上的错误,特别是当我们有f.e.控件时
    控制

  • 我们首先需要将表单控件链接到输入元素。它不是现成的。到目前为止,唯一的解决方案(至少对于纯ngModel绑定)是我发布的

    下面的代码不适用于纯ngModel绑定,所以我做了很多实验。Maximillian Schwarzmuller也确认的最新版本应为:

    @Directive({
        selector: '[ngModel]', // or 'input, select, textarea' - but then your controls won't be handled and also checking for undefined would be necessary
    })
    export class NativeElementInjectorDirective {
        constructor(private el: ElementRef, private control : NgControl) {
            (<any>control.control).nativeElement = el.nativeElement;
        }
    }
    
    @指令({
    选择器:“[ngModel]”、//或“input,select,textarea”-但这样就不会处理控件,还需要检查未定义的控件
    })
    导出类NativeElementInjector指令{
    构造函数(专用el:ElementRef,专用控件:NgControl){
    (control.control).nativeElement=el.nativeElement;
    }
    }
    

    因此,如果在主模块中提供并导出此指令,它将向所有FormControl附加一个自定义nativeElement属性

    下一步是了解如何正确构建嵌套表单-请参阅

    而且不仅仅是前进ngForm,而且前进ngModelGroup

    @Component({
        selector: 'TestAddress',
        templateUrl: './address.location.html',
        viewProviders: [
            { provide: ControlContainer, useExisting: NgForm },
            { provide: ControlContainer, useExisting: forwardRef(() => NgModelGroup) },
        ],
        providers: [ MakeProvider(TestAddress) ]
    })
    export class TestProspectAddressLocation extends AbstractValueAccessor<any> {
    ...
    }
    
    现在,您可以订阅formValueChange和用户第三方进行验证,通过路径表达式访问任何控件,使用control.setErrors([])或通过jQuery aft.input添加HTML。仍在寻找:

  • 创建一些指令,可以将[(ngModel)]绑定添加到具有ngModel属性和已定义名称的每个控件,而指定的表达式应为“value”。+name属性值
  • 一种用角度组件动态替换jQuery添加的div的方法-由于div是动态的,@ViewChild(选择器)不能使用
  • 使用带有[(ngModel)]绑定的嵌套表单确实会产生奇怪的FormGroup结构,因此自定义角度组件应该自动定义ngModelGroup='name attr。“值”或单独使用ngControlOptions或其他不会造成混乱的方式
  • 因此,从上面的列表中选择2项是可行的。

    我们确实使用ngModel选择器获取所有输入的引用,当然我们有一个主控件(包含与验证相关的消息/代码),因此我们忽略了不在该容器内的控件

    现在有个窍门了:我们确实得到了输入本身的viewContainerRef。这允许我们在输入之后立即注入控件。由于注入是针对给定的viewContainer的,当输入get隐藏时,注入的控件将被销毁

    代码:

    import {Directive, OnInit, Optional, ViewContainerRef} from "@angular/core";
    import {NgControl} from "@angular/forms";
    import {ValidationContainerDirective} from "./validation-container-directive";
    
    /*
    This directive should match all inputs (ngModel)...
    If it's inside a validation container, it'll process it...
     */
    @Directive({
        selector: '[ngModel]'
    })
    export class ValidationControlForInjectorDirective implements OnInit {
    
        private initialized: boolean = false;
    
        constructor(@Optional() private validationContainer: ValidationContainerDirective,
                    private control: NgControl,
                    private viewContainerRef: ViewContainerRef,
                  ) {
    
            // skip if not inside a validation container...
            if (!this.validationContainer)
                return;
    
            // mark as initialized
            this.initialized = true;
        }
    
        ngOnInit(): void {
    
            // skip if not initialized
            if (!this.initialized)
                return;
    
            var thisLocator = this.control.path.join(".");
    
            // check inputs...
            if (!thisLocator)
                throw new Error('Validation wire issues!');
    
            // add a control aft. the input...but disable it temporarily...
            this.validationContainer.injectComponent(this.viewContainerRef, thisLocator);
        }
    
    }
    
    injectComponent(componentVcRef: ViewContainerRef, locator: string) {
    
        // create component
        var componentRef = componentVcRef.createComponent(this.componentFactoryForValidationControlFor);
    
        // basic set up (switching server/client messages on/off is done by the doCheckManagedComponents() fnc.
        (<ValidationControlFor>componentRef.instance).locator = locator;
        (<ValidationControlFor>componentRef.instance).isDynamic = true;
        (<ValidationControlFor>componentRef.instance).serverMessages = false;
        (<ValidationControlFor>componentRef.instance).clientMessages = false;
        (<ValidationControlFor>componentRef.instance).clientMessagesAlwaysVisible = this.clientMessagesAlwaysVisible;
    
        componentRef.changeDetectorRef.detectChanges();
    
        // NOTE:
        // registering of managed component is done via the component's ngOnInit function...
        // ...it was moved there, because we need to keep track also of manually added validation-message-controls!
    }
    
    上面的injectComponent方法只是设置我的控件并将其附加到viewContainerRef:

    import {Directive, OnInit, Optional, ViewContainerRef} from "@angular/core";
    import {NgControl} from "@angular/forms";
    import {ValidationContainerDirective} from "./validation-container-directive";
    
    /*
    This directive should match all inputs (ngModel)...
    If it's inside a validation container, it'll process it...
     */
    @Directive({
        selector: '[ngModel]'
    })
    export class ValidationControlForInjectorDirective implements OnInit {
    
        private initialized: boolean = false;
    
        constructor(@Optional() private validationContainer: ValidationContainerDirective,
                    private control: NgControl,
                    private viewContainerRef: ViewContainerRef,
                  ) {
    
            // skip if not inside a validation container...
            if (!this.validationContainer)
                return;
    
            // mark as initialized
            this.initialized = true;
        }
    
        ngOnInit(): void {
    
            // skip if not initialized
            if (!this.initialized)
                return;
    
            var thisLocator = this.control.path.join(".");
    
            // check inputs...
            if (!thisLocator)
                throw new Error('Validation wire issues!');
    
            // add a control aft. the input...but disable it temporarily...
            this.validationContainer.injectComponent(this.viewContainerRef, thisLocator);
        }
    
    }
    
    injectComponent(componentVcRef: ViewContainerRef, locator: string) {
    
        // create component
        var componentRef = componentVcRef.createComponent(this.componentFactoryForValidationControlFor);
    
        // basic set up (switching server/client messages on/off is done by the doCheckManagedComponents() fnc.
        (<ValidationControlFor>componentRef.instance).locator = locator;
        (<ValidationControlFor>componentRef.instance).isDynamic = true;
        (<ValidationControlFor>componentRef.instance).serverMessages = false;
        (<ValidationControlFor>componentRef.instance).clientMessages = false;
        (<ValidationControlFor>componentRef.instance).clientMessagesAlwaysVisible = this.clientMessagesAlwaysVisible;
    
        componentRef.changeDetectorRef.detectChanges();
    
        // NOTE:
        // registering of managed component is done via the component's ngOnInit function...
        // ...it was moved there, because we need to keep track also of manually added validation-message-controls!
    }
    
    injectComponent(componentVcRef:ViewContainerRef,定位器:string){
    //创建组件
    var componentRef=componentVcRef.createComponent(this.componentFactoryForValidationControlFor);
    //基本设置(打开/关闭服务器/客户端消息由doCheckManagedComponents()fnc完成。
    (componentRef.instance).locator=定位器;
    (componentRef.instance).isDynamic=true;
    (componentRef.instance).serverMessages=false;
    (componentRef.instance).clientMessages=false;
    (componentRef.instance).clientMessagesAlwaysVisible=this.clientMessagesAlwaysVisible;
    componentRef.changeDetectorRef.detectChanges();
    //注:
    //托管组件的注册通过组件的ngOnInit功能完成。。。
    //…它被移到了那里,因为我们还需要跟踪手动添加的验证消息控件!
    }
    
    您可以尝试一个名为ts.validator.fluent.Generic object validation.fluent rules的框架/库

    另外,一个演示框架的Angular 6 CLI应用程序

    /* Install npm package ts.validator.fluent and then import like below */
    import { IValidator, Validator, ValidationResult } from 'ts.validator.fluent/dist';
    
    /*TypeScript model*/
    class Person {
       Name: string;
    }
    
    /* Validation rules */
    var validatePersonRules = (validator: IValidator<Person>) : ValidationResult => {
      return validator
                 .NotEmpty(m => m.Name, "Name cannot be empty")
            .ToResult();
    };
    
    /* Populate model */
    var person = new Person();
    person.Name = "Shane";
    
    /* Validate model */
    /* Sync */
    var validationResult = new Validator(person).Validate(validatePersonRules); 
    /* Async */
    var validationResult = await new Validator(person).ValidateAsync(validatePersonRules);
    

    该应用程序使用面向服务的方法进行客户端表单验证,具有以下优点:

    • 围绕验证的业务规则与组件分离
    • 业务规则集中在验证服务中
    • 该服务是可重用的
    • 服务可以注入到任何组件中
    • 您可以通过对服务进行单元测试来对业务规则进行单元测试
    NPM包

    @Component({
        selector: 'TestAddress',
        templateUrl: './address.location.html',
        viewProviders: [
            { provide: ControlContainer, useExisting: NgForm },
            { provide: ControlContainer, useExisting: forwardRef(() => NgModelGroup) },
        ],
        providers: [ MakeProvider(TestAddress) ]
    })
    export class TestProspectAddressLocation extends AbstractValueAccessor<any> {
    ...
    }
    

    下面是如何使用框架验证TypeScript模型的示例:

    /* Install npm package ts.validator.fluent and then import like below */
    import { IValidator, Validator, ValidationResult } from 'ts.validator.fluent/dist';
    
    /*TypeScript model*/
    class Person {
       Name: string;
    }
    
    /* Validation rules */
    var validatePersonRules = (validator: IValidator<Person>) : ValidationResult => {
      return validator
                 .NotEmpty(m => m.Name, "Name cannot be empty")
            .ToResult();
    };
    
    /* Populate model */
    var person = new Person();
    person.Name = "Shane";
    
    /* Validate model */
    /* Sync */
    var validationResult = new Validator(person).Validate(validatePersonRules); 
    /* Async */
    var validationResult = await new Validator(person).ValidateAsync(validatePersonRules);
    
    /*安装npm包ts.validator.fluent,然后按如下方式导入*/
    从“ts.Validator.fluent/dist”导入{IValidator,Validator,ValidationResult};
    /*类型脚本模型*/
    班主任{
    名称:字符串;
    }
    /*验证规则*/
    var validatePersonRules=(验证器:IValidator):ValidationResult=>{
    返回验证器
    .NotEmpty(m=>m.Name,“Name不能为空”)
    .ToResult();
    };
    /*填充模型*/
    var person=新的person();
    人,不