编辑
IMPORT MODULE

简化表单HTML模板的高阶组件,并进一步优化了一些细节:

  • 更友好的表单校验状态

  • 自动化响应式布局

  • 自动维护表单 id

它由 se-container 容器(指令)和 se 组件来表示一个表单,一个简单HTML模板表单是这么写的:

<form nz-form #f="ngForm" se-container="2">
  <se label="App Key">
    <input type="text" nz-input [(ngModel)]="i.ak" name="ak" required>
  </se>
  <se label="App Secret">
    <input type="text" nz-input [(ngModel)]="i.sk" name="sk" required maxlength="32">
  </se>
  <se>
    <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
  </se>
</form>

同时,会自动处理所有 Angular 内置校验指令,例如:requiredmaxlengthminpattern 等,并以红色边框来表示无效值状态。

代码演示

基础

一行两列表单。

expand codeexpand code
import { Component } from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd/message';

@Component({
  selector: 'components-se-basic',
  template: ` <form nz-form #f="ngForm" se-container gutter="32">
    <se label="App Key" [error]="{ required: '请填写', pattern: '只能包含a-z, 0-9之间' }">
      <input
        type="text"
        nz-input
        [(ngModel)]="i.ak"
        name="ak"
        required
        pattern="^[a-z0-9]*$"
        placeholder="必填项,且只能包含a-z, 0-9之间"
      />
    </se>
    <ng-template #appSecretRequired> 请填写,密钥<a (click)="msg.success('success')">生成</a>地址。 </ng-template>
    <se label="App Secret" [error]="{ required: appSecretRequired, pattern: '只能包含0-9之间' }">
      <input
        type="text"
        nz-input
        [(ngModel)]="i.sk"
        name="sk"
        required
        maxlength="32"
        pattern="^[0-9]*$"
        placeholder="必填项,且只能包含0-9之间"
      />
    </se>
    <se>
      <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
    </se>
  </form>`,
})
export class ComponentsSeBasicComponent {
  i: { ak?: string; sk?: string } = {};

  constructor(public msg: NzMessageService) {}
}
响应式表单

支持响应式表单。

注意:

  • 响应式表单无法自动获取 Validators.required 来判断是否需要必填项标识符,因此需要手动标识 required 属性。

  • 当需要使用 patchValue 赋值时,因为 dirty 依然保持为 false 的因素,需要对所有表单元素设置 markAsDirty() 或者使用 ingoreDirty 属性强制忽略 dirty 的校验。

expand codeexpand code
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NzMessageService } from 'ng-zorro-antd/message';

@Component({
  selector: 'components-se-reactive',
  template: ` <form nz-form [formGroup]="validateForm" (ngSubmit)="submitForm()" se-container gutter="32" ingoreDirty>
    <se label="App Key" required [error]="{ required: 'Please input your username!', pattern: 'Incorrect format, muse be A' }">
      <input formControlName="userName" nz-input placeholder="Username" />
    </se>
    <se label="App Secret" required error="Please input your Password!">
      <input formControlName="password" nz-input type="password" placeholder="Password" />
    </se>
    <se>
      <button nz-button nzType="primary" [disabled]="!validateForm.valid">Log in</button>
      <button nz-button nzType="link" type="button" (click)="updateValue()">Update value via patchValue</button>
    </se>
  </form>`,
})
export class ComponentsSeReactiveComponent {
  validateForm: FormGroup;
  constructor(fb: FormBuilder, private msg: NzMessageService) {
    this.validateForm = fb.group({
      userName: [null, [Validators.required, Validators.pattern(/A/)]],
      password: [null, [Validators.required]],
      remember: [true],
    });
  }

  submitForm(): void {
    this.msg.success(JSON.stringify(this.validateForm.value));
  }

  updateValue(): void {
    this.validateForm.patchValue({ userName: 'a' });
  }
}
紧凑型

强制忽略 errorextra 展示。

expand codeexpand code
import { Component } from '@angular/core';

@Component({
  selector: 'components-se-compact',
  template: ` <form nz-form #f="ngForm" se-container labelWidth="150" gutter="32" size="compact">
    <se label="App Key" error="请填写" optional="(选填)" optionalHelp="通过控制台-查看KEY获取" extra="额外提示信息">
      <input type="text" nz-input [(ngModel)]="i.ak" name="ak" required />
    </se>
    <se label="App Secret" error="请填写,最多32位">
      <input type="text" nz-input [(ngModel)]="i.sk" name="sk" required maxlength="32" />
    </se>
    <se>
      <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
    </se>
  </form>`,
})
export class ComponentsSeCompactComponent {
  i: { ak?: string; sk?: string } = {};
}
额外提示信息
水平排列

一行一列表单。

expand codeexpand code
import { Component } from '@angular/core';

@Component({
  selector: 'components-se-horizontal',
  template: ` <form nz-form #f="ngForm" se-container="1" labelWidth="150">
    <se label="App Key" error="请填写" optional="(选填)" optionalHelp="通过控制台-查看KEY获取" extra="额外提示信息">
      <input type="text" nz-input [(ngModel)]="i.ak" name="ak" required />
    </se>
    <se label="App Secret" error="请填写,最多32位">
      <input type="text" nz-input [(ngModel)]="i.sk" name="sk" required maxlength="32" />
    </se>
    <se>
      <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
    </se>
  </form>`,
})
export class ComponentsSeHorizontalComponent {
  i: { ak?: string; sk?: string } = {};
}
内联布局

nzLayout: inline 时强制使用紧凑型,一般用于搜索框。

expand codeexpand code
import { Component } from '@angular/core';

@Component({
  selector: 'components-se-inline',
  template: `
  <form nz-form nzLayout="inline" #f="ngForm" se-container>
    <se label="App Key" error="请填写" optional="(选填)" optionalHelp="通过控制台-查看KEY获取" extra="额外提示信息">
      <input type="text" nz-input [(ngModel)]="i.ak" name="ak" required>
    </se>
    <se label="App Secret" error="请填写,最多32位">
      <input type="text" nz-input [(ngModel)]="i.sk" name="sk" required maxlength="32">
    </se>
    <se>
      <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
    </se>
  </form>`,
})
export class ComponentsSeInlineComponent {
  i: { ak?: string; sk?: string } = {};
}
额外提示信息
垂直布局

垂直布局表单。

expand codeexpand code
import { Component } from '@angular/core';

@Component({
  selector: 'components-se-vertical',
  template: ` <form nz-form nzLayout="vertical" #f="ngForm" se-container>
    <se label="App Key" error="请填写" optional="(选填)" optionalHelp="通过控制台-查看KEY获取" extra="额外提示信息">
      <input type="text" nz-input [(ngModel)]="i.ak" name="ak" required />
    </se>
    <se label="App Secret" error="请填写,最多32位">
      <input type="text" nz-input [(ngModel)]="i.sk" name="sk" required maxlength="32" />
    </se>
    <se>
      <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
    </se>
  </form>`,
})
export class ComponentsSeVerticalComponent {
  i: { ak?: string; sk?: string } = {};
}
头像
分隔线

构建一个左右结构的标准表单行。

expand codeexpand code
import { Component } from '@angular/core';

@Component({
  selector: 'components-se-line',
  template: ` <form nz-form #f="ngForm" se-container="1" size="compact" gutter="32">
    <se label="所属类目" line>头像</se>
    <se label="图片">
      <nz-input-group nzSearch [nzAddOnAfter]="suffixButton">
        <input type="text" nz-input placeholder="请贴入网络图片地址" />
      </nz-input-group>
      <ng-template #suffixButton>
        <button nz-button nzType="primary" nzSearch>提取</button>
      </ng-template>
    </se>
  </form>`,
})
export class ComponentsSeLineComponent {}

Operating

Reactive

批量重置错误消息

利用 errors 可以批量对所有 se 组件重置 error 值。

expand codeexpand code
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { SEErrorRefresh } from '@delon/abc/se';
import { NzMessageService } from 'ng-zorro-antd/message';

@Component({
  selector: 'components-se-reset-errors',
  template: `
    <h3>Operating</h3>
    <div class="mb-md">
      <button nz-button (click)="resetErrors()">Reset all errors</button>
    </div>
    <form nz-form #f="ngForm" se-container [errors]="ngModelErrors" gutter="32">
      <se label="App Key" [error]="{ required: '请填写', pattern: '只能包含a-z, 0-9之间' }">
        <input
          type="text"
          nz-input
          [(ngModel)]="i.ak"
          name="ak"
          required
          pattern="^[a-z0-9]*$"
          placeholder="必填项,且只能包含a-z, 0-9之间"
        />
      </se>
      <ng-template #appSecretRequired> 请填写,密钥<a (click)="msg.success('success')">生成</a>地址。 </ng-template>
      <se label="App Secret" [error]="{ required: appSecretRequired, pattern: '只能包含0-9之间' }">
        <input
          type="text"
          nz-input
          [(ngModel)]="i.sk"
          name="sk"
          required
          maxlength="32"
          pattern="^[0-9]*$"
          placeholder="必填项,且只能包含0-9之间"
        />
      </se>
      <se>
        <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
      </se>
    </form>
    <h3>Reactive</h3>
    <form nz-form [formGroup]="validateForm" se-container gutter="32" [errors]="reactiveErrors">
      <se label="App Key" [error]="{ required: 'Please input your username!', pattern: 'Incorrect format, muse be A' }">
        <input formControlName="userName" nz-input placeholder="Username" />
      </se>
      <se label="App Secret" error="Please input your Password!">
        <input formControlName="password" nz-input type="password" placeholder="Password" />
      </se>
      <se>
        <button nz-button nzType="primary" [disabled]="!validateForm.valid">Log in</button>
      </se>
    </form>
  `,
})
export class ComponentsSeResetErrorsComponent {
  validateForm: FormGroup;
  i: { ak?: string; sk?: string } = {};
  ngModelErrors: SEErrorRefresh[] = [];
  reactiveErrors: SEErrorRefresh[] = [];

  constructor(fb: FormBuilder, public msg: NzMessageService) {
    this.validateForm = fb.group({
      userName: [null, [Validators.required, Validators.pattern(/A/)]],
      password: [null, [Validators.required]],
      remember: [true],
    });
  }

  resetErrors(): void {
    this.ngModelErrors = [{ name: 'ak', error: 'Required field, and can only contain a-z, 0-9' }];
    this.reactiveErrors = [
      { name: 'userName', error: 'Required username' },
      { name: 'password', error: 'Required password' },
    ];
  }
}
Title 1
1000
3
+86
Title 2
id

暂无数据

不规则布局

利用 col 可以构建复杂不规则布局。

expand codeexpand code
import { Component } from '@angular/core';

import { STColumn } from '@delon/abc/st';

@Component({
  selector: 'components-se-complex',
  template: `
    <form nz-form #f="ngForm" se-container size="compact" gutter="24">
      <se-title>Title 1</se-title>
      <se label="ID" col="1" [optionalHelp]="optionalHelpTpl">
        1000
        <ng-template #optionalHelpTpl> Via by ng-template </ng-template>
      </se>
      <se label="Name" required col="3" optionalHelp="The background color is #f50" optionalHelpColor="#f50">
        <input type="text" nz-input [(ngModel)]="i.user_name" name="user_name" required />
      </se>
      <se label="Age" required col="3">
        <nz-select [(ngModel)]="i.user_age" name="user_age" nzAllowClear nzPlaceHolder="Choose">
          <nz-option [nzValue]="1" nzLabel="1"></nz-option>
          <nz-option [nzValue]="2" nzLabel="2"></nz-option>
          <nz-option [nzValue]="3" nzLabel="3"></nz-option>
          <nz-option [nzValue]="4" nzLabel="4"></nz-option>
          <nz-option [nzValue]="5" nzLabel="5"></nz-option>
        </nz-select>
      </se>
      <se label="Brithday" required col="3">
        <nz-date-picker [(ngModel)]="i.user_birthday" name="user_birthday" nzShowTime></nz-date-picker>
      </se>
      <se label="App Key" required>
        <input type="text" nz-input [(ngModel)]="i.ak" name="ak" required />
      </se>
      <se label="App Secret" required>
        <input type="text" nz-input [(ngModel)]="i.sk" name="sk" required maxlength="32" />
      </se>
      <nz-divider></nz-divider>
      <se label="Phone Number" required>
        <nz-input-group [nzAddOnBefore]="addOnBeforeTemplate">
          <ng-template #addOnBeforeTemplate>
            <nz-select [(ngModel)]="i.phoneNumberPrefix" name="phoneNumberPrefix" style="width: 70px;">
              <nz-option nzLabel="+86" nzValue="+86"></nz-option>
              <nz-option nzLabel="+87" nzValue="+87"></nz-option>
            </nz-select>
          </ng-template>
          <input type="text" nz-input [(ngModel)]="i.phoneNumber" name="phoneNumber" required maxlength="32" />
        </nz-input-group>
      </se>
      <se>
        <label nz-checkbox [(ngModel)]="i.agree" name="agree">
          <span>I have read the <a>agreement</a></span>
        </label>
      </se>
      <se-title>Title 2</se-title>
      <se label="Long Long Long Long Long Long Label" col="1">
        <textarea [(ngModel)]="i.comment" name="comment" nz-input rows="2" placeholder="write any thing"></textarea>
      </se>
      <se hideLabel col="1">
        <st [columns]="columns" size="small"></st>
      </se>
      <se col="1">
        <button nz-button nzType="primary" [disabled]="f.invalid">Save</button>
      </se>
    </form>
  `
})
export class ComponentsSeComplexComponent {
  i = {
    user_name: '',
    user_age: 3,
    user_birthday: '',
    ak: '',
    sk: '',
    phoneNumberPrefix: '+86',
    phoneNumber: '',
    agree: true,
    comment: ''
  };
  columns: STColumn[] = [{ title: 'id' }];
}

API

se-container

成员说明类型默认值全局配置
[gutter]间距,当 nzLayout:horizontal 时有效number32
[se-container]指定表单元素最多分几列展示,最终一行几列由 col 配置结合响应式规则决定'1','2','3','4','5','6'-
[col]指定表单元素最多分几列展示,最终一行几列由 col 配置结合响应式规则决定'1','2','3','4','5','6'-
[labelWidth]表单元素默认标签文本宽度,单位:pxnumber150
[nzLayout]表单布局,当 inline 时强制大小为 compact'horizontal','vertical','inline''horizontal'
[size]大小 compact 紧凑型,强制忽略 errorextra 展示'default','compact''default'
[firstVisual]是否立即呈现错误视觉booleanfalse
[ingoreDirty]是否忽略 dirty 校验booleanfalse
[line]分隔线booleanfalse-
[title]标题string,TemplateRef<void>-
[errors]批量修改 se 错误消息描述SEErrorRefresh[]-
[noColon]默认是否不显示 label 后面的冒号booleanfalse-

se

成员说明类型默认值
[col]指定表单元素最多分几列展示,最终一行几列由 col 配置结合响应式规则决定(继承于 se-container'1','2','3','4','5','6'-
[label]标签文本string, TemplateRef<void>-
[labelWidth]标签文本宽度,单位:px(继承于 se-containernumber-
[hideLabel]是否隐藏当前 labelbooleanfalse
[optional]标签可选信息string, TemplateRef<void>-
[optionalHelp]标签可选帮助string, TemplateRef<void>-
[optionalHelpColor]标签可选帮助背景颜色string-
[error]错误描述string, TemplateRef<void>, { [key: string]: string, TemplateRef}-
[extra]额外提示信息string, TemplateRef<void>-
[required]是否必填项标识符,若不设置自动根据表单元素是否有 RequiredValidator 校验来设置值string-
[controlClass]控件区域样式名string-
[id]自定义组件 idstring-
[line]分隔线(继承于 se-containerboolean-
[noColon]是否不显示 label 后面的冒号booleanfalse-

se-title

用于展示标题,单独一行。

常见问题

什么时候自定义组件id

点击表单的 Label 会将光标定位至相应组件下,而 ng-edit 会自动根据 ngModel 状态合理的设定 id,绝大多数情况下你无须关心 id 的绑定状态,当然若你手动指定 id 值则优先级更高但同时你需要自己维护组件对应的 id 值。