```json //[doc-seo] { "Description": "Learn how to enhance your Angular UI with custom entity actions, enabling dynamic functionality like modals and API calls in ABP Framework." } ``` # Entity Action Extensions for Angular UI ## Introduction 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 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 In this example, we will add a "Click Me!" action and alert the current row's `userName` in the user management page of the [Identity Module](../../../modules/identity.md). ### Step 1. Create Entity Action Contributors The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root application configuration: ```ts // src/app/entity-action-contributors.ts import { eIdentityComponents, IdentityEntityActionContributors } from '@abp/ng.identity'; import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { EntityAction, EntityActionList } from '@abp/ng.components/extensible'; const alertUserName = new EntityAction({ text: 'Click Me!', action: (data) => { // Replace alert with your custom code alert(data.record.userName); }, // See EntityActionOptions in API section for all options }); export function alertUserNameContributor(actionList: EntityActionList) { actionList.addTail(alertUserName); } export const identityEntityActionContributors: IdentityEntityActionContributors = { // enum indicates the page to add contributors to [eIdentityComponents.Users]: [ alertUserNameContributor, // You can add more contributors here ], }; ``` 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). ### Step 2. Import and Use Entity Action Contributors Import `identityEntityActionContributors` in your routing configuration and pass it to the static `configureRoutes` method for `identity` routes as seen below: ```js // src/app/app.routes.ts // other imports import { identityEntityActionContributors } from './entity-action-contributors'; export const APP_ROUTES: Routes = [ // other routes { path: 'identity', loadChildren: () => import('@abp/ng.identity').then(c => c.createRoutes({ 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 `identity` package. ## How to Place a Custom Modal and Trigger It by Entity Actions Let's employ dependency injection to extend the functionality of `identity` package and add a quick view action for the User entity. We will take a lazy-loaded approach. Entity Action Extension Example: Custom Modal 1. Create a folder at this path: `src/app/identity-extended` 2. Add an entity action similar to this: ```js // src/app/identity-extended/entity-action-contributors.ts import { eIdentityComponents, IdentityEntityActionContributors, IdentityUserDto, } from '@abp/ng.identity'; import { EntityAction, EntityActionList } from '@abp/ng.components/extensible'; import { IdentityExtendedComponent } from './identity-extended.component'; const quickViewAction = new EntityAction({ text: 'Quick View', action: data => { const component = data.getInjected(IdentityExtendedComponent); component.openUserQuickView(data.record); }, }); export function customModalContributor(actionList: EntityActionList) { actionList.addTail(quickViewAction); } export const identityEntityActionContributors: IdentityEntityActionContributors = { // enum indicates the page to add contributors to [eIdentityComponents.Users]: [ customModalContributor, // You can add more contributors here ], }; ``` 3. Create a parent component to the identity package. ```js // src/app/identity-extended/identity-extended.component.ts import { LocalizationPipe } from '@abp/ng.core'; import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { ModalCloseDirective, ModalComponent } from '@abp/ng.theme.shared'; import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-identity-extended', templateUrl: './identity-extended.component.html', imports: [ CommonModule, ModalComponent, RouterOutlet, LocalizationPipe, ModalCloseDirective ] }) export class IdentityExtendedComponent { isUserQuickViewVisible: boolean; user: IdentityUserDto; openUserQuickView(record: IdentityUserDto) { this.user = new Proxy(record, { get: (target, prop) => target[prop] || '—', }); this.isUserQuickViewVisible = true; } } ``` 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 }}}%}
``` 5. Add a routing configuration for the component as seen below: ```js // src/app/identity-extended/identity-extended.routes.ts import { Routes } from '@angular/router'; import { IdentityExtendedComponent } from './identity-extended.component'; import { identityEntityActionContributors } from './entity-action-contributors'; export const createExtendedIdentityRoutes = (): Routes => [ { path: '', component: IdentityExtendedComponent, children: [ { path: '', loadChildren: () => import('@abp/ng.identity').then(c => c.createRoutes({ entityActionContributors: identityEntityActionContributors, }), ), }, ], }, ]; ``` 6. Use `createExtendedIdentityRoutes` instead of the `createRoutes` function in your root routing configuration. Since the routes are already lazily loaded in the `createExtendedIdentityRoutes` function, you can directly use its children array to avoid an unnecessary additional lazy-loading call. ```js // src/app/app.routes.ts export const APP_ROUTES: Routes = [ // other routes { path: 'identity', children: [ ...createExtendedIdentityRoutes() ], }, // other routes ]; ``` 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 ### ActionData\ `ActionData` is the shape of the parameter passed to all callbacks or predicates in an `EntityAction`. It has the following properties: - **record** is the row data, i.e. current value rendered in the table. ```js { text: 'Click Me!', action: data => { alert(data.record.userName); }, } ``` - **index** is the table index where the record is at. - **getInjected** is the equivalent of [Injector.get](https://angular.dev/api/core/Injector). You can use it to reach injected dependencies of `GridActionsComponent`, including, but not limited to, its parent component. ```js { text: 'Click Me!', action: data => { const restService = data.getInjected(RestService); // Use restService public props and methods here }, visible: data => { const usersComponent = data.getInjected(UsersComponent); // Use usersComponent public props and methods here }, } ``` ### ActionCallback\ `ActionCallback` is the type of the callback function that can be passed to an `EntityAction` as `action` parameter. An action callback gets a single parameter, the `ActionData`. The return type may be anything, including `void`. Here is a simplified representation: ```js type ActionCallback = (data?: ActionData) => R; ``` ### ActionPredicate\ `ActionPredicate` is the type of the predicate function that can be passed to an `EntityAction` as `visible` parameter. An action predicate gets a single parameter, the `ActionData`. The return type must be `boolean`. Here is a simplified representation: ```js type ActionPredicate = (data?: ActionData) => boolean; ``` ### EntityActionOptions\ `EntityActionOptions` is the type that defines required and optional properties you have to pass in order to create an entity action. Its type definition is as follows: ```js type EntityActionOptions = { action: ActionCallback, text: string, icon?: string, permission?: string, visible?: ActionPredicate, btnClass?: string, btnStyle?: string, showOnlyIcon?: boolean, tooltip?: FormPropTooltip; }; ``` As you see, passing `action` and `text` is enough to create an entity action. Here is what each property is good for: - **action** is a callback that is called when the grid action is clicked. (_required_) - **text** is the button text which will be localized. (_required_) - **icon** is the classes that define an icon to be placed before the text. (_default:_ `''`) - **permission** is the permission context which will be used to decide if this type of grid action should be displayed to the user or not. (_default:_ `undefined`) - **visible** is a predicate that will be used to decide if the current record should have this grid action or not. (_default:_ `() => true`) - **btnClass** is the classes that will be applied to the button. (_default:_ `'btn btn-primary text-center'`) - **btnStyle** is the styles that will be applied to the button. (_default:_ `''`) - **showOnlyIcon** is shows only the icon itself. (_default:_ `false`) - **tooltip** is only available in single entity action button. Adds an tooltip for button. (_default:_ undefined) You may find a full example below. ### EntityAction\ `EntityAction` is the class that defines your entity actions. It takes an `EntityActionOptions` and sets the default values to the properties, creating an entity action that can be passed to an entity contributor. ```js const options: EntityActionOptions = { action: data => { const component = data.getInjected(IdentityExtendedComponent); component.unlock(data.record.id); }, text: 'AbpIdentity::Unlock', icon: 'fa fa-unlock', permission: 'AbpIdentity.Users.Update', visible: data => data.record.isLockedOut, btnClass:'btn btn-warning text-center', btnStyle: '', //Adds inline style showOnlyIcon: true, tooltip: { text: 'AbpIdentity::Edit', placement: 'top' } }; const action = new EntityAction(options); ``` It also has two static methods to create its instances: - **EntityAction.create\\(options: EntityActionOptions\\)** is used to create an instance of `EntityAction`. ```js const action = EntityAction.create(options); ``` - **EntityAction.createMany\\(options: EntityActionOptions\\[\]\)** is used to create multiple instances of `EntityAction` with given array of `EntityActionOptions`. ```js const actions = EntityAction.createMany(optionsArray); ``` ### EntityActionList\ `EntityActionList` is the list of actions passed to every action contributor callback as the first parameter named `actionList`. It is a **doubly linked list**. You may find [all available methods here](../common/utils/linked-list.md). The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this: ```js export function reorderUserContributors( actionList: EntityActionList, ) { // drop "Unlock" button const unlockActionNode = actionList.dropByValue( 'AbpIdentity::Unlock', (action, text) => action.text === text, ); // add it back to the head of the list actionList.addHead(unlockActionNode.value); } ``` ### EntityActionContributorCallback\ `EntityActionContributorCallback` is the type that you can pass as entity action contributor callbacks to static `createRoutes` methods of the packages. ```js // lockUserContributor should have EntityActionContributorCallback type export function lockUserContributor( actionList: EntityActionList, ) { // add lockUser as 3rd action actionList.add(lockUser).byIndex(2); } export const identityEntityActionContributors = { [eIdentityComponents.Users]: [lockUserContributor], }; ``` ## See Also - [Customizing Application Modules Guide](../../architecture/modularity/extending/customizing-application-modules-guide.md)