替换组件

你可以将一些ABP的组件替换为你自己的自定义组件.

你可以替换不能自定义默认ABP组件的原因是禁用或更改该组件的一部分可能会导致问题. 所以我们把这些组件称为可替换组件.

如何替换组件

创建一个你想要使用的新组件,添加到 AppModule 中的 declarationsentryComponents 中.

然后打开 app.component.ts 使用 AddReplaceableComponent 将你的组件替换ABP组件. 如下所示:

  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

如何替换布局

每个ABP主题模块有3个布局,分别是ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent. 这些布局可以用相同的方式替换.

一个布局组件模板应该包含 <router-outlet></router-outlet> 元素.

下面的例子解释了如何更换 ApplicationLayoutComponent:

运行以下命令在 angular 文件夹中生成布局:

  1. yarn ng generate component my-application-layout

在你的布局模板(my-layout.component.html)中添加以下代码:

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

打开 src/app 文件夹下的 app.component.ts 文件添加以下内容:

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

布局组件

Layout Components

如何替换LogoComponent

LogoComponent

angular 目录下运行以下命令创建新的组件 LogoComponent:

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

打开 src/app/logo 目录下生成的 logo.component.ts 并使用以下内容替换它:

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

打开 src/app 目录下的 app.component.ts 做以下修改:

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

最终UI如下:

New logo

如何替换RoutesComponent

RoutesComponent

angular 目录下运行以下命令创建新的组件 RoutesComponent:

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

打开 src/app/routes 目录下生成的 routes.component.ts 并使用以下内容替换它:

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

打开 src/app/routes 目录下生成的 routes.component.html 并使用以下内容替换它:

  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>

打开 src/app 目录下的 app.component.ts 做以下修改:

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

最终UI如下:

New routes

如何替换NavItemsComponent

NavItemsComponent

angular 目录下运行以下命令创建新的组件 NavItemsComponent:

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

打开 src/app/nav-items 目录下生成的 nav-items.component.ts 并使用以下内容替换它:

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

打开 src/app/nav-items 目录下生成的 nav-items.component.html 并使用以下内容替换它:

  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>

打开 src/app 目录下的 app.component.ts 做以下修改:

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

最终UI如下:

New nav-items

另请参阅

下一步是什么?