Angular UI 的动态表单(或表单属性)扩展
介绍
表单属性扩展系统允许您向表单的创建和/或编辑表单添加新字段,或更改/移除现有字段。下图用户管理页面中添加了一个“生日”字段:
您可以验证该字段、执行可见性检查以及更多操作。在为编辑表单创建贡献者时,您还可以访问当前实体。
如何设置
在此示例中,我们将在身份模块的用户管理页面中添加一个“生日”字段并进行验证。
步骤 1. 创建表单属性贡献者
以下代码准备了两个常量,分别命名为 identityCreateFormPropContributors 和 identityEditFormPropContributors,可供导入并在您的根应用程序配置中使用:
// 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. 导入并使用表单属性贡献者
在您的路由配置中导入 identityCreateFormPropContributors 和 identityEditFormPropContributors,并将其传递给 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;
};
如您所见,传递 type 和 name 就足以创建一个表单属性。以下是每个属性的作用:
- 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)
重要提示:不要在创建表单谓词和回调中使用
PropData的record属性,因为它将是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],
};
抠丁客


