diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index 54a1869c0e..0010a30a2f 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -546,4 +546,5 @@ The final UI looks like below: ## See Also +- [How Replaceable Components Work with Extensions](./How-Replaceable-Components-Work-with-Extensions.md) - [How to Replace PermissionManagementComponent](./Permission-Management-Component-Replacement.md) diff --git a/docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md b/docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md new file mode 100644 index 0000000000..84425667c7 --- /dev/null +++ b/docs/en/UI/Angular/How-Replaceable-Components-Work-with-Extensions.md @@ -0,0 +1,288 @@ +# How Replaceable Components Work with Extensions + +Additional UI extensibility points ([Entity action extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Entity-Action-Extensions), [data table column extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Data-Table-Column-Extensions), [page toolbar extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Page-Toolbar-Extensions) and others) are used in ABP pages to allow to control entity actions, table columns and page toolbar of a page. If you replace a page, you need to apply some configurations to be able to work extension components in your component. Let's see how to do this by replacing the roles page. + +Create a new module called `MyRolesModule`: + +```bash +yarn ng generate module my-roles --module app +``` + +Create a new component called `MyRolesComponent`: + +```bash +yarn ng generate component my-roles/my-roles --flat --export +``` + +Open the generated `src/app/my-roles/my-roles.component.ts` file and replace its content with the following: + +```js +import { ListService, PagedAndSortedResultRequestDto } from '@abp/ng.core'; +import { + CreateRole, + DeleteRole, + eIdentityComponents, + GetRoleById, + GetRoles, + IdentityRoleDto, + IdentityState, + RolesComponent, + UpdateRole, +} from '@abp/ng.identity'; +import { ePermissionManagementComponents } from '@abp/ng.permission-management'; +import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared'; +import { + EXTENSIONS_IDENTIFIER, + FormPropData, + generateFormFromProps, +} from '@abp/ng.theme.shared/extensions'; +import { Component, Injector, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { Select, Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { finalize, pluck } from 'rxjs/operators'; + +@Component({ + selector: 'app-my-roles', + templateUrl: './my-roles.component.html', + providers: [ + ListService, + { + provide: EXTENSIONS_IDENTIFIER, + useValue: eIdentityComponents.Roles, + }, + { provide: RolesComponent, useExisting: MyRolesComponent }, + ], +}) +export class MyRolesComponent implements OnInit { + @Select(IdentityState.getRoles) + data$: Observable; + + @Select(IdentityState.getRolesTotalCount) + totalCount$: Observable; + + form: FormGroup; + + selected: IdentityRoleDto; + + isModalVisible: boolean; + + visiblePermissions = false; + + providerKey: string; + + modalBusy = false; + + permissionManagementKey = ePermissionManagementComponents.PermissionManagement; + + onVisiblePermissionChange = event => { + this.visiblePermissions = event; + }; + + constructor( + public readonly list: ListService, + protected confirmationService: ConfirmationService, + protected store: Store, + protected injector: Injector + ) {} + + ngOnInit() { + this.hookToQuery(); + } + + buildForm() { + const data = new FormPropData(this.injector, this.selected); + this.form = generateFormFromProps(data); + } + + openModal() { + this.buildForm(); + this.isModalVisible = true; + } + + add() { + this.selected = {} as IdentityRoleDto; + this.openModal(); + } + + edit(id: string) { + this.store + .dispatch(new GetRoleById(id)) + .pipe(pluck('IdentityState', 'selectedRole')) + .subscribe(selectedRole => { + this.selected = selectedRole; + this.openModal(); + }); + } + + save() { + if (!this.form.valid) return; + this.modalBusy = true; + + this.store + .dispatch( + this.selected.id + ? new UpdateRole({ ...this.selected, ...this.form.value, id: this.selected.id }) + : new CreateRole(this.form.value) + ) + .pipe(finalize(() => (this.modalBusy = false))) + .subscribe(() => { + this.isModalVisible = false; + this.list.get(); + }); + } + + delete(id: string, name: string) { + this.confirmationService + .warn('AbpIdentity::RoleDeletionConfirmationMessage', 'AbpIdentity::AreYouSure', { + messageLocalizationParams: [name], + }) + .subscribe((status: Confirmation.Status) => { + if (status === Confirmation.Status.confirm) { + this.store.dispatch(new DeleteRole(id)).subscribe(() => this.list.get()); + } + }); + } + + private hookToQuery() { + this.list.hookToQuery(query => this.store.dispatch(new GetRoles(query))).subscribe(); + } + + openPermissionsModal(providerKey: string) { + this.providerKey = providerKey; + setTimeout(() => { + this.visiblePermissions = true; + }, 0); + } + + sort(data) { + const { prop, dir } = data.sorts[0]; + this.list.sortKey = prop; + this.list.sortOrder = dir; + } +} +``` + +```js + { + provide: EXTENSIONS_IDENTIFIER, + useValue: eIdentityComponents.Roles, + }, + { + provide: RolesComponent, + useExisting: MyRolesComponent + } +``` + +The two providers we have defined in `MyRolesComponent` are required for the extension components to work correctly. + +* With the first provider, we defined the extension identifier for using `RolesComponent`'s extension actions in the `MyRolesComponent`. +* With the second provider, we have replaced the `RolesComponent` injection with the `MyRolesComponent`. Default extension actions of the `RolesComponent` try to get `RolesComponent` instance. However, the actions can get the `MyRolesComponent` instance after defining the second provider. + +Open the generated `src/app/my-role/my-role.component.html` file and replace its content with the following: + +```html +
+
+
+
+
My Roles
+
+
+ +
+
+
+ +
+ +
+
+ + + +

{%{{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewRole') | abpLocalization }}}%}

+
+ + +
+ +
+
+ + + + {%{{{ + 'AbpIdentity::Save' | abpLocalization + }}}%} + +
+ + + +``` + +We have added the `abp-page-toolbar`, `abp-extensible-table`, and `abp-extensible-form` extension components to template of the `MyRolesComponent`. + +You should import the required modules for the `MyRolesComponent` to `MyRolesModule`. Open the `src/my-roles/my-roles.module.ts` file and replace the content with the following: + +```ts +import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions'; +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import { MyRolesComponent } from './my-roles.component'; +import { PermissionManagementModule } from '@abp/ng.permission-management'; + +@NgModule({ + declarations: [MyRolesComponent], + imports: [SharedModule, UiExtensionsModule, PermissionManagementModule], + exports: [MyRolesComponent], +}) +export class MyRolesModule {} +``` + +- `UiExtensionsModule` imported to be able to use the extension components in your component. +- `PermissionManagementModule` imported to be able to use the `abp-permission-*management` in your component. + +As the last step, it is needs to be replaced the `RolesComponent` with the `MyRolesComponent`. Open the `app.component.ts` and modify its content as shown below: + +```js +import { ReplaceableComponentsService } from '@abp/ng.core'; +import { eIdentityComponents } from '@abp/ng.identity'; +import { MyRolesComponent } from './my-roles/my-roles.component'; + +@Component(/* component metadata */) +export class AppComponent { + constructor(private replaceableComponents: ReplaceableComponentsService) { + this.replaceableComponents.add({ component: MyRolesComponent, key: eIdentityComponents.Roles }); + } +} +``` + +After the steps above, the `RolesComponent` has been successfully replaced with the `MyRolesComponent`. When you navigate to the `/identity/roles` URL, you will see the `MyRolesComponent`'s template and see the extension components working correctly. + +![my-roles-component-with-extensions](./images/my-roles-component-with-extensions.jpg) + +![my-roles-component-form-extensions](./images/my-roles-component-form-extensions.jpg) \ No newline at end of file diff --git a/docs/en/UI/Angular/images/my-roles-component-form-extensions.jpg b/docs/en/UI/Angular/images/my-roles-component-form-extensions.jpg new file mode 100644 index 0000000000..a686d26edc Binary files /dev/null and b/docs/en/UI/Angular/images/my-roles-component-form-extensions.jpg differ diff --git a/docs/en/UI/Angular/images/my-roles-component-with-extensions.jpg b/docs/en/UI/Angular/images/my-roles-component-with-extensions.jpg new file mode 100644 index 0000000000..1ac82c40b7 Binary files /dev/null and b/docs/en/UI/Angular/images/my-roles-component-with-extensions.jpg differ diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index c0a4d4d2bc..369d14371c 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -847,6 +847,27 @@ { "text": "Custom Setting Page", "path": "UI/Angular/Custom-Setting-Page.md" + }, + { + "text": "Extensions", + "items": [ + { + "text": "Entity Action Extensions", + "path": "UI/Angular/Entity-Action-Extensions.md" + }, + { + "text": "Data Table Column Extensions", + "path": "UI/Angular/Data-Table-Column-Extensions.md" + }, + { + "text": "Page Toolbar Extensions", + "path": "UI/Angular/Page-Toolbar-Extensions.md" + }, + { + "text": "Dynamic Form Extensions", + "path": "UI/Angular/Dynamic-Form-Extensions.md" + } + ] } ] }