• Angular响应式表单及封装表单控件


    响应式表单也叫模型驱动型表单。

    有三个重要元素FormControl,FormGroup和FormBuilder。还有一个FormArray。

    验证器和异步验证器。

    动态指定验证器。条件改变验证方式改变。

    自定义FormControl。用于表单过于复杂之后,逻辑难以理清楚。把复杂问题拆成若干简单问题永远是万能钥匙。用于简化form表单自己的逻辑。

    一、登录表单

    多个validators:

    Validators.compose([Validators.required, Validators.email])返回ValidatorFn。
    动态指定validator: 
    一开始可以不指定validator,在某些条件下动态指定validator:
     this.form.controls['email'].setValidators(this.validate);
    查看errors:
    <mat-error>{{form.controls['email'].errors | json}}</mat-error>
    模板:
    <form [formGroup]="form" (ngSubmit)="onSubmit(form,$event)">
        <mat-card class="example-card">
            <mat-card-header>
                <mat-card-title>登录:</mat-card-title>
            </mat-card-header>
            <mat-card-content>
                <mat-form-field class="example-full-width" class="full-width">
                    <input type="text" formControlName="email" matInput placeholder="您的email" style="text-align: right">
                    <mat-error>{{form.controls['email'].errors | json}}</mat-error>
                </mat-form-field>
                <mat-form-field class="example-full-width" class="full-width">
                    <input type="password" formControlName="password"  matInput placeholder="您的密码" style="text-align: right">
                </mat-form-field>
                <button mat-raised-button type="submit" color="primary" [disabled]="!form.valid">登录</button>
    
            </mat-card-content>
            <mat-card-actions class="text-right">
                <p>还没有账户?<a routerLink="/register">注册</a></p>
                <p>忘记密码?<a href="">找回</a></p>
            </mat-card-actions>
        </mat-card>
    
        <mat-card class="example-card">
            <mat-card-header>
                <mat-card-title>每日佳句</mat-card-title>
                <mat-card-subtitle>满足感在于不断的努力,而不是现有成就。全心努力定会胜利满满。</mat-card-subtitle>
            </mat-card-header>
            <img mat-card-image src="/assets/images/quote_fallback.jpg" alt="">
            <mat-card-content>
                Satisfaction lies in the effort, not in the attainment. Full effort is full victory.
            </mat-card-content>
        </mat-card>
    </form>
    View Code

    组件:

    export class LoginComponent implements OnInit {
      form: FormGroup;
      constructor(private fb: FormBuilder) {
        // this.form = new FormGroup({
        //   email: new FormControl("wang@163.com", Validators.compose([Validators.required, Validators.email])),
        //   password: new FormControl("",Validators.required),
        // })
    
        //formBuilder不需要显示的new FormControl
        this.form = this.fb.group({
          email: ["wang@163.com", Validators.compose([Validators.required, Validators.email, this.validate]) ],
          password:["",Validators.required]
    
        })
      }
    
      ngOnInit(): void {
       
      }
    
      onSubmit(form: FormGroup, event: Event) {
        event.preventDefault();
        console.log(JSON.stringify(form.value));
        console.log(form.valid);
      }
    
      validate(c:FormControl):{[key:string]:any} | null{
        if(!c.value){
          return null;
        }
        const pattern=/^wang+/;
        if(pattern.test(c.value)){
          return null;
        }else{
          return {
            emailNotValid: 'The email must start with wang'
          }
        }
      }
    
    }
    View Code

     二、封装自定义表单控件

    把注册表单中的图片列表抽成一个独立组件。

     ng g c shared/image-list-select生成组件

    •  实现ControlValueAccessor接口。实现writeValue(),registerOnChange()和registerOnTouched()
    • 定义一个providers,令牌NG_VALUE_ACCESSOR和NG_VALIDATORS。用useExisting加forwardRef。并且设置multi为true。

     在image-list-select中可以放开的属性有很多,有没有必要一一放开?需要权衡。

     如果想要充分的自由度的话,可以用transclude嵌入组件<ng-content></ng-content>。

     在image-list-select中隔离封装,放开有限的属性。

    模板:

    <div>
      <span>{{title}}</span>
      <mat-icon class="avatar" [svgIcon]="selected" *ngIf="useSvgIcon else imgSelect"> </mat-icon>
      <ng-template #imgSelect>
        <img [src]="selected" alt="image selected" class="cover">
      </ng-template>
    </div>
    
    <div class="scroll-container">
      <mat-grid-list [cols]="cols" [rowHeight]="rowHight">
        <mat-grid-tile *ngFor="let item of items; let i = index">
          <div class="image-container" (click)="onChange(i)">
            <mat-icon class="avatar" [svgIcon]="item" *ngIf="useSvgIcon else imgItem"></mat-icon>
            <ng-template #imgItem>
              <img [src]="item" alt="image item" [ngStyle]="{'width': itemWidth}">
            </ng-template>
            
            <div class="after">
              <div class="zoom">
                <mat-icon>checked</mat-icon>
              </div>
            </div>
          </div>
      
        </mat-grid-tile>
      </mat-grid-list>
    </div>
    View Code

    组件:

    import { Component, forwardRef, Input, OnInit } from '@angular/core';
    import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
    
    @Component({
      selector: 'app-image-list-select',
      templateUrl: './image-list-select.component.html',
      styleUrls: ['./image-list-select.component.scss'],
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => ImageListSelectComponent),
          multi: true
        },
        {
          provide: NG_VALIDATORS,
          useExisting: forwardRef(() => ImageListSelectComponent),
          multi: true
        }
      ]
    })
    export class ImageListSelectComponent implements ControlValueAccessor {
      @Input() title = "选择"
      @Input() cols = 6;
      @Input() rowHight = '64px'
      @Input() items: string[] = [];
      @Input() useSvgIcon: boolean = false;
      @Input() itemWidth = '80px';
      selected: string = '';
      constructor() { }
    
      private propagateChange = (_: any) => { };
    
      //写值,设置控件的值form中setValue设置初始值,通过表单控件的writeValue方法设值。
      writeValue(obj: any): void {
        this.selected = obj;
      }
      //控件view发生变化,把变化emit给表单
      registerOnChange(fn: any): void {
        this.propagateChange = fn;
      }
      //什么状态算touched,告诉表单
      registerOnTouched(fn: any): void {
      }
    
      onChange(i: number) {
        this.selected = this.items[i];
        this.propagateChange(this.selected); //变化通知表单
      }
    
      validate(c: FormControl): { [key: string]: any } | null {
        return this.selected ? null : {
          imageListInvalid: {
            valid: false
          }
        }
      }
    
    }
    View Code

    1,UI布局 

    图片鼠标划过去如果没有任何反应,用户会无法感知到有没有选中,所以

     <div class="image-container" (click)="onChange(i)">
            <mat-icon class="avatar" [svgIcon]="avator"></mat-icon>
            <div class="after">
              <div class="zoom">
                <mat-icon>checked</mat-icon>
              </div>
            </div>
    </div>

    让组件既处理icon又处理图片。用useSvgIcon控制,通过条件子句判断。

    <mat-icon class="" [svgIcon]="selected" *ngIf="useSvgIcon else imgSelect"> </mat-icon>
    <ng-template #imgSelect></ng-template>

    list太多支持滚动。包在.scroll-container容器中。

    .scroll-container {
        overflow-y: scroll;
        height: 200px;
    }

     2,实现表单控件

    可以通过 formContrlName来操作

     实现ControlValueAccessor接口。

    private propagateChange = (_: any) => { };
    
      //写值,设置控件的值form中setValue设置初始值,通过表单控件的writeValue方法设值。
      writeValue(obj: any): void {
        this.selected = obj;
      }
      //控件view发生变化,把变化emit给表单
      registerOnChange(fn: any): void {
        this.propagateChange = fn;
      }
      //什么状态算touched,告诉表单
      registerOnTouched(fn: any): void {
        throw new Error('Method not implemented.');
      }
    
      onChange(i: number) {
        this.selected = this.items[i];
        this.propagateChange(this.selected); //变化通知表单
      }

    providers中定义自己的provider,把自己注册进去。

    包括NG_VALUE_ACCESSOR和NG_VALIDATORS。

    providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => ImageListSelectComponent),
          multi: true
        },
        {
          provide: NG_VALIDATORS,
          useExisting: forwardRef(() => ImageListSelectComponent),
          multi: true
        }
      ]
    
    validate(c: FormControl): { [key: string]: any } | null {
        return this.selected ? null : {
          imageListInvalid: {
            valid: false
          }
        }
      }

     3,调用/使用

    在sharedModule中导出ImageListSelectComponent。

    <app-image-list-select
              [useSvgIcon]="true"
              [cols]="6"
              [title]="'选择头像:'"
              [items]="items"
              formControlName="avatar">
    </app-image-list-select>

    2019-04-07

    如果觉得本文对您有帮助~可以支付宝(左)或微信支持一下:


    看到小伙伴打赏时给我写一些鼓励的话,真的非常感动,谢谢你们。


    我开了个微信公众号(第三个二维码)用来分享自己的职场英语相关学习经验,感兴趣可以关注,我会不断更新~


    微信打赏微信公众号

  • 相关阅读:
    Markdown实用教程
    Python三次登陆
    Python猜年龄
    Pycharm用鼠标滚轮控制字体大小
    检测浏览器是否存在某个css或者js的api
    隐式绑定和显式绑定实现一个apply
    promise顺序执行的多种方案
    数据结构栈的定义和使用
    数据以及数据结构是数据处理的起点
    Vue的高阶组件(HOC)使用举例
  • 原文地址:https://www.cnblogs.com/starof/p/10666517.html
Copyright © 2020-2023  润新知