From 01ed7f9c78a44ef8e0da73150bcb11bfbc096840 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Mon, 13 Jul 2020 12:06:36 +0300 Subject: [PATCH 1/8] test: use relative import for ConfigState --- npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts index cb865787fa..96bbc197d0 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts @@ -1,10 +1,10 @@ -import { ConfigState } from '@abp/ng.core'; import { createHttpFactory, HttpMethod, SpectatorHttp, SpyObject } from '@ngneat/spectator/jest'; import { NgxsModule, Store } from '@ngxs/store'; import { of, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { Rest } from '../models'; import { RestService } from '../services/rest.service'; +import { ConfigState } from '../states/config.state'; import { CORE_OPTIONS } from '../tokens'; describe('HttpClient testing', () => { From 4b7c944648710837dd6b7b7cbe1a1b8f8121a457 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Mon, 13 Jul 2020 12:06:57 +0300 Subject: [PATCH 2/8] test: update PermissionGuard specs --- .../core/src/lib/guards/permission.guard.ts | 17 ++--- .../src/lib/tests/permission.guard.spec.ts | 74 ++++++++++++++++--- 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts b/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts index 6ba6bd18b5..5de83fef7a 100644 --- a/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts +++ b/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts @@ -1,12 +1,11 @@ import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, Router } from '@angular/router'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { Store } from '@ngxs/store'; import { Observable, of } from 'rxjs'; import { tap } from 'rxjs/operators'; -import snq from 'snq'; import { RestOccurError } from '../actions/rest.actions'; -import { ConfigState } from '../states/config.state'; import { RoutesService } from '../services/routes.service'; +import { ConfigState } from '../states/config.state'; import { findRoute, getRoutePath } from '../utils/route-utils'; @Injectable({ @@ -15,18 +14,16 @@ import { findRoute, getRoutePath } from '../utils/route-utils'; export class PermissionGuard implements CanActivate { constructor(private router: Router, private routes: RoutesService, private store: Store) {} - canActivate( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot, - ): Observable | boolean { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { let { requiredPolicy } = route.data || {}; if (!requiredPolicy) { - requiredPolicy = findRoute(this.routes, getRoutePath(this.router, state.url))?.requiredPolicy; - - if (!requiredPolicy) return true; + const route = findRoute(this.routes, getRoutePath(this.router, state.url)); + requiredPolicy = route?.requiredPolicy; } + if (!requiredPolicy) return of(true); + return this.store.select(ConfigState.getGrantedPolicy(requiredPolicy)).pipe( tap(access => { if (!access) { diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index a995a4c822..dcfd27ab69 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -1,22 +1,57 @@ +import { APP_BASE_HREF } from '@angular/common'; +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; -import { Store } from '@ngxs/store'; +import { Actions, Store } from '@ngxs/store'; import { of } from 'rxjs'; -import { PermissionGuard } from '../guards/permission.guard'; import { RestOccurError } from '../actions'; +import { PermissionGuard } from '../guards/permission.guard'; +import { RoutesService } from '../services/routes.service'; describe('PermissionGuard', () => { let spectator: SpectatorService; let guard: PermissionGuard; + let routes: SpyObject; let store: SpyObject; + @Component({ template: '' }) + class DummyComponent {} + const createService = createServiceFactory({ service: PermissionGuard, mocks: [Store], + declarations: [DummyComponent], + imports: [ + RouterModule.forRoot([ + { + path: 'test', + component: DummyComponent, + data: { + requiredPolicy: 'TestPolicy', + }, + }, + ]), + ], + providers: [ + { + provide: APP_BASE_HREF, + useValue: '/', + }, + { + provide: Actions, + useValue: { + pipe() { + return of(null); + }, + }, + }, + ], }); beforeEach(() => { spectator = createService(); guard = spectator.service; + routes = spectator.inject(RoutesService); store = spectator.get(Store); }); @@ -41,17 +76,32 @@ describe('PermissionGuard', () => { }); }); - it('should find the requiredPolicy from child route', done => { + it('should check the requiredPolicy from RoutesService', done => { + routes.add([ + { + path: '/test', + name: 'Test', + requiredPolicy: 'TestPolicy', + }, + ]); store.select.andReturn(of(false)); const spy = jest.spyOn(store, 'select'); - guard - .canActivate( - { data: {}, routeConfig: { children: [{ path: 'test', data: { requiredPolicy: 'TestPolicy' } }] } } as any, - { url: 'test' } as any, - ) - .subscribe(() => { - expect(spy.mock.calls[0][0]({ auth: { grantedPolicies: { TestPolicy: true } } })).toBe(true); - done(); - }); + guard.canActivate({ data: {} } as any, { url: 'test' } as any).subscribe(() => { + expect(spy.mock.calls[0][0]({ auth: { grantedPolicies: { TestPolicy: true } } })).toBe(true); + done(); + }); + }); + + it('should return Observable if RoutesService does not have requiredPolicy for given URL', done => { + routes.add([ + { + path: '/test', + name: 'Test', + }, + ]); + guard.canActivate({ data: {} } as any, { url: 'test' } as any).subscribe(result => { + expect(result).toBe(true); + done(); + }); }); }); From ff18197733dcdf87786cc623d2d8de4c5b78384e Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Mon, 13 Jul 2020 12:43:52 +0300 Subject: [PATCH 3/8] fix: get layout component on every route change --- .../lib/components/dynamic-layout.component.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 2548cfece6..7b1395223c 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -24,6 +24,13 @@ import { TreeNode } from '../utils/tree-utils'; export class DynamicLayoutComponent implements OnDestroy { layout: Type; + // TODO: Consider a shared enum (eThemeSharedComponents) for known layouts + readonly layouts = new Map([ + ['application', 'Theme.ApplicationLayoutComponent'], + ['account', 'Theme.AccountLayoutComponent'], + ['empty', 'Theme.EmptyLayoutComponent'], + ]); + isLayoutVisible = true; constructor( @@ -36,11 +43,6 @@ export class DynamicLayoutComponent implements OnDestroy { const route = injector.get(ActivatedRoute); const router = injector.get(Router); const routes = injector.get(RoutesService); - const layouts = { - application: this.getComponent('Theme.ApplicationLayoutComponent'), - account: this.getComponent('Theme.AccountLayoutComponent'), - empty: this.getComponent('Theme.EmptyLayoutComponent'), - }; router.events.pipe(takeUntilDestroy(this)).subscribe(event => { if (event instanceof NavigationEnd) { @@ -62,7 +64,8 @@ export class DynamicLayoutComponent implements OnDestroy { if (!expectedLayout) expectedLayout = eLayoutType.empty; - this.layout = layouts[expectedLayout].component; + const key = this.layouts.get(expectedLayout); + this.layout = this.getComponent(key).component; } }); From 44f535b6d57277d4b2ea881d4fc6d1c6b6f243e9 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Mon, 13 Jul 2020 13:45:22 +0300 Subject: [PATCH 4/8] docs: update component replacement document --- docs/en/UI/Angular/Component-Replacement.md | 32 ++++++++--------- .../UI/Angular/Component-Replacement.md | 36 ++++++++----------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index d2c75cd67c..4a461ae431 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -11,25 +11,24 @@ Create a new component that you want to use instead of an ABP component. Add tha Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action +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 +export class AppComponent { + constructor( + private store: Store // injected Store + ) + { + // dispatched the AddReplaceableComponent action this.store.dispatch( new AddReplaceableComponent({ component: YourNewRoleComponent, key: eIdentityComponents.Roles, }), ); - - //... } } ``` @@ -60,26 +59,23 @@ Add the following code in your layout template (`my-layout.component.html`) wher Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent +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 -//... +import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent @Component(/* component metadata */) -export class AppComponent implements OnInit { - constructor(..., private store: Store) {} // injected Store - - ngOnInit() { - // added dispatch +export class AppComponent { + constructor( + private store: Store, // injected Store + ) { + // dispatched the AddReplaceableComponent action this.store.dispatch( new AddReplaceableComponent({ component: MyApplicationLayoutComponent, key: eThemeBasicComponents.ApplicationLayout, }), ); - - //... } } ``` diff --git a/docs/zh-Hans/UI/Angular/Component-Replacement.md b/docs/zh-Hans/UI/Angular/Component-Replacement.md index 145359c348..221ac6775a 100644 --- a/docs/zh-Hans/UI/Angular/Component-Replacement.md +++ b/docs/zh-Hans/UI/Angular/Component-Replacement.md @@ -11,25 +11,24 @@ 然后打开 `app.component.ts` 使用 `AddReplaceableComponent` 将你的组件替换ABP组件. 如下所示: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action +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 +export class AppComponent { + constructor( + private store: Store // injected Store + ) + { + // dispatched the AddReplaceableComponent action this.store.dispatch( new AddReplaceableComponent({ component: YourNewRoleComponent, key: eIdentityComponents.Roles, }), ); - - //... } } ``` @@ -47,9 +46,7 @@ export class AppComponent implements OnInit { 运行以下命令在 `angular` 文件夹中生成布局: ```bash -yarn ng generate component shared/my-application-layout --export --entryComponent - -# You don't need the --entryComponent option in Angular 9 +yarn ng generate component my-application-layout ``` 在你的布局模板(`my-layout.component.html`)中添加以下代码: @@ -61,26 +58,23 @@ yarn ng generate component shared/my-application-layout --export --entryComponen 打开 `src/app` 文件夹下的 `app.component.ts` 文件添加以下内容: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent +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 -//... +import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent @Component(/* component metadata */) -export class AppComponent implements OnInit { - constructor(..., private store: Store) {} // injected Store - - ngOnInit() { - // added dispatch +export class AppComponent { + constructor( + private store: Store, // injected Store + ) { + // dispatched the AddReplaceableComponent action this.store.dispatch( new AddReplaceableComponent({ component: MyApplicationLayoutComponent, key: eThemeBasicComponents.ApplicationLayout, }), ); - - //... } } ``` From bb6e1a11fb472944acf295f483b1df722d74e618 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Mon, 13 Jul 2020 13:57:44 +0300 Subject: [PATCH 5/8] feat: add a reload parameter to AddReplaceableComponent --- .../actions/replaceable-components.actions.ts | 5 ++++- .../states/replaceable-components.state.ts | 21 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts index 3b948cf63d..8df6a384ad 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts @@ -5,5 +5,8 @@ import { ReplaceableComponents } from '../models/replaceable-components'; */ export class AddReplaceableComponent { static readonly type = '[ReplaceableComponents] Add'; - constructor(public payload: ReplaceableComponents.ReplaceableComponent) {} + constructor( + public payload: ReplaceableComponents.ReplaceableComponent, + public reload?: boolean, + ) {} } diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index 7c07621ad4..0e48104f87 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -1,8 +1,9 @@ import { Injectable } from '@angular/core'; -import { State, Action, StateContext, Selector, createSelector } from '@ngxs/store'; +import { Router } from '@angular/router'; +import { Action, createSelector, Selector, State, StateContext } from '@ngxs/store'; +import snq from 'snq'; import { AddReplaceableComponent } from '../actions/replaceable-components.actions'; import { ReplaceableComponents } from '../models/replaceable-components'; -import snq from 'snq'; @State({ name: 'ReplaceableComponentsState', @@ -28,10 +29,12 @@ export class ReplaceableComponentsState { return selector; } + constructor(private router: Router) {} + @Action(AddReplaceableComponent) - replaceableComponentsAction( + async replaceableComponentsAction( { getState, patchState }: StateContext, - { payload }: AddReplaceableComponent, + { payload, reload }: AddReplaceableComponent, ) { let { replaceableComponents } = getState(); @@ -48,5 +51,15 @@ export class ReplaceableComponentsState { patchState({ replaceableComponents, }); + + if (reload) { + // TODO: Create a shared service for route reload and more + const { shouldReuseRoute } = this.router.routeReuseStrategy; + this.router.routeReuseStrategy.shouldReuseRoute = () => false; + this.router.navigated = false; + + await this.router.navigateByUrl(this.router.url).catch(); + this.router.routeReuseStrategy.shouldReuseRoute = shouldReuseRoute; + } } } From 14b8b1d4d3e26adaff70bcf11aab358b0e27a49b Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Mon, 13 Jul 2020 14:32:46 +0300 Subject: [PATCH 6/8] test: add tests for replacing when reload is true --- .../states/replaceable-components.state.ts | 32 ++++++++++++------- .../replaceable-components.state.spec.ts | 24 ++++++++++++-- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index 0e48104f87..1f583f233f 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Router } from '@angular/router'; import { Action, createSelector, Selector, State, StateContext } from '@ngxs/store'; import snq from 'snq'; @@ -29,10 +29,26 @@ export class ReplaceableComponentsState { return selector; } - constructor(private router: Router) {} + constructor(private ngZone: NgZone, private router: Router) {} + + // TODO: Create a shared service for route reload and more + private reloadRoute() { + const { shouldReuseRoute } = this.router.routeReuseStrategy; + const setRouteReuse = (reuse: typeof shouldReuseRoute) => { + this.router.routeReuseStrategy.shouldReuseRoute = reuse; + }; + + setRouteReuse(() => false); + this.router.navigated = false; + + this.ngZone.run(async () => { + await this.router.navigateByUrl(this.router.url).catch(); + setRouteReuse(shouldReuseRoute); + }); + } @Action(AddReplaceableComponent) - async replaceableComponentsAction( + replaceableComponentsAction( { getState, patchState }: StateContext, { payload, reload }: AddReplaceableComponent, ) { @@ -52,14 +68,6 @@ export class ReplaceableComponentsState { replaceableComponents, }); - if (reload) { - // TODO: Create a shared service for route reload and more - const { shouldReuseRoute } = this.router.routeReuseStrategy; - this.router.routeReuseStrategy.shouldReuseRoute = () => false; - this.router.navigated = false; - - await this.router.navigateByUrl(this.router.url).catch(); - this.router.routeReuseStrategy.shouldReuseRoute = shouldReuseRoute; - } + if (reload) this.reloadRoute(); } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts index c294b131e1..dc113c433c 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts @@ -1,21 +1,27 @@ +import { APP_BASE_HREF } from '@angular/common'; +import { Component } from '@angular/core'; +import { Router, RouterModule } from '@angular/router'; +import { SpyObject } from '@ngneat/spectator'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; import { NgxsModule, Store } from '@ngxs/store'; -import { ReplaceableComponentsState } from '../states/replaceable-components.state'; -import { Component } from '@angular/core'; import { AddReplaceableComponent } from '../actions'; +import { ReplaceableComponentsState } from '../states/replaceable-components.state'; @Component({ selector: 'abp-dummy', template: 'dummy works' }) class DummyComponent {} describe('ReplaceableComponentsState', () => { let spectator: SpectatorHost; + let router: SpyObject; const createHost = createHostFactory({ component: DummyComponent, - imports: [NgxsModule.forRoot([ReplaceableComponentsState])], + providers: [{ provide: APP_BASE_HREF, useValue: '/' }], + imports: [RouterModule.forRoot([]), NgxsModule.forRoot([ReplaceableComponentsState])], }); beforeEach(() => { spectator = createHost(''); + router = spectator.inject(Router); }); it('should add a component to the state', () => { @@ -38,4 +44,16 @@ describe('ReplaceableComponentsState', () => { }); expect(store.selectSnapshot(ReplaceableComponentsState.getAll)).toHaveLength(1); }); + + it('should call reloadRoute when reload parameter is given as true to AddReplaceableComponent', async () => { + const spy = jest.spyOn(router, 'navigateByUrl'); + const store = spectator.get(Store); + store.dispatch(new AddReplaceableComponent({ component: DummyComponent, key: 'Dummy' })); + store.dispatch(new AddReplaceableComponent({ component: null, key: 'Dummy' }, true)); + + await spectator.fixture.whenStable(); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(router.url); + }); }); From 2c4357a99a9a7c1b8cf431d91e2e0bb4e1f8618a Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Mon, 13 Jul 2020 14:43:20 +0300 Subject: [PATCH 7/8] fix: add noop to catch routing errors --- .../core/src/lib/states/replaceable-components.state.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index 1f583f233f..fe9cecaa79 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -1,3 +1,4 @@ +import { noop } from '@abp/ng.core'; import { Injectable, NgZone } from '@angular/core'; import { Router } from '@angular/router'; import { Action, createSelector, Selector, State, StateContext } from '@ngxs/store'; @@ -42,7 +43,7 @@ export class ReplaceableComponentsState { this.router.navigated = false; this.ngZone.run(async () => { - await this.router.navigateByUrl(this.router.url).catch(); + await this.router.navigateByUrl(this.router.url).catch(noop); setRouteReuse(shouldReuseRoute); }); } From aca3207d2726dad90633d4259e51f0a01c168cf2 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Mon, 13 Jul 2020 17:45:03 +0300 Subject: [PATCH 8/8] docs: add a description to component-replacement.md --- docs/en/UI/Angular/Component-Replacement.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index 4a461ae431..fd34fcbae7 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -80,6 +80,8 @@ export class AppComponent { } ``` +> 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](./images/layout-components.png)