项目

Angular UI 的动态表单(或表单属性)扩展

介绍

表单属性扩展系统允许您向表单的创建和/或编辑表单添加新字段,或更改/移除现有字段。下图用户管理页面中添加了一个“生日”字段:

您可以验证该字段、执行可见性检查以及更多操作。在为编辑表单创建贡献者时,您还可以访问当前实体。

如何设置

在此示例中,我们将在身份模块的用户管理页面中添加一个“生日”字段并进行验证。

步骤 1. 创建表单属性贡献者

以下代码准备了两个常量,分别命名为 identityCreateFormPropContributorsidentityEditFormPropContributors,可供导入并在您的根应用程序配置中使用:

// src/app/form-prop-contributors.ts

import {
  eIdentityComponents,
  IdentityCreateFormPropContributors,
} from '@abp/ng.identity';
import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { ePropType, FormProp, FormPropList } from '@abp/ng.components/extensible';
import { Validators } from '@angular/forms';

const birthdayProp = new FormProp<IdentityUserDto>({
  type: ePropType.Date,
  name: 'birthday',
  displayName: 'AbpIdentity::Birthday',
  validators: () => [Validators.required],
});

export function birthdayPropContributor(propList: FormPropList<IdentityUserDto>) {
  propList.addByIndex(birthdayProp, 4);
}

export const identityCreateFormPropContributors: IdentityCreateFormPropContributors = {
  // 枚举指示要添加贡献者的页面
  [eIdentityComponents.Users]: [
    birthdayPropContributor,
    // 您可以在此处添加更多贡献者
  ],
};

export const identityEditFormPropContributors = identityCreateFormPropContributors;
// 如果您愿意,可以为编辑表单定义不同的贡献者

属性列表,为了方便命名为 propList,是一个双向链表。这就是我们使用 addByIndex 方法的原因,该方法将给定值添加到列表的指定索引处。您可以在此处找到所有可用的方法

步骤 2. 导入并使用表单属性贡献者

在您的路由配置中导入 identityCreateFormPropContributorsidentityEditFormPropContributors,并将其传递给 identity 路由的静态 createRoutes 方法,如下所示:

// src/app/app.routes.ts

// 其他导入
import {
  identityCreateFormPropContributors,
  identityEditFormPropContributors,
} from './form-prop-contributors';

export const APP_ROUTES: Routes = [
  // 其他路由

  {
    path: 'identity',
    loadChildren: () =>
      import('@abp/ng.identity').then(c =>
        c.createRoutes({
          createFormPropContributors: identityCreateFormPropContributors,
          editFormPropContributors: identityEditFormPropContributors,
        })
      ),
  },

  // 其他路由
];

至此,birthdayProp 表单属性将被添加,您将在 identity 包中用户页面表单的“电子邮件地址”字段之前看到“生日”字段的日期选择器。

对象扩展

在现有实体上定义的额外属性将被包含在创建和编辑表单中,并根据其配置进行验证。表单值也会自动映射到 extraProperties 以及从 extraProperties 映射出来。在定义自定义贡献者时它们是可用的,因此您可以删除、修改或重新排序它们。这些属性的 isExtra 标识符将设置为 true,并将定义此自动行为。

API

PropData<R = any>

PropData 是传递给 FormProp 中所有回调函数或谓词的参数的结构。

它具有以下属性:

  • getInjected 等同于 Injector.get。您可以使用它来获取 ExtensibleFormPropComponent 的注入依赖项,包括但不限于其父组件。

    {
      type: ePropType.Enum,
      name: 'myField',
      options: data => {
        const restService = data.getInjected(RestService);
        const usersComponent = data.getInjected(UsersComponent);
    
        // 在此处使用 restService 和 usersComponent 的公共属性和方法
      }
    },
    
  • record 是行数据,即所选待编辑项目的当前值。此属性_仅在编辑表单上可用_。

    {
      type: ePropType.String,
      name: 'myProp',
      readonly: data => data.record.someOtherProp,
    }
    

PropCallback<T, R = any>

PropCallback 是可以作为 prop 参数传递给 FormProp 的回调函数类型。属性回调接收单个参数 PropData。返回类型可以是任何类型,包括 void。以下是简化表示:

type PropCallback<T, R = any> = (data?: PropData<T>) => R;

PropPredicate<T>

PropPredicate 是可以作为 visible 参数传递给 FormProp 的谓词函数类型。属性谓词接收单个参数 PropData。返回类型必须是 boolean。以下是简化表示:

type PropPredicate<T> = (data?: PropData<T>) => boolean;

FormPropOptions<R = any>

FormPropOptions 是定义创建表单属性时必须传递的必需和可选属性的类型。

其类型定义如下:

type FormPropOptions<R = any> = {
  type: ePropType;
  name: string;
  displayName?: string;
  id?: string;
  permission?: string;
  visible?: PropPredicate<R>;
  readonly?: PropPredicate<R>;
  disabled?: PropPredicate<R>;
  validators?: PropCallback<R, ValidatorFn[]>;
  asyncValidators?: PropCallback<R, AsyncValidatorFn[]>;
  defaultValue?: boolean | number | string | Date;
  options?: PropCallback<R, Observable<ABP.Option<any>[]>>;
  autocomplete?: string;
  isExtra?: boolean;
  formText?: string;
  tooltip?: FormPropTooltip;
};

如您所见,传递 typename 就足以创建一个表单属性。以下是每个属性的作用:

  • type 是属性值的类型。它定义了表单中为该属性渲染哪种输入控件。(必填)
  • name 是用于读取属性值的属性名(或键)。(必填)
  • displayName 是属性的名称,将被本地化并显示为列标题。(默认值: options.name)
  • id 将设置为字段标签的 for 属性和输入框的 id 属性。(默认值: options.name)
  • permission 是权限上下文,用于决定是否应向用户显示此表单属性的列。(默认值: undefined)
  • visible 是一个谓词,用于决定是否应在表单上显示此属性。(默认值: () => true)
  • readonly 是一个谓词,用于决定此属性是否应为只读。(默认值: () => false)
  • disabled 是一个谓词,用于决定此属性是否应被禁用。(默认值: () => false)
  • validators 是一个返回属性验证器的回调函数。(默认值: () => [])
  • asyncValidators 是一个返回属性异步验证器的回调函数。(默认值: () => [])
  • defaultValue 是字段将具有的初始值。(默认值: null)
  • options 当需要下拉列表时调用的回调函数。它必须返回一个可观察对象。(默认值: undefined)
  • autocomplete 将设置为字段输入框的 autocomplete 属性。请查看可能的值。(默认值: 'off')
  • isExtra 指示此属性是否为对象扩展。当为 true 时,字段的值将从实体的 extraProperties 映射以及映射到 extraProperties。(默认值: undefined)
  • formText 是字段的说明文字。放置在字段下方。(默认值: undefined)
  • tooltip 是靠近标签放置的字段工具提示(默认值: undefined)

重要提示:不要在创建表单谓词和回调中使用 PropDatarecord 属性,因为它将是 undefined。不过,您可以在编辑表单贡献者中使用它。

您可以在下面找到一个完整示例。

FormProp<R = any>

FormProp 是定义表单属性的类。它接收一个 FormPropOptions 并为属性设置默认值,创建一个可以传递给表单贡献者的表单属性。

const options: FormPropOptions<IdentityUserDto> = {
  type: ePropType.Enum,
  name: 'myProp',
  displayName: 'Default::MyPropName',
  id: 'my-prop',
  permission: 'AbpIdentity.Users.ReadSensitiveData', // 假设的
  visible: data => {
    const store = data.getInjected(Store);
    const selectSensitiveDataVisibility = ConfigState.getSetting(
      'Abp.Identity.IsSensitiveDataVisible'  // 假设的
    );
    
    return store.selectSnapshot(selectSensitiveDataVisibility).toLowerCase() === 'true';
  },
  readonly: data => data.record.someProp,
  disabled: data => data.record.someOtherProp,
  validators: () => [Validators.required],
  asyncValidators: data => {
    const http = data.getInjected(HttpClient);

    function validate(control: AbstractControl): Observable<ValidationErrors | null> {
      if (control.pristine) return of(null);

      return http
        .get('https://api.my-brand.io/hypothetical/endpoint/' + control.value)
        .pipe(map(response => (response.valid ? null : { invalid: true })));
    }

    return [validate];
  },
  defaultValue: 0,
  options: data => {
    const service = data.getInjected(MyIdentityService);

    return service.getMyPropOptions()
      .pipe(
        map(({items}) => items.map(
          item => ({key: item.name, value: item.id })
        )),
      );
  },
  autocomplete: 'off',
  isExtra: true,
  template: undefined | Type<any>, // 自定义 Angular 组件
  tooltip: { text: 'Default::MyPropName_Tooltip', placement: 'top' },
  formText: 'Default::MyPropName_Description',
};

const prop = new FormProp(options);

从 6.0 版本开始,FormProp 具有 template 选项,可以接受自定义 Angular 组件。 该组件可以访问 PropData 和 Prop。 自定义属性组件的示例。

import {
  EXTENSIBLE_FORM_VIEW_PROVIDER,
  EXTENSIONS_FORM_PROP,
  EXTENSIONS_FORM_PROP_DATA,
} from '@abp/ng.components/extensible';


@Component({
  selector: 'my-custom-custom-prop',
  templateUrl: './my-custom-custom-prop.component.html',
  viewProviders: [EXTENSIBLE_FORM_VIEW_PROVIDER], //您应该添加这个,否则 form-group 不起作用。
})
export class MyCustomPropComponent {
  constructor(
    @Inject(EXTENSIONS_FORM_PROP) private formProp: FormProp,
    @Inject(EXTENSIONS_FORM_PROP_DATA) private propData: ProfileDto,
    ...)
  ...
}

它还有两个静态方法来创建其实例:

  • FormProp.create<R = any>(options: FormPropOptions<R>) 用于创建 FormProp 的实例。

    const prop = FormProp.create(options);
    
  • FormProp.createMany<R = any>(options: FormPropOptions<R>[]) 用于使用给定的 FormPropOptions 数组创建多个 FormProp 实例。

    const props = FormProp.createMany(optionsArray);
    

FormPropList<R = any>

FormPropList 是作为第一个参数(名为 propList)传递给每个属性贡献者回调的属性列表。它是一个双向链表。您可以在此处找到所有可用的方法

列表中的项目将按照链表的顺序(即从头到尾)显示。如果您想重新排序它们,您只需要这样做:

export function reorderUserContributors(
  propList: FormPropList<IdentityUserDto>,
) {
  // 删除 email 节点
  const emailPropNode = propList.dropByValue(
    'AbpIdentity::EmailAddress',
    (prop, displayName) => prop.displayName === displayName,
  );

  // 在 phoneNumber 之后添加回来
  propList.addAfter(
    emailPropNode.value,
    'phoneNumber',
    (value, name) => value.name === name,
  );
}

CreateFormPropContributorCallback<R = any>

CreateFormPropContributorCallback 是您可以作为创建表单属性贡献者回调传递给包静态 createRoutes 方法的类型。

export function myPropCreateContributor(
  propList: FormPropList<IdentityUserDto>,
) {
  // 从开头将 myProp 添加为第 2 个字段
  propList.add(myProp).byIndex(1);
}

export const identityCreateFormPropContributors = {
  [eIdentityComponents.Users]: [myPropCreateContributor],
};

EditFormPropContributorCallback<R = any>

EditFormPropContributorCallback 是您可以作为编辑表单属性贡献者回调传递给包静态 createRoutes 方法的类型。

export function myPropEditContributor(
  propList: FormPropList<IdentityUserDto>,
) {
  // 从末尾将 myProp 添加为第 2 个字段
  propList.add(myProp).byIndex(-1);
}

export const identityEditFormPropContributors = {
  [eIdentityComponents.Users]: [myPropEditContributor],
};

另请参阅

在本文档中