diff --git a/docs/en/UI/Angular/Data-Table-Column-Extensions.md b/docs/en/UI/Angular/Data-Table-Column-Extensions.md index cd2df5cf4f5..6f1367d934b 100644 --- a/docs/en/UI/Angular/Data-Table-Column-Extensions.md +++ b/docs/en/UI/Angular/Data-Table-Column-Extensions.md @@ -5,7 +5,7 @@ Entity prop extension system allows you to add a new column to the data table for an entity or change/remove an already existing one. A "Name" column was added to the user management page below: -![Entity Prop Extension Example: "Name" Column](images/user-prop-extension-name-column-ng.png) +Entity Prop Extension Example: 'Name' Column You will have access to the current entity in your code and display its value, make the column sortable, perform visibility checks, and more. You can also render custom HTML in table cells. @@ -18,10 +18,14 @@ In this example, we will add a "Name" column and display the value of the `name` The following code prepares a constant named `identityEntityPropContributors`, ready to be imported and used in your root module: ```js -// entity-prop-contributors.ts +// src/app/entity-prop-contributors.ts +import { + eIdentityComponents, + IdentityEntityPropContributors, + IdentityUserDto, +} from '@abp/ng.identity'; import { EntityProp, EntityPropList, ePropType } from '@abp/ng.theme.shared/extensions'; -import { IdentityEntityPropContributors, IdentityUserDto } from '@volo/abp.ng.identity'; const nameProp = new EntityProp({ type: ePropType.String, @@ -32,62 +36,45 @@ const nameProp = new EntityProp({ }); export function namePropContributor(propList: EntityPropList) { - propList.addAfter( - nameProp, - 'userName', - (value, name) => value.name === name, - ); + propList.addAfter(nameProp, 'userName', (value, name) => value.name === name); } export const identityEntityPropContributors: IdentityEntityPropContributors = { - 'Identity.UsersComponent': [namePropContributor], + // enum indicates the page to add contributors to + [eIdentityComponents.Users]: [ + namePropContributor, + // You can add more contributors here + ], }; ``` The list of props, conveniently named as `propList`, is a **doubly linked list**. That is why we have used the `addAfter` method, which adds a node with given value after the first node that has the previous value. 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 `namePropContributor` 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 identityEntityPropContributors: IdentityEntityPropContributors = { - 'Identity.UsersComponent': [ namePropContributor ], -}; - -/* OR */ - -const identityContributors: IdentityEntityPropContributors = {}; -identityContributors[eIdentityComponents.Users] = [ namePropContributor ]; -export const identityEntityPropContributors = identityContributors; -``` - ### Step 2. Import and Use Entity Prop Contributors Import `identityEntityPropContributors` 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 { identityEntityPropContributors } from './entity-prop-contributors'; const routes: Routes = [ + // other routes + { - path: '', - component: DynamicLayoutComponent, - children: [ - { - path: 'identity', - loadChildren: () => - import('@volo/abp.ng.identity').then(m => - m.IdentityModule.forLazy({ - entityPropContributors: identityEntityPropContributors, - }), - ), - }, - // other child routes - ], - // other routes - } + path: 'identity', + loadChildren: () => + import('@abp/ng.identity').then(m => + m.IdentityModule.forLazy({ + entityPropContributors: identityEntityPropContributors, + }) + ), + }, + + // other routes ]; ``` @@ -97,12 +84,18 @@ That is it, `nameProp` entity prop will be added, and you will see the "Name" co You can use the `valueResolver` to render an HTML string in the table. Imagine we want to show a red times icon (❌) next to unconfirmed emails and phones, instead of showing a green check icon next to confirmed emails and phones. The contributors below would do that for you. +Entity Prop Extension Example: Custom Cell Render + ```js -// entity-prop-contributors.ts +// src/app/entity-prop-contributors.ts -import { EntityProp, EntityPropList, ePropType } from '@abp/ng.theme.shared/extensions'; -import { IdentityUserDto } from '@volo/abp.ng.identity'; -import { IdentityEntityPropContributors } from '@volo/abp.ng.identity/config'; +import { + eIdentityComponents, + IdentityEntityPropContributors, + IdentityUserDto, +} from '@abp/ng.identity'; +import { EntityProp, EntityPropList } from '@abp/ng.theme.shared/extensions'; +import { of } from 'rxjs'; export function emailPropContributor(propList: EntityPropList) { const index = propList.indexOf('email', (value, name) => value.name === name); @@ -138,7 +131,7 @@ export function phonePropContributor(propList: EntityPropList) } export const identityEntityPropContributors: IdentityEntityPropContributors = { - 'Identity.UsersComponent': [emailPropContributor, phonePropContributor], + [eIdentityComponents.Users]: [emailPropContributor, phonePropContributor], }; ``` diff --git a/docs/en/UI/Angular/Dynamic-Form-Extensions.md b/docs/en/UI/Angular/Dynamic-Form-Extensions.md index 880250a247c..d7780ceec0f 100644 --- a/docs/en/UI/Angular/Dynamic-Form-Extensions.md +++ b/docs/en/UI/Angular/Dynamic-Form-Extensions.md @@ -5,7 +5,7 @@ Form prop extension system allows you to add a new field to the create and/or edit forms for a form or change/remove an already existing one. A "Date of Birth" field was added to the user management page below: -![Form Prop Extension Example: "Date of Birth" Field](images/user-prop-extension-date-of-birth-field-ng.png) +Form Prop Extension Example: 'Date of Birth' Field You can validate the field, perform visibility checks, and do more. You will also have access to the current entity when creating a contibutor for an edit form. @@ -18,16 +18,20 @@ In this example, we will add a "Date of Birth" field in the user management page The following code prepares two constants named `identityCreateFormPropContributors` and `identityEditFormPropContributors`, ready to be imported and used in your root module: ```js -// form-prop-contributors.ts +// src/app/form-prop-contributors.ts -import { Validators } from '@angular/forms'; +import { + eIdentityComponents, + IdentityCreateFormPropContributors, + IdentityUserDto, +} from '@abp/ng.identity'; import { ePropType, FormProp, FormPropList } from '@abp/ng.theme.shared/extensions'; -import { IdentityCreateFormPropContributors, IdentityEditFormPropContributors, IdentityUserDto } from '@volo/abp.ng.identity'; +import { Validators } from '@angular/forms'; const birthdayProp = new FormProp({ type: ePropType.Date, name: 'birthday', - displayName: 'Date of Birth', + displayName: 'AbpIdentity::Birthday', validators: () => [Validators.required], }); @@ -36,63 +40,49 @@ export function birthdayPropContributor(propList: FormPropList) } export const identityCreateFormPropContributors: IdentityCreateFormPropContributors = { - 'Identity.UsersComponent': [birthdayPropContributor], + // enum indicates the page to add contributors to + [eIdentityComponents.Users]: [ + birthdayPropContributor, + // You can add more contributors here + ], }; -export const identityEditFormPropContributors: IdentityEditFormPropContributors = { - 'Identity.UsersComponent': [birthdayPropContributor], -}; +export const identityEditFormPropContributors = identityCreateFormPropContributors; +// you may define different contributors for edit form if you like ``` The list of props, conveniently named as `propList`, is a **doubly linked list**. That is why we have used the `addByIndex` method, which adds the given value to the specified index 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 `birthdayPropContributor` 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 identityCreateFormPropContributors: IdentityCreateFormPropContributors = { - 'Identity.UsersComponent': [ birthdayPropContributor ], -}; - -/* OR */ - -const identityCreateContributors: IdentityCreateFormPropContributors = {}; -identityCreateContributors[eIdentityComponents.Users] = [ birthdayPropContributor ]; -export const identityCreateFormPropContributors = identityCreateContributors; -``` - ### Step 2. Import and Use Form Prop Contributors Import `identityCreateFormPropContributors` and `identityEditFormPropContributors` 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 { identityCreateFormPropContributors, identityEditFormPropContributors, } from './form-prop-contributors'; const routes: Routes = [ + // other routes + { - path: '', - component: DynamicLayoutComponent, - children: [ - { - path: 'identity', - loadChildren: () => - import('@volo/abp.ng.identity').then(m => - m.IdentityModule.forLazy({ - createFormPropContributors: identityCreateFormPropContributors, - editFormPropContributors: identityEditFormPropContributors, - }), - ), - }, - // other child routes - ], - // other routes - } + path: 'identity', + loadChildren: () => + import('@abp/ng.identity').then(m => + m.IdentityModule.forLazy({ + createFormPropContributors: identityCreateFormPropContributors, + editFormPropContributors: identityEditFormPropContributors, + }) + ), + }, + + // other routes ]; ``` diff --git a/docs/en/UI/Angular/Entity-Action-Extensions.md b/docs/en/UI/Angular/Entity-Action-Extensions.md index d46bd8e1943..fd22e293fd1 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/Page-Toolbar-Extensions.md b/docs/en/UI/Angular/Page-Toolbar-Extensions.md index 187ac9387d4..7e639502242 100644 --- a/docs/en/UI/Angular/Page-Toolbar-Extensions.md +++ b/docs/en/UI/Angular/Page-Toolbar-Extensions.md @@ -4,7 +4,7 @@ Page toolbar extension system allows you to add a new action to the toolbar of a page. A "Click Me" action was added to the user management page below: -![Page Toolbar Extension Example: "Click Me!" Action](images/user-page-toolbar-extension-click-me-ng.png) +Page Toolbar 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 to page data (the main record, usually an entity list) in your code. Additionally, you can pass in custom components instead of using the default button. @@ -17,10 +17,14 @@ In this example, we will add a "Click Me!" action and log `userName` of all user The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module: ```js -// toolbar-action-contributors.ts +// src/app/toolbar-action-contributors.ts -import { ToolbarActionList, ToolbarAction } from '@abp/ng.theme.shared/extensions'; -import { IdentityToolbarActionContributors, IdentityUserDto } from '@volo/abp.ng.identity'; +import { + eIdentityComponents, + IdentityToolbarActionContributors, + IdentityUserDto, +} from '@abp/ng.identity'; +import { ToolbarAction, ToolbarActionList } from '@abp/ng.theme.shared/extensions'; const logUserNames = new ToolbarAction({ text: 'Click Me!', @@ -31,9 +35,7 @@ const logUserNames = new ToolbarAction({ // See ToolbarActionOptions in API section for all options }); -export function logUserNamesContributor( - actionList: ToolbarActionList -) { +export function logUserNamesContributor(actionList: ToolbarActionList) { actionList.addHead(logUserNames); } @@ -49,33 +51,30 @@ export const identityToolbarActionContributors: IdentityToolbarActionContributor The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addHead` method, which adds the given value to the beginning of the list. You may find [all available methods here](../Common/Utils/Linked-List.md). -> **Important Note:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `logUserNamesContributor` 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. - ### Step 2. Import and Use Toolbar Action Contributors Import `identityToolbarActionContributors` 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 { identityToolbarActionContributors } from './toolbar-action-contributors'; const routes: Routes = [ + // other routes + { - path: '', - component: DynamicLayoutComponent, - children: [ - { - path: 'identity', - loadChildren: () => - import('@volo/abp.ng.identity').then(m => - m.IdentityModule.forLazy({ - toolbarActionContributors: identityToolbarActionContributors, - }), - ), - }, - // other child routes - ], - // other routes - } + path: 'identity', + loadChildren: () => + import('@abp/ng.identity').then(m => + m.IdentityModule.forLazy({ + toolbarActionContributors: identityToolbarActionContributors, + }) + ), + }, + + // other routes ]; ``` @@ -85,22 +84,22 @@ That is it, `logUserNames` toolbar action will be added as the first action on t In this example, we will add a custom "Click Me!" button and log `userName` of all users in the user management page of the [Identity Module](../../Modules/Identity.md) to the console. +Page Toolbar Extension Example: Custom Component + ### Step 1. Create A Custom Component We need to have a component before we can pass it to the toolbar action contributors: ```js -// click-me-button.component.ts +// src/app/click-me-button.component.ts -import { Component, Inject } from '@angular/core'; +import { IdentityUserDto } from '@abp/ng.identity'; import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.theme.shared/extensions'; -import { IdentityUserDto } from '@volo/abp.ng.identity'; +import { Component, Inject } from '@angular/core'; @Component({ selector: 'app-click-me-button', - template: ` - - `, + template: ``, }) export class ClickMeButtonComponent { constructor( @@ -112,6 +111,7 @@ export class ClickMeButtonComponent { this.data.record.forEach(user => console.log(user.userName)); } } + ``` Here, `EXTENSIONS_ACTION_DATA` token provides us the context from the page toolbar. Therefore, we are able to reach the page data via `record`, which is an array of users, i.e. `IdentityUserDto[]`. @@ -123,11 +123,14 @@ Here, `EXTENSIONS_ACTION_DATA` token provides us the context from the page toolb The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module. When `ToolbarComponent` is used instead of `ToolbarAction`, we can pass a component in: ```js -// toolbar-action-contributors.ts +// src/app/toolbar-action-contributors.ts +import { + eIdentityComponents, + IdentityToolbarActionContributors, + IdentityUserDto, +} from '@abp/ng.identity'; import { ToolbarActionList, ToolbarComponent } from '@abp/ng.theme.shared/extensions'; -import { IdentityUserDto } from '@volo/abp.ng.identity'; -import { IdentityToolbarActionContributors } from '@volo/abp.ng.identity/config'; import { ClickMeButtonComponent } from './click-me-button.component'; const logUserNames = new ToolbarComponent({ @@ -135,9 +138,7 @@ const logUserNames = new ToolbarComponent({ // See ToolbarActionOptions in API section for all options }); -export function logUserNamesContributor( - actionList: ToolbarActionList -) { +export function logUserNamesContributor(actionList: ToolbarActionList) { actionList.addHead(logUserNames); } @@ -153,54 +154,35 @@ export const identityToolbarActionContributors: IdentityToolbarActionContributor The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addHead` method, which adds the given value to the beginning 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 `logUserNamesContributor` 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 identityToolbarActionContributors: IdentityToolbarActionContributors = { - 'Identity.UsersComponent': [ logUserNamesContributor ], -}; - -/* OR */ - -const identityContributors: IdentityToolbarActionContributors = {}; -identityContributors[eIdentityComponents.Users] = [ logUserNamesContributor ]; -export const identityToolbarActionContributors = identityContributors; -``` - ### Step 3. Import and Use Toolbar Action Contributors -Import `identityToolbarActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below. If Ivy is not enabled in your project, do not forget putting `ClickMeButtonComponent` into `entryComponents`: +Import `identityToolbarActionContributors` 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 { identityToolbarActionContributors } from './toolbar-action-contributors'; const routes: Routes = [ + // other routes + { - path: '', - component: DynamicLayoutComponent, - children: [ - { - path: 'identity', - loadChildren: () => - import('@volo/abp.ng.identity').then(m => - m.IdentityModule.forLazy({ - toolbarActionContributors: identityToolbarActionContributors, - }), - ), - }, - // other child routes - ], - // other routes - } + path: 'identity', + loadChildren: () => + import('@abp/ng.identity').then(m => + m.IdentityModule.forLazy({ + toolbarActionContributors: identityToolbarActionContributors, + }) + ), + }, + + // other routes ]; ``` That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `IdentityModule` and it will be triggered by a custom button, i.e. `ClickMeButtonComponent`. Please note that **component projection is not limited to buttons** and you may use other UI components. -![Page Toolbar Extension Example: Custom "Click Me!" Button](images/user-page-toolbar-extension-custom-click-me-ng.png) - ## How to Place a Custom Modal and Trigger It by Toolbar Actions Please check the same topic in [entity action extensions document](Entity-Action-Extensions.md) and replace entity action with a toolbar action. 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 00000000000..6f5191874d6 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 00000000000..0b1010e029a Binary files /dev/null and b/docs/en/UI/Angular/images/entity-action-extensions---custom-modal.gif differ diff --git a/docs/en/UI/Angular/images/entity-prop-extensions---custom-cell.gif b/docs/en/UI/Angular/images/entity-prop-extensions---custom-cell.gif new file mode 100644 index 00000000000..2f3d79f7010 Binary files /dev/null and b/docs/en/UI/Angular/images/entity-prop-extensions---custom-cell.gif differ diff --git a/docs/en/UI/Angular/images/entity-prop-extensions---name-column.gif b/docs/en/UI/Angular/images/entity-prop-extensions---name-column.gif new file mode 100644 index 00000000000..7c8be587596 Binary files /dev/null and b/docs/en/UI/Angular/images/entity-prop-extensions---name-column.gif differ diff --git a/docs/en/UI/Angular/images/form-prop-extensions---birthday-field.gif b/docs/en/UI/Angular/images/form-prop-extensions---birthday-field.gif new file mode 100644 index 00000000000..dd19f834e0a Binary files /dev/null and b/docs/en/UI/Angular/images/form-prop-extensions---birthday-field.gif differ diff --git a/docs/en/UI/Angular/images/toolbar-action-extensions---click-me.gif b/docs/en/UI/Angular/images/toolbar-action-extensions---click-me.gif new file mode 100644 index 00000000000..b6585184f1b Binary files /dev/null and b/docs/en/UI/Angular/images/toolbar-action-extensions---click-me.gif differ diff --git a/docs/en/UI/Angular/images/toolbar-action-extensions---custom-component.gif b/docs/en/UI/Angular/images/toolbar-action-extensions---custom-component.gif new file mode 100644 index 00000000000..9775dbd1f02 Binary files /dev/null and b/docs/en/UI/Angular/images/toolbar-action-extensions---custom-component.gif differ