diff --git a/docs/en/UI/Angular/Entity-Action-Extensions.md b/docs/en/UI/Angular/Entity-Action-Extensions.md index d46bd8e194..8918bc37a6 100644 --- a/docs/en/UI/Angular/Entity-Action-Extensions.md +++ b/docs/en/UI/Angular/Entity-Action-Extensions.md @@ -4,9 +4,9 @@ Entity action extension system allows you to add a new action to the action menu for an entity. A "Click Me" action was added to the user management page below: -![Entity Action Extension Example: "Click Me!" Action](images/user-action-extension-click-me-ng.png) +Entity Action Extension Example: 'Click Me!' Action -You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can access to the current entity in your code. +You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access the current entity in your code. ## How to Set Up @@ -17,10 +17,14 @@ In this example, we will add a "Click Me!" action and alert the current row's `u The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root module: ```js -// entity-action-contributors.ts +// src/app/entity-action-contributors.ts +import { + eIdentityComponents, + IdentityEntityActionContributors, + IdentityUserDto, +} from '@abp/ng.identity'; import { EntityAction, EntityActionList } from '@abp/ng.theme.shared/extensions'; -import { IdentityEntityActionContributors, IdentityUserDto } from '@volo/abp.ng.identity'; const alertUserName = new EntityAction({ text: 'Click Me!', @@ -31,9 +35,7 @@ const alertUserName = new EntityAction({ // See EntityActionOptions in API section for all options }); -export function alertUserNameContributor( - actionList: EntityActionList, -) { +export function alertUserNameContributor(actionList: EntityActionList) { actionList.addTail(alertUserName); } @@ -48,243 +50,199 @@ export const identityEntityActionContributors: IdentityEntityActionContributors The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addTail` method, which adds the given value to the end of the list. You may find [all available methods here](../Common/Utils/Linked-List.md). -> **Important Note 1:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `alertUserNameContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details. - -> **Important Note 2:** Please use one of the following if Ivy is not enabled in your project. Otherwise, you will get an "Expression form not supported." error. - -```js -export const identityEntityActionContributors: IdentityEntityActionContributors = { - 'Identity.UsersComponent': [ alertUserNameContributor ], -}; - -/* OR */ - -const identityContributors: IdentityEntityActionContributors = {}; -identityContributors[eIdentityComponents.Users] = [ alertUserNameContributor ]; -export const identityEntityActionContributors = identityContributors; -``` - ### Step 2. Import and Use Entity Action Contributors Import `identityEntityActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below: ```js +// src/app/app-routing.module.ts + +// other imports import { identityEntityActionContributors } from './entity-action-contributors'; const routes: Routes = [ + // other routes + { - path: '', - component: DynamicLayoutComponent, - children: [ - { - path: 'identity', - loadChildren: () => - import('@volo/abp.ng.identity').then(m => - m.IdentityModule.forLazy({ - entityActionContributors: identityEntityActionContributors, - }), - ), - }, - // other child routes - ], - // other routes - } + path: 'identity', + loadChildren: () => + import('@abp/ng.identity').then(m => + m.IdentityModule.forLazy({ + entityActionContributors: identityEntityActionContributors, + }) + ), + }, + + // other routes ]; ``` -That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the users page (`UsersComponent`) of the `IdentityModule`. +That is it, `alertUserName` entity action will be added as the last action on the grid dropdown in the "Users" page (`UsersComponent`) of the `IdentityModule`. ## How to Place a Custom Modal and Trigger It by Entity Actions -Incase you need to place a custom modal that will be triggered by an entity action, there are two ways to do it: A quick one and an elaborate one. +Let's employ dependency injection to extend the functionality of `IdentityModule` and add a quick view action for the User entity. We will take a lazy-loaded approach. -### The Quick Solution +Entity Action Extension Example: Custom Modal -1. Place your custom modal inside `AppComponent` template. - ```html - - -

-
+1. Create a folder at this path: `src/app/identity-extended` - - - - - - - - - -
- ``` - -2. Add the following inside your `AppComponent` class: - ```js - isModalOpen: boolean; - - openModal(/* may take parameters */) { - /* and set things before showing the modal */ - this.isModalOpen = true; - } - ``` - -3. Add an entity action similar to this: +2. Add an entity action similar to this: ```js - const customModalAction = new EntityAction({ - text: 'Custom Modal Action', + // src/app/identity-extended/entity-action-contributors.ts + + import { + eIdentityComponents, + IdentityEntityActionContributors, + IdentityUserDto, + } from '@abp/ng.identity'; + import { EntityAction, EntityActionList } from '@abp/ng.theme.shared/extensions'; + import { IdentityExtendedComponent } from './identity-extended.component'; + + const quickViewAction = new EntityAction({ + text: 'Quick View', action: data => { - const component = data.getInjected(AppComponent); - component.openModal(/* you may pass parameters */); + const component = data.getInjected(IdentityExtendedComponent); + component.openUserQuickView(data.record); }, }); - ``` - -That should work. However, there is a longer but lazy-loading solution, and we are going to use NGXS for it. -### The Elaborate Solution + export function customModalContributor(actionList: EntityActionList) { + actionList.addTail(quickViewAction); + } -Consider the modal will be displayed in the Identity module. How can we lazy-load it too? + export const identityEntityActionContributors: IdentityEntityActionContributors = { + // enum indicates the page to add contributors to + [eIdentityComponents.Users]: [ + customModalContributor, + // You can add more contributors here + ], + }; + ``` -1. Create a folder called `identity-extended` inside your app folder. -2. Create a file called `identity-popups.store.ts` in it. -3. Insert the following code in the new file: +3. Create a parent component to the identity module. ```js - import { Action, Selector, State, StateContext } from '@ngxs/store'; + // src/app/identity-extended/identity-extended.component.ts - export class ToggleIdentityPopup { - static readonly type = '[IdentityPopups] Toggle'; - constructor(public readonly payload: boolean) {} - } + import { IdentityUserDto } from '@abp/ng.identity'; + import { Component } from '@angular/core'; - @State({ - name: 'IdentityPopups', - defaults: { - isVisible: false, - }, + @Component({ + selector: 'app-identity-extended', + templateUrl: './identity-extended.component.html', }) - export class IdentityPopupsState { - @Selector() - static isVisible(state: IdentityPopupsStateModel) { - return state.isVisible; - } + export class IdentityExtendedComponent { + isUserQuickViewVisible: boolean; + + user: IdentityUserDto; - @Action(ToggleIdentityPopup) - toggleModal( - context: StateContext, - { payload }: ToggleIdentityPopup, - ) { - context.patchState({ isVisible: payload }); + openUserQuickView(record: IdentityUserDto) { + this.user = new Proxy(record, { + get: (target, prop) => target[prop] || '—', + }); + this.isUserQuickViewVisible = true; } } + ``` - interface IdentityPopupsStateModel { - isVisible: boolean; - } +4. Add a router outlet and a modal to the parent component. + ```html + + + + + + +

{{ user.userName }}

+
+ + + + + + + + + + + + + + + + + + + + + +
{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}{{ user.name }}
{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}{{ user.surname }}
{{ 'AbpIdentity::EmailAddress' | abpLocalization }}{{ user.email }}
{{ 'AbpIdentity::PhoneNumber' | abpLocalization }}{{ user.phoneNumber }}
+
+ + + + +
``` -4. Create a file called `identity-extended.module.ts` in the same folder. -5. Insert the following code in the new file: +5. Add a module for the component and load `IdentityModule` as seen below: ```js + // src/app/identity-extended/identity-extended.module.ts + import { CoreModule } from '@abp/ng.core'; + import { IdentityModule } from '@abp/ng.identity'; import { ThemeSharedModule } from '@abp/ng.theme.shared'; - import { Component, NgModule } from '@angular/core'; + import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; - import { NgxsModule, Select, Store } from '@ngxs/store'; - import { Observable } from 'rxjs'; - import { IdentityPopupsState, ToggleIdentityPopup } from './identity-popups.store'; - - @Component({ - template: ` - - - `, - }) - export class IdentityOutletComponent {} - - @Component({ - template: ` - - -

-
- - - - - - - - - - -
- `, - }) - export class IdentityPopupsComponent { - @Select(IdentityPopupsState.isVisible) - isVisible$: Observable; - - constructor(private store: Store) {} - - onDisappear() { - this.store.dispatch(new ToggleIdentityPopup(false)); - } - } + import { identityEntityActionContributors } from './entity-action-contributors'; + import { IdentityExtendedComponent } from './identity-extended.component'; @NgModule({ - declarations: [IdentityPopupsComponent, IdentityOutletComponent], imports: [ CoreModule, ThemeSharedModule, - NgxsModule.forFeature([IdentityPopupsState]), RouterModule.forChild([ { path: '', - component: IdentityOutletComponent, + component: IdentityExtendedComponent, children: [ { path: '', - outlet: 'popup', - component: IdentityPopupsComponent, - }, - { - path: '', - loadChildren: () => import('@volo/abp.ng.identity').then(m => m.IdentityModule), + loadChildren: () => + IdentityModule.forLazy({ + entityActionContributors: identityEntityActionContributors, + }), }, ], }, ]), ], + declarations: [IdentityExtendedComponent], }) export class IdentityExtendedModule {} ``` -6. Change the `identity` path in your `AppRoutingModule` to this: +6. Load `IdentityExtendedModule` instead of `IdentityModule` in your root routing module. ```js - { - path: 'identity', - loadChildren: () => - import('./identity-extended/identity-extended.module').then(m => m.IdentityExtendedModule), - }, - ``` + // src/app/app-routing.module.ts -7. Add an entity action similar to this: - ```js - const customModalAction = new EntityAction({ - text: 'Custom Modal Action', - action: data => { - const store = data.getInjected(Store); - store.dispatch(new ToggleIdentityPopup(true)); + const routes: Routes = [ + // other routes + + { + path: 'identity', + loadChildren: () => + import('./identity-extended/identity-extended.module') + .then(m => m.IdentityExtendedModule), }, - }); + + // other routes + ]; ``` -It should now be working well with lazy-loading. The files are compact in the description to make it quicker to explain. You may split the files as you wish. +That's it. As you see, we reached the `IdentityExtendedComponent` through dependency injection and called one of its methods in our action. The specific user was also available via `data.record`, so we were able to display a summary view. ## API @@ -374,7 +332,7 @@ You may find a full example below. ```js const options: EntityActionOptions = { action: data => { - const component = data.getInjected(UsersComponent); + const component = data.getInjected(IdentityExtendedComponent); component.unlock(data.record.id); }, text: 'AbpIdentity::Unlock', diff --git a/docs/en/UI/Angular/images/entity-action-extensions---click-me.gif b/docs/en/UI/Angular/images/entity-action-extensions---click-me.gif new file mode 100644 index 0000000000..6f5191874d Binary files /dev/null and b/docs/en/UI/Angular/images/entity-action-extensions---click-me.gif differ diff --git a/docs/en/UI/Angular/images/entity-action-extensions---custom-modal.gif b/docs/en/UI/Angular/images/entity-action-extensions---custom-modal.gif new file mode 100644 index 0000000000..0b1010e029 Binary files /dev/null and b/docs/en/UI/Angular/images/entity-action-extensions---custom-modal.gif differ