Component Replacement

You can replace some ABP components with your custom components.

The reason that you can replace but cannot customize default ABP components is disabling or changing a part of that component can cause problems. So we named those components as Replaceable Components.

How to Replace a Component

Create a new component that you want to use instead of an ABP component. Add that component to declarations and entryComponents in the AppModule.

Then, open the app.component.ts and dispatch the AddReplaceableComponent action to replace your component with an ABP component as shown below:

  1. import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
  2. import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
  3. import { Store } from '@ngxs/store'; // imported Store
  4. //...
  5. @Component(/* component metadata */)
  6. export class AppComponent {
  7. constructor(
  8. private store: Store // injected Store
  9. )
  10. {
  11. // dispatched the AddReplaceableComponent action
  12. this.store.dispatch(
  13. new AddReplaceableComponent({
  14. component: YourNewRoleComponent,
  15. key: eIdentityComponents.Roles,
  16. }),
  17. );
  18. }
  19. }

Example Usage

How to Replace a Layout

Each ABP theme module has 3 layouts named ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent. These layouts can be replaced the same way.

A layout component template should contain <router-outlet></router-outlet> element.

The example below describes how to replace the ApplicationLayoutComponent:

Run the following command to generate a layout in angular folder:

  1. yarn ng generate component my-application-layout

Add the following code in your layout template (my-layout.component.html) where you want the page to be loaded.

  1. <router-outlet></router-outlet>

Open app.component.ts in src/app folder and modify it as shown below:

  1. import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
  2. import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
  3. import { Store } from '@ngxs/store'; // imported Store
  4. import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
  5. @Component(/* component metadata */)
  6. export class AppComponent {
  7. constructor(
  8. private store: Store, // injected Store
  9. ) {
  10. // dispatched the AddReplaceableComponent action
  11. this.store.dispatch(
  12. new AddReplaceableComponent({
  13. component: MyApplicationLayoutComponent,
  14. key: eThemeBasicComponents.ApplicationLayout,
  15. }),
  16. );
  17. }
  18. }

If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the AddReplaceableComponent action as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of AddReplaceableComponent is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.

Layout Components

Layout Components

How to Replace LogoComponent

LogoComponent

Run the following command in angular folder to create a new component called LogoComponent.

  1. yarn ng generate component logo --inlineTemplate --inlineStyle --entryComponent
  2. # You don't need the --entryComponent option in Angular 9

Open the generated logo.component.ts in src/app/logo folder and replace its content with the following:

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'app-logo',
  4. template: `
  5. <a class="navbar-brand" routerLink="/">
  6. <!-- Change the img src -->
  7. <img
  8. src="https://via.placeholder.com/100x50/343a40/FF0000?text=MyLogo"
  9. alt="logo"
  10. width="100%"
  11. height="auto"
  12. />
  13. </a>
  14. `,
  15. })
  16. export class LogoComponent {}

Open app.component.ts in src/app folder and modify it as shown below:

  1. import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
  2. import { Store } from '@ngxs/store'; // imported Store
  3. import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
  4. import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
  5. //...
  6. @Component(/* component metadata */)
  7. export class AppComponent implements OnInit {
  8. constructor(..., private store: Store) {} // injected Store
  9. ngOnInit() {
  10. //...
  11. // added dispatch
  12. this.store.dispatch(
  13. new AddReplaceableComponent({
  14. component: LogoComponent,
  15. key: eThemeBasicComponents.Logo,
  16. }),
  17. );
  18. }
  19. }

The final UI looks like below:

New logo

How to Replace RoutesComponent

RoutesComponent

Run the following command in angular folder to create a new component called RoutesComponent.

  1. yarn ng generate component routes --entryComponent
  2. # You don't need the --entryComponent option in Angular 9

Open the generated routes.component.ts in src/app/routes folder and replace its content with the following:

  1. import { ABP, ReplaceableComponents } from '@abp/ng.core';
  2. import {
  3. Component,
  4. HostBinding,
  5. Inject,
  6. Renderer2,
  7. TrackByFunction,
  8. AfterViewInit,
  9. } from '@angular/core';
  10. import { fromEvent } from 'rxjs';
  11. import { debounceTime } from 'rxjs/operators';
  12. @Component({
  13. selector: 'app-routes',
  14. templateUrl: 'routes.component.html',
  15. })
  16. export class RoutesComponent implements AfterViewInit {
  17. @HostBinding('class.mx-auto')
  18. marginAuto = true;
  19. smallScreen = window.innerWidth < 992;
  20. constructor(private renderer: Renderer2) {}
  21. ngAfterViewInit() {
  22. fromEvent(window, 'resize')
  23. .pipe(debounceTime(150))
  24. .subscribe(() => {
  25. this.smallScreen = window.innerWidth < 992;
  26. });
  27. }
  28. }

Open the generated routes.component.html in src/app/routes folder and replace its content with the following:

  1. <ul class="navbar-nav">
  2. <li class="nav-item">
  3. <a class="nav-link" routerLink="/"
  4. ><i class="fas fa-home"></i> {{ '::Menu:Home' | abpLocalization }}</a
  5. >
  6. </li>
  7. <li class="nav-item">
  8. <a class="nav-link" routerLink="/my-page"><i class="fas fa-newspaper mr-1"></i>My Page</a>
  9. </li>
  10. <li
  11. #navbarRootDropdown
  12. [abpVisibility]="routeContainer"
  13. class="nav-item dropdown"
  14. display="static"
  15. (click)="
  16. navbarRootDropdown.expand
  17. ? (navbarRootDropdown.expand = false)
  18. : (navbarRootDropdown.expand = true)
  19. "
  20. >
  21. <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
  22. <i class="fas fa-wrench"></i>
  23. {{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}
  24. </a>
  25. <div
  26. #routeContainer
  27. class="dropdown-menu border-0 shadow-sm"
  28. (click)="$event.preventDefault(); $event.stopPropagation()"
  29. [class.d-block]="smallScreen && navbarRootDropdown.expand"
  30. >
  31. <div
  32. class="dropdown-submenu"
  33. ngbDropdown
  34. #dropdownSubmenu="ngbDropdown"
  35. placement="right-top"
  36. [autoClose]="true"
  37. *abpPermission="'AbpIdentity.Roles || AbpIdentity.Users'"
  38. >
  39. <div ngbDropdownToggle [class.dropdown-toggle]="false">
  40. <a
  41. abpEllipsis="210px"
  42. [abpEllipsisEnabled]="!smallScreen"
  43. role="button"
  44. class="btn d-block text-left dropdown-toggle"
  45. >
  46. <i class="fa fa-id-card-o"></i>
  47. {{ 'AbpIdentity::Menu:IdentityManagement' | abpLocalization }}
  48. </a>
  49. </div>
  50. <div
  51. #childrenContainer
  52. class="dropdown-menu border-0 shadow-sm"
  53. [class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
  54. >
  55. <div class="dropdown-submenu" *abpPermission="'AbpIdentity.Roles'">
  56. <a class="dropdown-item" routerLink="/identity/roles">
  57. {{ 'AbpIdentity::Roles' | abpLocalization }}</a
  58. >
  59. </div>
  60. <div class="dropdown-submenu" *abpPermission="'AbpIdentity.Users'">
  61. <a class="dropdown-item" routerLink="/identity/users">
  62. {{ 'AbpIdentity::Users' | abpLocalization }}</a
  63. >
  64. </div>
  65. </div>
  66. </div>
  67. <div
  68. class="dropdown-submenu"
  69. ngbDropdown
  70. #dropdownSubmenu="ngbDropdown"
  71. placement="right-top"
  72. [autoClose]="true"
  73. *abpPermission="'AbpTenantManagement.Tenants'"
  74. >
  75. <div ngbDropdownToggle [class.dropdown-toggle]="false">
  76. <a
  77. abpEllipsis="210px"
  78. [abpEllipsisEnabled]="!smallScreen"
  79. role="button"
  80. class="btn d-block text-left dropdown-toggle"
  81. >
  82. <i class="fa fa-users"></i>
  83. {{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}
  84. </a>
  85. </div>
  86. <div
  87. #childrenContainer
  88. class="dropdown-menu border-0 shadow-sm"
  89. [class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
  90. >
  91. <div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
  92. <a class="dropdown-item" routerLink="/tenant-management/tenants">
  93. {{ 'AbpTenantManagement::Tenants' | abpLocalization }}</a
  94. >
  95. </div>
  96. </div>
  97. </div>
  98. </div>
  99. </li>
  100. </ul>

Open app.component.ts in src/app folder and modify it as shown below:

  1. import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
  2. import { Store } from '@ngxs/store'; // imported Store
  3. import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent
  4. import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
  5. //...
  6. @Component(/* component metadata */)
  7. export class AppComponent implements OnInit {
  8. constructor(..., private store: Store) {} // injected Store
  9. ngOnInit() {
  10. //...
  11. // added dispatch
  12. this.store.dispatch(
  13. new AddReplaceableComponent({
  14. component: RoutesComponent,
  15. key: eThemeBasicComponents.Routes,
  16. }),
  17. );
  18. }
  19. }

The final UI looks like below:

New routes

How to Replace NavItemsComponent

NavItemsComponent

Run the following command in angular folder to create a new component called NavItemsComponent.

  1. yarn ng generate component nav-items --entryComponent
  2. # You don't need the --entryComponent option in Angular 9

Open the generated nav-items.component.ts in src/app/nav-items folder and replace the content with the following:

  1. import {
  2. ApplicationConfiguration,
  3. AuthService,
  4. ConfigState,
  5. SessionState,
  6. SetLanguage,
  7. } from '@abp/ng.core';
  8. import { Component, AfterViewInit } from '@angular/core';
  9. import { Navigate, RouterState } from '@ngxs/router-plugin';
  10. import { Select, Store } from '@ngxs/store';
  11. import { Observable, fromEvent } from 'rxjs';
  12. import { map, debounceTime } from 'rxjs/operators';
  13. import snq from 'snq';
  14. @Component({
  15. selector: 'app-nav-items',
  16. templateUrl: 'nav-items.component.html',
  17. })
  18. export class NavItemsComponent implements AfterViewInit {
  19. @Select(ConfigState.getOne('currentUser'))
  20. currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
  21. @Select(ConfigState.getDeep('localization.languages'))
  22. languages$: Observable<ApplicationConfiguration.Language[]>;
  23. smallScreen = window.innerWidth < 992;
  24. get defaultLanguage$(): Observable<string> {
  25. return this.languages$.pipe(
  26. map(
  27. languages =>
  28. snq(
  29. () => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
  30. ),
  31. '',
  32. ),
  33. );
  34. }
  35. get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
  36. return this.languages$.pipe(
  37. map(
  38. languages =>
  39. snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
  40. [],
  41. ),
  42. );
  43. }
  44. get selectedLangCulture(): string {
  45. return this.store.selectSnapshot(SessionState.getLanguage);
  46. }
  47. constructor(private store: Store, private authService: AuthService) {}
  48. ngAfterViewInit() {
  49. fromEvent(window, 'resize')
  50. .pipe(debounceTime(150))
  51. .subscribe(() => {
  52. this.smallScreen = window.innerWidth < 992;
  53. });
  54. }
  55. onChangeLang(cultureName: string) {
  56. this.store.dispatch(new SetLanguage(cultureName));
  57. }
  58. logout() {
  59. this.authService.logout().subscribe(() => {
  60. this.store.dispatch(
  61. new Navigate(['/'], null, {
  62. state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
  63. }),
  64. );
  65. });
  66. }
  67. }

Open the generated nav-items.component.html in src/app/nav-items folder and replace the content with the following:

  1. <ul class="navbar-nav">
  2. <input type="search" placeholder="Search" class="bg-transparent border-0 text-white" />
  3. <li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
  4. <div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
  5. <a
  6. ngbDropdownToggle
  7. class="nav-link"
  8. href="javascript:void(0)"
  9. role="button"
  10. id="dropdownMenuLink"
  11. data-toggle="dropdown"
  12. aria-haspopup="true"
  13. aria-expanded="false"
  14. >
  15. {{ defaultLanguage$ | async }}
  16. </a>
  17. <div
  18. class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
  19. aria-labelledby="dropdownMenuLink"
  20. [class.d-block]="smallScreen && languageDropdown.isOpen()"
  21. >
  22. <a
  23. *ngFor="let lang of dropdownLanguages$ | async"
  24. href="javascript:void(0)"
  25. class="dropdown-item"
  26. (click)="onChangeLang(lang.cultureName)"
  27. >{{ lang?.displayName }}</a
  28. >
  29. </div>
  30. </div>
  31. </li>
  32. <li class="nav-item">
  33. <ng-template #loginBtn>
  34. <a role="button" class="nav-link" routerLink="/account/login">{{
  35. 'AbpAccount::Login' | abpLocalization
  36. }}</a>
  37. </ng-template>
  38. <div
  39. *ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
  40. ngbDropdown
  41. class="dropdown"
  42. #currentUserDropdown="ngbDropdown"
  43. display="static"
  44. >
  45. <a
  46. ngbDropdownToggle
  47. class="nav-link"
  48. href="javascript:void(0)"
  49. role="button"
  50. id="dropdownMenuLink"
  51. data-toggle="dropdown"
  52. aria-haspopup="true"
  53. aria-expanded="false"
  54. >
  55. {{ (currentUser$ | async)?.userName }}
  56. </a>
  57. <div
  58. class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
  59. aria-labelledby="dropdownMenuLink"
  60. [class.d-block]="smallScreen && currentUserDropdown.isOpen()"
  61. >
  62. <a class="dropdown-item" routerLink="/account/manage-profile"
  63. ><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a
  64. >
  65. <a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
  66. ><i class="fa fa-power-off mr-1"></i>{{ 'AbpUi::Logout' | abpLocalization }}</a
  67. >
  68. </div>
  69. </div>
  70. </li>
  71. </ul>

Open app.component.ts in src/app folder and modify it as shown below:

  1. import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
  2. import { Store } from '@ngxs/store'; // imported Store
  3. import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
  4. import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
  5. //...
  6. @Component(/* component metadata */)
  7. export class AppComponent implements OnInit {
  8. constructor(..., private store: Store) {} // injected Store
  9. ngOnInit() {
  10. //...
  11. // added dispatch
  12. this.store.dispatch(
  13. new AddReplaceableComponent({
  14. component: NavItemsComponent,
  15. key: eThemeBasicComponents.NavItems,
  16. }),
  17. );
  18. }
  19. }

The final UI looks like below:

New nav-items

See Also

What’s Next?