【问题标题】:How to do Angular2/4 validation with 3rd party JS components如何使用 3rd 方 JS 组件进行 Angular2/4 验证
【发布时间】:2018-06-19 19:10:27
【问题描述】:

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

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

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

无论如何,理想的解决方案是使用规则对属性进行建模,然后向控件生成错误消息。类似于 .NET 流式验证和 ModelState 或在 JS 世界中类似于 Aurelia.io 验证:http://aurelia.io/docs/plugins/validation/

  1. 在 Angular2+ 中是否有类似的可能?
  2. 如何/在何处将检查(基于模型更改)电汇到第 3 方 (JS) 库?像 fluent-ts-validator 或 flient-validator。
  3. 如何在控件上设置错误,尤其是当我们有 f.e.内部控制
    控制...

【问题讨论】:

标签: angular validation


【解决方案1】:

我们首先需要将 FormControls 链接到输入元素。它不是开箱即用的。到目前为止唯一的解决方案(至少对于纯 ngModel 绑定)是我发布的here。

下面的代码不适用于纯 ngModel 绑定,所以我做了很多实验。最新的,Maximilian 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;
    }
}

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

【讨论】:

    【解决方案2】:

    接下来是了解如何正确构建嵌套表单 - 请参阅 link here!

    不仅转发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> {
    ...
    }
    

    我们必须使用 AbstractValueAccessor,因此也必须使用 MakeProvider。 Here's the link!

    最后一点:一种访问这些 FormControls 的方法:

    this.form.get('HomeAddress.City').nativeElement
    

    现在您可以订阅 formValueChange 和用户 3rd 方进行验证,通过路径表达式访问任何控件,使用 control.setErrors([]) 或只是通过 jQuery aft 附加 HTML。输入。

    【讨论】:

      【解决方案3】:

      仍在寻找:

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

      【讨论】:

        【解决方案4】:

        所以上面列表中的 #2 是可能的。

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

        现在诀窍来了:我们确实获得了输入本身的 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 方法只是设置了我的控件并将其附加到 viewContainerRef:

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

        【讨论】:

          【解决方案5】:

          您可以试用一个名为 ts.validator.fluent 的框架/库。通用对象验证。规则流畅。

          https://github.com/VeritasSoftware/ts.validator

          此外,还有一个 Angular 6 CLI 应用,用于演示框架:

          https://github.com/VeritasSoftware/ts-validator-app-angular6

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

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

          NPM 包:

          https://www.npmjs.com/package/ts.validator.fluent

          以下是如何使用框架验证 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);
          

          【讨论】:

          • 嗨,似乎是一个非常令人印象深刻的解决方案。请问你如何在角度分量上显示结果?
          • @baHi 谢谢。结果显示在角度组件中使用 ng-bootstrap 工具提示。但你可以随意展示它们。
          • 我有很多工作要通过自动注入的组件进行显示,即能够使用角度模板等。验证本身是一件容易的事,但必须修补原始的流利验证器以自动添加参数名称。该方法无需额外的验证类即可工作,因此 IMO 可以灵活应对。
          • @baHI 好的,我的使用带有
              的工具提示来显示结果。很容易做到。此外,我的是一种面向服务的表单验证方法。这样做有很多好处。
          猜你喜欢
          • 1970-01-01
          • 2020-05-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-17
          • 1970-01-01
          • 1970-01-01
          • 2021-05-14
          • 2021-11-21
          相关资源
          最近更新 更多