docs/en/framework/ui/angular/entity-action-extensions.md
//[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 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:
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.
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.
The following code prepares a constant named identityEntityActionContributors, ready to be imported and used in your root application configuration:
// 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<IdentityUserDto>({
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<IdentityUserDto>) {
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.
Import identityEntityActionContributors in your routing configuration and pass it to the static configureRoutes method for identity routes as seen below:
// 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.
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.
Create a folder at this path: src/app/identity-extended
Add an entity action similar to this:
// 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<IdentityUserDto>({
text: 'Quick View',
action: data => {
const component = data.getInjected(IdentityExtendedComponent);
component.openUserQuickView(data.record);
},
});
export function customModalContributor(actionList: EntityActionList<IdentityUserDto>) {
actionList.addTail(quickViewAction);
}
export const identityEntityActionContributors: IdentityEntityActionContributors = {
// enum indicates the page to add contributors to
[eIdentityComponents.Users]: [
customModalContributor,
// You can add more contributors here
],
};
// 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;
}
}
<!-- src/app/identity-extended/identity-extended.component.html -->
<router-outlet></router-outlet>
<abp-modal [(visible)]="isUserQuickViewVisible">
<ng-template #abpHeader>
<h3>{%{{{ user.userName }}}%}</h3>
</ng-template>
<ng-template #abpBody>
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">{%{{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}}%}</th>
<td>{%{{{ user.name }}}%}</td>
</tr>
<tr>
<th scope="row">{%{{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}}%}</th>
<td>{%{{{ user.surname }}}%}</td>
</tr>
<tr>
<th scope="row">{%{{{ 'AbpIdentity::EmailAddress' | abpLocalization }}}%}</th>
<td>{%{{{ user.email }}}%}</td>
</tr>
<tr>
<th scope="row">{%{{{ 'AbpIdentity::PhoneNumber' | abpLocalization }}}%}</th>
<td>{%{{{ user.phoneNumber }}}%}</td>
</tr>
</tbody>
</table>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{%{{{ 'AbpUi::Close' | abpLocalization }}}%}
</button>
</ng-template>
</abp-modal>
// 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,
}),
),
},
],
},
];
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.// 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.
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.
{
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. You can use it to reach injected dependencies of GridActionsComponent, including, but not limited to, its parent component.
{
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 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:
type ActionCallback<T, R = any> = (data?: ActionData<T>) => R;
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:
type ActionPredicate<T> = (data?: ActionData<T>) => boolean;
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:
type EntityActionOptions<R = any> = {
action: ActionCallback<R>,
text: string,
icon?: string,
permission?: string,
visible?: ActionPredicate<R>,
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:
'')undefined)() => true)'btn btn-primary text-center')'')false)You may find a full example below.
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.
const options: EntityActionOptions<IdentityUserDto> = {
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.
const action = EntityAction.create(options);
EntityAction with given array of EntityActionOptions.
const actions = EntityAction.createMany(optionsArray);
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.
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:
export function reorderUserContributors(
actionList: EntityActionList<IdentityUserDto>,
) {
// 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 is the type that you can pass as entity action contributor callbacks to static createRoutes methods of the packages.
// lockUserContributor should have EntityActionContributorCallback<IdentityUserDto> type
export function lockUserContributor(
actionList: EntityActionList<IdentityUserDto>,
) {
// add lockUser as 3rd action
actionList.add(lockUser).byIndex(2);
}
export const identityEntityActionContributors = {
[eIdentityComponents.Users]: [lockUserContributor],
};