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:
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
//...
}
}
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:
yarn ng generate component shared/my-application-layout --export --entryComponent
# You don't need the --entryComponent option in Angular 9
Add the following code in your layout template (my-layout.component.html
) where you want the page to be loaded.
<router-outlet></router-outlet>
Open app.component.ts
in src/app
folder and modify it as shown below:
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
}),
);
//...
}
}
Layout Components
How to Replace LogoComponent
Run the following command in angular
folder to create a new component called LogoComponent
.
yarn ng generate component logo --inlineTemplate --inlineStyle --entryComponent
# 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:
import { Component } from '@angular/core';
@Component({
selector: 'app-logo',
template: `
<a class="navbar-brand" routerLink="/">
<!-- Change the img src -->
<img
src="https://via.placeholder.com/100x50/343a40/FF0000?text=MyLogo"
alt="logo"
width="100%"
height="auto"
/>
</a>
`,
})
export class LogoComponent {}
Open app.component.ts
in src/app
folder and modify it as shown below:
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: LogoComponent,
key: eThemeBasicComponents.Logo,
}),
);
}
}
The final UI looks like below:
How to Replace RoutesComponent
Run the following command in angular
folder to create a new component called RoutesComponent
.
yarn ng generate component routes --entryComponent
# 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:
import { ABP, ReplaceableComponents } from '@abp/ng.core';
import {
Component,
HostBinding,
Inject,
Renderer2,
TrackByFunction,
AfterViewInit,
} from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Component({
selector: 'app-routes',
templateUrl: 'routes.component.html',
})
export class RoutesComponent implements AfterViewInit {
@HostBinding('class.mx-auto')
marginAuto = true;
smallScreen = window.innerWidth < 992;
constructor(private renderer: Renderer2) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
}
Open the generated routes.component.html
in src/app/routes
folder and replace its content with the following:
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" routerLink="/"
><i class="fas fa-home"></i> {{ '::Menu:Home' | abpLocalization }}</a
>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/my-page"><i class="fas fa-newspaper mr-1"></i>My Page</a>
</li>
<li
#navbarRootDropdown
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
(click)="
navbarRootDropdown.expand
? (navbarRootDropdown.expand = false)
: (navbarRootDropdown.expand = true)
"
>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
<i class="fas fa-wrench"></i>
{{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}
</a>
<div
#routeContainer
class="dropdown-menu border-0 shadow-sm"
(click)="$event.preventDefault(); $event.stopPropagation()"
[class.d-block]="smallScreen && navbarRootDropdown.expand"
>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpIdentity.Roles || AbpIdentity.Users'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-id-card-o"></i>
{{ 'AbpIdentity::Menu:IdentityManagement' | abpLocalization }}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Roles'">
<a class="dropdown-item" routerLink="/identity/roles">
{{ 'AbpIdentity::Roles' | abpLocalization }}</a
>
</div>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Users'">
<a class="dropdown-item" routerLink="/identity/users">
{{ 'AbpIdentity::Users' | abpLocalization }}</a
>
</div>
</div>
</div>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpTenantManagement.Tenants'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-users"></i>
{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
<a class="dropdown-item" routerLink="/tenant-management/tenants">
{{ 'AbpTenantManagement::Tenants' | abpLocalization }}</a
>
</div>
</div>
</div>
</div>
</li>
</ul>
Open app.component.ts
in src/app
folder and modify it as shown below:
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: RoutesComponent,
key: eThemeBasicComponents.Routes,
}),
);
}
}
The final UI looks like below:
How to Replace NavItemsComponent
Run the following command in angular
folder to create a new component called NavItemsComponent
.
yarn ng generate component nav-items --entryComponent
# 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:
import {
ApplicationConfiguration,
AuthService,
ConfigState,
SessionState,
SetLanguage,
} from '@abp/ng.core';
import { Component, AfterViewInit } from '@angular/core';
import { Navigate, RouterState } from '@ngxs/router-plugin';
import { Select, Store } from '@ngxs/store';
import { Observable, fromEvent } from 'rxjs';
import { map, debounceTime } from 'rxjs/operators';
import snq from 'snq';
@Component({
selector: 'app-nav-items',
templateUrl: 'nav-items.component.html',
})
export class NavItemsComponent implements AfterViewInit {
@Select(ConfigState.getOne('currentUser'))
currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
smallScreen = window.innerWidth < 992;
get defaultLanguage$(): Observable<string> {
return this.languages$.pipe(
map(
languages =>
snq(
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
),
'',
),
);
}
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
return this.languages$.pipe(
map(
languages =>
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
[],
),
);
}
get selectedLangCulture(): string {
return this.store.selectSnapshot(SessionState.getLanguage);
}
constructor(private store: Store, private authService: AuthService) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
onChangeLang(cultureName: string) {
this.store.dispatch(new SetLanguage(cultureName));
}
logout() {
this.authService.logout().subscribe(() => {
this.store.dispatch(
new Navigate(['/'], null, {
state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
}),
);
});
}
}
Open the generated nav-items.component.html
in src/app/nav-items
folder and replace the content with the following:
<ul class="navbar-nav">
<input type="search" placeholder="Search" class="bg-transparent border-0 text-white" />
<li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
<div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{{ defaultLanguage$ | async }}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && languageDropdown.isOpen()"
>
<a
*ngFor="let lang of dropdownLanguages$ | async"
href="javascript:void(0)"
class="dropdown-item"
(click)="onChangeLang(lang.cultureName)"
>{{ lang?.displayName }}</a
>
</div>
</div>
</li>
<li class="nav-item">
<ng-template #loginBtn>
<a role="button" class="nav-link" routerLink="/account/login">{{
'AbpAccount::Login' | abpLocalization
}}</a>
</ng-template>
<div
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
ngbDropdown
class="dropdown"
#currentUserDropdown="ngbDropdown"
display="static"
>
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{{ (currentUser$ | async)?.userName }}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item" routerLink="/account/manage-profile"
><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
><i class="fa fa-power-off mr-1"></i>{{ 'AbpUi::Logout' | abpLocalization }}</a
>
</div>
</div>
</li>
</ul>
Open app.component.ts
in src/app
folder and modify it as shown below:
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: NavItemsComponent,
key: eThemeBasicComponents.NavItems,
}),
);
}
}
The final UI looks like below: