```json //[doc-seo] { "Description": "Learn how to replace ABP components with your custom ones for enhanced flexibility while maintaining system integrity." } ``` # 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. Then, open the `app.component.ts` and execute the `add` method of `ReplaceableComponentsService` to replace your component with an ABP component as shown below: ```js import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum import { Component, inject } from '@angular/core'; //... @Component(/* component metadata */) export class AppComponent { private replaceableComponents = inject(ReplaceableComponentsService); constructor() { this.replaceableComponents.add({ component: YourNewRoleComponent, key: eIdentityComponents.Roles, }); } } ``` ![Example Usage](./images/component-replacement.gif) ## How to Replace a Layout Each ABP theme package has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way. > A layout component template should contain `` element. The example below describes how to replace the `ApplicationLayoutComponent`: Run the following command to generate a layout in `angular` folder: ```bash yarn ng generate component my-application-layout ``` Add the following code in your layout template (`my-application-layout.component.html`) where you want the page to be loaded. ```html ``` Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent import { Component, inject } from '@angular/core'; @Component(/* component metadata */) export class AppComponent { private replaceableComponents = inject(ReplaceableComponentsService); constructor() { this.replaceableComponents.add({ component: MyApplicationLayoutComponent, key: eThemeBasicComponents.ApplicationLayout, }); } } ``` > If you would like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the `add` method of `ReplaceableComponentsService` as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of the `add` method 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](./images/layout-components.png) ### How to Add a New Layout Component To add a new layout component, you need to follow these steps: #### Step 1: Create a New Angular Component This component should have a 'router-outlet' for dynamic content loading. You can create a new component using the Angular CLI. Run the following command in your terminal: ```bash ng generate component new-layout ``` This command will create a new component named `new-layout`. Now, open the new-layout.component.html file and add a `router-outlet` to it: ```html ``` This 'router-outlet' will act as a placeholder that Angular dynamically fills based on the current router state. #### Step 2: Define a Variable for the Layout Component Although this step is optional, it can be useful if you're going to use the layout component's value multiple times. You can define a variable for the layout component like this: ```javascript export const eCustomLayout = { key: "CustomLayout", component: "CustomLayoutComponent", }; ``` In this variable, `key` is a unique identifier for the layout component, and `component` is the name of the layout component. You can use this variable when you need to refer to the layout component. #### Step 3: Add the Layout Component to the ABP Replaceable-System Next, you need to add the new layout component to the `ReplaceableComponentsService`. This service allows you to replace a component with another one dynamically. You can do this by defining a provider for `provideAppInitializer` that uses a factory function. In this function, you inject the `ReplaceableComponentsService` and use its `add` method to add the new layout component. Here's how you can do it: ```javascript export const CUSTOM_LAYOUT_PROVIDERS = [ provideAppInitializer(()=>{ configureLayoutFn(); }), ]; function configureLayoutFn() { const service = inject(ReplaceableComponentsService); service.add({ key: eCustomLayout.component, component: CustomLayoutComponent, }); } ``` In this code, `configureLayoutFn` is a factory function that adds the new layout component to the `ReplaceableComponentsService`. The `provideAppInitializer` provider runs this function when the application starts. note: (don't forget: you should add the CUSTOM_LAYOUT_PROVIDERS in the app.config.ts file) #### Step 4: Define the Application's Dynamic Layouts Finally, you need to define the application's dynamic layouts. This is a map where the keys are the layout keys and the values are the layout components. You can add the new layout to the existing layouts like this: ```javascript export const myDynamicLayouts = new Map([...DEFAULT_DYNAMIC_LAYOUTS, [eCustomLayout.key, eCustomLayout.component]]); ``` #### Step 5: Pass the Dynamic Layouts to the Core Provider The final step is to pass the dynamic layouts to the `provideAbpCore` using the `withOptions` method. This method allows you to configure the provider with a static method. Here's how you can do it: ```ts export const appConfig: ApplicationConfig = { providers: [ // ... provideAbpCore( withOptions({ dynamicLayouts: myDynamicLayouts, environment, registerLocaleFn: registerLocale(), }), ), ], }; ``` In this code, `myDynamicLayouts` is the map of dynamic layouts you defined earlier. We pass this map to the `provideAbpCore` using the `withOptions` method. Now that you have defined the new layout, you can use it in the router definition. You do this by adding a new route that uses the new layout. Here's how you can do it: ```javascript // route.provider.ts import { eCustomLayout } from './custom-layout/custom-layout.provider'; import { RoutesService, eLayoutType } from '@abp/ng.core'; import { provideAppInitializer } from '@angular/core'; export const APP_ROUTE_PROVIDER = [ provideAppInitializer(() => { configureRoutes(); }), ]; function configureRoutes() { const routes = inject(RoutesService); routes.add([ { path: '/', name: '::Menu:Home', iconClass: 'fas fa-home', order: 1, layout: eLayoutType.application, }, { path: '/dashboard', name: '::Menu:Dashboard', iconClass: 'fas fa-chart-line', order: 2, layout: eCustomLayout.key as eLayoutType, requiredPolicy: 'MyProjectName.Dashboard.Host || MyProjectName.Dashboard.Tenant', }, ]); } ``` #### How to Replace LogoComponent ![LogoComponent](./images/logo-component.png) Note - If your goal is only to change the logo image or application name, you don't need to replace the component. Prefer providing the logo via `@abp/ng.theme.shared` so all themes/components consume it consistently: ```ts // app.config.ts import { provideLogo, withEnvironmentOptions } from '@abp/ng.theme.shared'; import { environment } from './environments/environment'; export const appConfig: ApplicationConfig = { providers: [ provideLogo(withEnvironmentOptions(environment)), ], }; ``` If you still want to completely replace the logo component UI, follow the steps below: Run the following command in `angular` folder to create a new component called `LogoComponent`. ```bash yarn ng generate component logo --inlineTemplate --inlineStyle ``` Open the generated `logo.component.ts` in `src/app/logo` folder and replace its content with the following: ```js import { Component } from "@angular/core"; @Component({ selector: "app-logo", template: ` logo `, }) export class LogoComponent {} ``` Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { LogoComponent } from './logo/logo.component'; // imported LogoComponent import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents //... @Component(/* component metadata */) export class AppComponent implements OnInit { private replaceableComponents = inject(ReplaceableComponentsService); ngOnInit() { //... this.replaceableComponents.add({ component: LogoComponent, key: eThemeBasicComponents.Logo, }); } } ``` The final UI looks like below: ![New logo](./images/replaced-logo-component.png) #### How to Replace RoutesComponent ![RoutesComponent](./images/routes-component.png) Run the following command in `angular` folder to create a new component called `RoutesComponent`. ```bash yarn ng generate component routes ``` Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following: ```js import { Component, HostBinding } from "@angular/core"; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { LocalizationPipe, PermissionDirective } from "@abp/ng.core"; import { EllipsisDirective } from '@abp/ng.theme.shared'; @Component({ selector: "app-routes", templateUrl: "routes.component.html", imports: [ CommonModule, RouterModule, NgbDropdownModule, PermissionDirective, EllipsisDirective, LocalizationPipe, ] }) export class RoutesComponent { @HostBinding("class.mx-auto") marginAuto = true; get smallScreen() { return window.innerWidth < 992; } } ``` Open the generated `routes.component.html` in `src/app/routes` folder and replace its content with the following: ```html ``` Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { RoutesComponent } from './routes/routes.component'; // imported RoutesComponent import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents //... @Component(/* component metadata */) export class AppComponent implements OnInit { private replaceableComponents = inject(ReplaceableComponentsService); ngOnInit() { //... this.replaceableComponents.add({ component: RoutesComponent, key: eThemeBasicComponents.Routes, }); } } ``` The final UI looks like below: ![New routes](./images/replaced-routes-component.png) #### How to Replace NavItemsComponent ![NavItemsComponent](./images/nav-items-component.png) Run the following command in `angular` folder to create a new component called `NavItemsComponent`. ```bash yarn ng generate component nav-items ``` Open the generated `nav-items.component.ts` in `src/app/nav-items` folder and replace the content with the following: ```js import { AuthService, ConfigStateService, CurrentUserDto, LanguageInfo, NAVIGATE_TO_MANAGE_PROFILE, SessionStateService, LocalizationPipe } from '@abp/ng.core'; import { Component, inject, Inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import snq from 'snq'; @Component({ selector: 'app-nav-items', templateUrl: 'nav-items.component.html', imports: [ CommonModule, FormsModule, NgbDropdownModule, LocalizationPipe ] }) export class NavItemsComponent { private configState = inject(ConfigStateService); private authService = inject(AuthService); private sessionState = inject(SessionStateService); @Inject(NAVIGATE_TO_MANAGE_PROFILE) public navigateToManageProfile: any; currentUser$: Observable = this.configState.getOne$('currentUser'); selectedTenant$ = this.sessionState.getTenant$(); languages$: Observable = this.configState.getDeep$('localization.languages'); get smallScreen(): boolean { return window.innerWidth < 992; } get defaultLanguage$(): Observable { return this.languages$.pipe( map( languages => snq( () => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName ), '' ) ); } get dropdownLanguages$(): Observable { return this.languages$.pipe( map( languages => snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)), [] ) ); } get selectedLangCulture(): string { return this.sessionState.getLanguage(); } onChangeLang(cultureName: string) { this.sessionState.setLanguage(cultureName); } navigateToLogin() { this.authService.navigateToLogin(); } logout() { this.authService.logout().subscribe(); } } ``` Open the generated `nav-items.component.html` in `src/app/nav-items` folder and replace the content with the following: ```html ``` Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService 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 { private replaceableComponents = inject(ReplaceableComponentsService); ngOnInit() { //... this.replaceableComponents.add({ component: NavItemsComponent, key: eThemeBasicComponents.NavItems, }); } } ``` The final UI looks like below: ![New nav-items](./images/replaced-nav-items-component.png) ## See Also - [How Replaceable Components Work with Extensions](./how-replaceable-components-work-with-extensions.md) - [How to Replace PermissionManagementComponent](./permission-management-component-replacement.md)