Component Replacement

    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.

    Create a new component that you want to use instead of an ABP component. Add that component to 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:

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

    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. }

    How to Replace 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:

    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

    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. }
    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. <div
    25. #routeContainer
    26. class="dropdown-menu border-0 shadow-sm"
    27. (click)="$event.preventDefault(); $event.stopPropagation()"
    28. [class.d-block]="smallScreen && navbarRootDropdown.expand"
    29. >
    30. <div
    31. class="dropdown-submenu"
    32. #dropdownSubmenu="ngbDropdown"
    33. placement="right-top"
    34. [autoClose]="true"
    35. *abpPermission="'AbpIdentity.Roles || AbpIdentity.Users'"
    36. >
    37. <div ngbDropdownToggle [class.dropdown-toggle]="false">
    38. <a
    39. abpEllipsis="210px"
    40. [abpEllipsisEnabled]="!smallScreen"
    41. role="button"
    42. class="btn d-block text-left dropdown-toggle"
    43. >
    44. <i class="fa fa-id-card-o"></i>
    45. {{ 'AbpIdentity::Menu:IdentityManagement' | abpLocalization }}
    46. </a>
    47. </div>
    48. <div
    49. #childrenContainer
    50. class="dropdown-menu border-0 shadow-sm"
    51. [class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
    52. >
    53. <div class="dropdown-submenu" *abpPermission="'AbpIdentity.Roles'">
    54. <a class="dropdown-item" routerLink="/identity/roles">
    55. {{ 'AbpIdentity::Roles' | abpLocalization }}</a
    56. >
    57. </div>
    58. <div class="dropdown-submenu" *abpPermission="'AbpIdentity.Users'">
    59. <a class="dropdown-item" routerLink="/identity/users">
    60. {{ 'AbpIdentity::Users' | abpLocalization }}</a
    61. >
    62. </div>
    63. </div>
    64. </div>
    65. <div
    66. class="dropdown-submenu"
    67. ngbDropdown
    68. #dropdownSubmenu="ngbDropdown"
    69. placement="right-top"
    70. [autoClose]="true"
    71. *abpPermission="'AbpTenantManagement.Tenants'"
    72. >
    73. <div ngbDropdownToggle [class.dropdown-toggle]="false">
    74. <a
    75. abpEllipsis="210px"
    76. [abpEllipsisEnabled]="!smallScreen"
    77. role="button"
    78. class="btn d-block text-left dropdown-toggle"
    79. >
    80. <i class="fa fa-users"></i>
    81. {{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}
    82. </a>
    83. </div>
    84. <div
    85. #childrenContainer
    86. class="dropdown-menu border-0 shadow-sm"
    87. [class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
    88. >
    89. <div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
    90. <a class="dropdown-item" routerLink="/tenant-management/tenants">
    91. {{ 'AbpTenantManagement::Tenants' | abpLocalization }}</a
    92. >
    93. </div>
    94. </div>
    95. </div>
    96. </div>
    97. </li>
    98. </ul>

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

    The final UI looks like below:

    How to Replace 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. [],
    40. ),
    41. );
    42. }
    43. get selectedLangCulture(): string {
    44. }
    45. constructor(private store: Store, private authService: AuthService) {}
    46. ngAfterViewInit() {
    47. fromEvent(window, 'resize')
    48. .pipe(debounceTime(150))
    49. .subscribe(() => {
    50. this.smallScreen = window.innerWidth < 992;
    51. });
    52. }
    53. onChangeLang(cultureName: string) {
    54. this.store.dispatch(new SetLanguage(cultureName));
    55. }
    56. logout() {
    57. this.authService.logout().subscribe(() => {
    58. this.store.dispatch(
    59. new Navigate(['/'], null, {
    60. state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
    61. }),
    62. );
    63. });
    64. }
    65. }

    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. }

    The final UI looks like below:

    New nav-items