Merge pull request #5333 from abpframework/feat/5026

Created ReplaceableComponentsService and used it instead of ReplaceableComponentsState
pull/5350/head
Levent Arman Özak 5 years ago committed by GitHub
commit c7043b3ccf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,27 +8,22 @@ The reason that you **can replace** but **cannot customize** default ABP compone
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:
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 { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
@Component(/* component metadata */)
export class AppComponent {
constructor(
private store: Store // injected Store
)
{
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
private replaceableComponents: ReplaceableComponentsService, // injected the service
) {
this.replaceableComponents.add({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
});
}
}
```
@ -59,28 +54,24 @@ 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 { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
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 {
constructor(
private store: Store, // injected Store
private replaceableComponents: ReplaceableComponentsService, // injected the service
) {
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
}),
);
this.replaceableComponents.add({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
});
}
}
```
> 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.
> 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 `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
@ -123,26 +114,22 @@ export class LogoComponent {}
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
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
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: LogoComponent,
key: eThemeBasicComponents.Logo,
}),
);
});
}
}
```
@ -166,7 +153,7 @@ yarn ng generate component routes --entryComponent
Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following:
```js
import { ABP, ReplaceableComponents } from '@abp/ng.core';
import { ABP } from '@abp/ng.core';
import {
Component,
HostBinding,
@ -309,26 +296,22 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService
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
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: RoutesComponent,
key: eThemeBasicComponents.Routes,
}),
);
});
}
}
```
@ -511,26 +494,22 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
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 {
constructor(..., private store: Store) {} // injected Store
constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: NavItemsComponent,
key: eThemeBasicComponents.NavItems,
}),
);
});
}
}
```

@ -473,24 +473,20 @@ Open the generated `permission-management.component.html` in `src/app/permission
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core';
import { ReplaceableComponentsService } from '@abp/ng.core';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngxs/store';
import { PermissionManagementComponent } from './permission-management/permission-management.component';
//...
export class AppComponent implements OnInit {
constructor(private store: Store) {} // injected store
constructor(private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService
ngOnInit() {
// added dispatching the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
this.replaceableComponents.add({
component: PermissionManagementComponent,
key: ePermissionManagementComponents.PermissionManagement,
})
);
});
}
}
```

@ -1,7 +1,8 @@
import { ReplaceableComponents } from '../models/replaceable-components';
// tslint:disable: max-line-length
/**
* @see usage: https://github.com/abpframework/abp/pull/2522#issue-358333183
* @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement)
*/
export class AddReplaceableComponent {
static readonly type = '[ReplaceableComponents] Add';

@ -1,13 +1,12 @@
import { Component, Injector, Optional, SkipSelf, Type } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { eLayoutType } from '../enums/common';
import { ABP } from '../models';
import { ReplaceableComponents } from '../models/replaceable-components';
import { LocalizationService } from '../services/localization.service';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
import { RoutesService } from '../services/routes.service';
import { SubscriptionService } from '../services/subscription.service';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
import { findRoute, getRoutePath } from '../utils/route-utils';
import { TreeNode } from '../utils/tree-utils';
@ -37,7 +36,7 @@ export class DynamicLayoutComponent {
constructor(
injector: Injector,
private localizationService: LocalizationService,
private store: Store,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
@Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent,
) {
@ -67,7 +66,7 @@ export class DynamicLayoutComponent {
if (!expectedLayout) expectedLayout = eLayoutType.empty;
const key = this.layouts.get(expectedLayout);
this.layout = this.getComponent(key).component;
this.layout = this.getComponent(key)?.component;
}
});
@ -82,6 +81,6 @@ export class DynamicLayoutComponent {
}
private getComponent(key: string): ReplaceableComponents.ReplaceableComponent {
return this.store.selectSnapshot(ReplaceableComponentsState.getComponent(key));
return this.replaceableComponents.get(key);
}
}

@ -1,10 +1,9 @@
import { Component, OnInit, Type } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngxs/store';
import { distinctUntilChanged } from 'rxjs/operators';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
import { SubscriptionService } from '../services/subscription.service';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
@Component({
selector: 'abp-replaceable-route-container',
@ -22,7 +21,7 @@ export class ReplaceableRouteContainerComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private store: Store,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
) {}
@ -31,8 +30,8 @@ export class ReplaceableRouteContainerComponent implements OnInit {
this.componentKey = (this.route.snapshot.data
.replaceableComponent as ReplaceableComponents.RouteData).key;
const component$ = this.store
.select(ReplaceableComponentsState.getComponent(this.componentKey))
const component$ = this.replaceableComponents
.get$(this.componentKey)
.pipe(distinctUntilChanged());
this.subscription.addOne(

@ -10,15 +10,14 @@ import {
Type,
ViewContainerRef,
} from '@angular/core';
import { Store } from '@ngxs/store';
import compare from 'just-compare';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import snq from 'snq';
import { ABP } from '../models/common';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
import { SubscriptionService } from '../services/subscription.service';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
@Directive({ selector: '[abpReplaceableTemplate]', providers: [SubscriptionService] })
export class ReplaceableTemplateDirective implements OnInit, OnChanges {
@ -45,7 +44,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
private templateRef: TemplateRef<any>,
private cfRes: ComponentFactoryResolver,
private vcRef: ViewContainerRef,
private store: Store,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
) {
this.context = {
@ -58,8 +57,8 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
}
ngOnInit() {
const component$ = this.store
.select(ReplaceableComponentsState.getComponent(this.data.componentKey))
const component$ = this.replaceableComponents
.get$(this.data.componentKey)
.pipe(
filter(
(res = {} as ReplaceableComponents.ReplaceableComponent) =>

@ -9,6 +9,7 @@ export * from './localization.service';
export * from './multi-tenancy.service';
export * from './profile-state.service';
export * from './profile.service';
export * from './replaceable-components.service';
export * from './rest.service';
export * from './routes.service';
export * from './session-state.service';

@ -0,0 +1,57 @@
import { Injectable, NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { ReplaceableComponents } from '../models/replaceable-components';
import { BehaviorSubject, Observable } from 'rxjs';
import { noop } from '../utils/common-utils';
import { map, filter } from 'rxjs/operators';
import { InternalStore } from '../utils/internal-store-utils';
import { reloadRoute } from '../utils/route-utils';
@Injectable({ providedIn: 'root' })
export class ReplaceableComponentsService {
private store: InternalStore<ReplaceableComponents.ReplaceableComponent[]>;
get replaceableComponents$(): Observable<ReplaceableComponents.ReplaceableComponent[]> {
return this.store.sliceState(state => state);
}
get replaceableComponents(): ReplaceableComponents.ReplaceableComponent[] {
return this.store.state;
}
get onUpdate$(): Observable<ReplaceableComponents.ReplaceableComponent[]> {
return this.store.sliceUpdate(state => state);
}
constructor(private ngZone: NgZone, private router: Router) {
this.store = new InternalStore([]);
}
add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void {
const replaceableComponents = [...this.store.state];
const index = replaceableComponents.findIndex(
component => component.key === replaceableComponent.key,
);
if (index > -1) {
replaceableComponents[index] = replaceableComponent;
} else {
replaceableComponents.push(replaceableComponent);
}
this.store.patch(replaceableComponents);
if (reload) reloadRoute(this.router, this.ngZone);
}
get(replaceableComponentKey: string): ReplaceableComponents.ReplaceableComponent {
return this.replaceableComponents.find(component => component.key === replaceableComponentKey);
}
get$(replaceableComponentKey: string): Observable<ReplaceableComponents.ReplaceableComponent> {
return this.replaceableComponents$.pipe(
map(components => components.find(component => component.key === replaceableComponentKey)),
);
}
}

@ -1,11 +1,23 @@
import { Injectable, NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { Injectable, isDevMode } from '@angular/core';
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 { noop } from '../utils/common-utils';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
function logDeprecationMsg() {
if (isDevMode()) {
console.warn(`
ReplacableComponentsState has been deprecated. Use ReplaceableComponentsService instead.
See the doc https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement
`);
}
}
// tslint:disable: max-line-length
/**
* @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement)
*/
@State<ReplaceableComponents.State>({
name: 'ReplaceableComponentsState',
defaults: { replaceableComponents: [] } as ReplaceableComponents.State,
@ -16,6 +28,7 @@ export class ReplaceableComponentsState {
static getAll({
replaceableComponents,
}: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent[] {
logDeprecationMsg();
return replaceableComponents || [];
}
@ -23,6 +36,7 @@ export class ReplaceableComponentsState {
const selector = createSelector(
[ReplaceableComponentsState],
(state: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent => {
logDeprecationMsg();
return snq(() => state.replaceableComponents.find(component => component.key === key));
},
);
@ -30,29 +44,15 @@ export class ReplaceableComponentsState {
return selector;
}
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(noop);
setRouteReuse(shouldReuseRoute);
});
}
constructor(private service: ReplaceableComponentsService) {}
@Action(AddReplaceableComponent)
replaceableComponentsAction(
{ getState, patchState }: StateContext<ReplaceableComponents.State>,
{ payload, reload }: AddReplaceableComponent,
) {
logDeprecationMsg();
let { replaceableComponents } = getState();
const index = snq(
@ -69,6 +69,6 @@ export class ReplaceableComponentsState {
replaceableComponents,
});
if (reload) this.reloadRoute();
this.service.add(payload, reload);
}
}

@ -7,7 +7,11 @@ import { NEVER } from 'rxjs';
import { DynamicLayoutComponent, RouterOutletComponent } from '../components';
import { eLayoutType } from '../enums/common';
import { ABP } from '../models';
import { ApplicationConfigurationService, RoutesService } from '../services';
import {
ApplicationConfigurationService,
RoutesService,
ReplaceableComponentsService,
} from '../services';
import { ReplaceableComponentsState } from '../states';
@Component({
@ -78,33 +82,7 @@ const routes: ABP.Route[] = [
},
];
const storeData = {
ReplaceableComponentsState: {
replaceableComponents: [
{
key: 'Theme.ApplicationLayoutComponent',
component: DummyApplicationLayoutComponent,
},
{
key: 'Theme.AccountLayoutComponent',
component: DummyAccountLayoutComponent,
},
{
key: 'Theme.EmptyLayoutComponent',
component: DummyEmptyLayoutComponent,
},
],
},
};
describe('DynamicLayoutComponent', () => {
const mockActions: Actions = NEVER;
const mockStore = ({
selectSnapshot() {
return true;
},
} as unknown) as Store;
const createComponent = createRoutingFactory({
component: RouterOutletComponent,
stubsEnabled: false,
@ -113,10 +91,16 @@ describe('DynamicLayoutComponent', () => {
providers: [
{
provide: RoutesService,
useFactory: () => new RoutesService(mockActions, mockStore),
useFactory: () =>
new RoutesService(NEVER, ({
selectSnapshot() {
return true;
},
} as unknown) as Store),
},
ReplaceableComponentsService,
],
imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot([ReplaceableComponentsState])],
imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot()],
routes: [
{ path: '', component: RouterOutletComponent },
{
@ -163,15 +147,26 @@ describe('DynamicLayoutComponent', () => {
});
let spectator: SpectatorRouting<RouterOutletComponent>;
let store: Store;
let replaceableComponents: ReplaceableComponentsService;
beforeEach(async () => {
spectator = createComponent();
store = spectator.inject(Store);
replaceableComponents = spectator.inject(ReplaceableComponentsService);
const routesService = spectator.inject(RoutesService);
routesService.add(routes);
store.reset(storeData);
replaceableComponents.add({
key: 'Theme.ApplicationLayoutComponent',
component: DummyApplicationLayoutComponent,
});
replaceableComponents.add({
key: 'Theme.AccountLayoutComponent',
component: DummyAccountLayoutComponent,
});
replaceableComponents.add({
key: 'Theme.EmptyLayoutComponent',
component: DummyEmptyLayoutComponent,
});
});
it('should handle application layout from parent abp route and display it', async () => {
@ -204,8 +199,8 @@ describe('DynamicLayoutComponent', () => {
});
it('should not display any layout when layouts are empty', async () => {
store.reset({ ...storeData, ReplaceableComponentsState: {} });
const spy = jest.spyOn(replaceableComponents, 'get');
spy.mockReturnValue(null);
spectator.detectChanges();
spectator.router.navigateByUrl('/withoutLayout');

@ -1,10 +1,9 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngxs/store';
import { of, Subject, BehaviorSubject } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { BehaviorSubject } from 'rxjs';
import { ReplaceableRouteContainerComponent } from '../components/replaceable-route-container.component';
import { ReplaceableComponentsState } from '../states';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
@Component({
selector: 'abp-external-component',
@ -30,18 +29,18 @@ const activatedRouteMock = {
};
describe('ReplaceableRouteContainerComponent', () => {
const selectResponse = new BehaviorSubject(undefined);
const mockSelect = jest.fn(() => selectResponse);
let spectator: SpectatorHost<ReplaceableRouteContainerComponent>;
const get$Res = new BehaviorSubject(undefined);
const createHost = createHostFactory({
component: ReplaceableRouteContainerComponent,
providers: [
{ provide: ActivatedRoute, useValue: activatedRouteMock },
{ provide: Store, useValue: { select: mockSelect } },
{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } },
],
declarations: [ExternalComponent, DefaultComponent],
entryComponents: [DefaultComponent, ExternalComponent],
mocks: [Router],
});
beforeEach(() => {
@ -55,11 +54,11 @@ describe('ReplaceableRouteContainerComponent', () => {
});
it("should display the external component if it's available in store.", () => {
selectResponse.next({ component: ExternalComponent });
get$Res.next({ component: ExternalComponent });
spectator.detectChanges();
expect(spectator.query('p')).toHaveText('external');
selectResponse.next({ component: null });
get$Res.next({ component: null });
spectator.detectChanges();
expect(spectator.query('p')).toHaveText('default');
});

@ -1,9 +1,11 @@
import { Component, EventEmitter, Inject, Input, OnInit, Optional, Output } from '@angular/core';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { Subject } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { ReplaceableTemplateDirective } from '../directives';
import { ReplaceableComponents } from '../models';
import { Router } from '@angular/router';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
@Component({
selector: 'abp-default-component',
@ -48,15 +50,15 @@ class ExternalComponent {
}
describe('ReplaceableTemplateDirective', () => {
const selectResponse = new Subject();
const mockSelect = jest.fn(() => selectResponse);
let spectator: SpectatorDirective<ReplaceableTemplateDirective>;
const get$Res = new BehaviorSubject(undefined);
const createDirective = createDirectiveFactory({
directive: ReplaceableTemplateDirective,
providers: [{ provide: Store, useValue: { select: mockSelect } }],
declarations: [DefaultComponent, ExternalComponent],
entryComponents: [ExternalComponent],
mocks: [Router],
providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }],
});
describe('without external component', () => {
@ -72,7 +74,7 @@ describe('ReplaceableTemplateDirective', () => {
`,
{ hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } },
);
selectResponse.next(undefined);
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
@ -114,7 +116,8 @@ describe('ReplaceableTemplateDirective', () => {
`,
{ hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } },
);
selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
});
afterEach(() => twoWayChange.mockClear());
@ -150,7 +153,7 @@ describe('ReplaceableTemplateDirective', () => {
const externalComponent = spectator.query(ExternalComponent);
spectator.setHostInput({ oneWay: 'test' });
externalComponent.data.inputs.twoWay = true;
selectResponse.next({ component: null, key: 'TestModule.TestComponent' });
get$Res.next({ component: null, key: 'TestModule.TestComponent' });
spectator.detectChanges();
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
@ -161,14 +164,14 @@ describe('ReplaceableTemplateDirective', () => {
});
it('should reset default component subscriptions', () => {
selectResponse.next({ component: null, key: 'TestModule.TestComponent' });
get$Res.next({ component: null, key: 'TestModule.TestComponent' });
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
const unsubscribe = jest.fn(() => {});
spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe;
selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
expect(unsubscribe).toHaveBeenCalled();
});
});

@ -2,6 +2,8 @@ import { PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router';
import { ABP } from '../models/common';
import { RoutesService } from '../services/routes.service';
import { TreeNode } from './tree-utils';
import { noop } from './common-utils';
import { NgZone } from '@angular/core';
export function findRoute(routes: RoutesService, path: string): TreeNode<ABP.Route> {
const node = routes.find(route => route.path === path);
@ -23,3 +25,18 @@ export function getRoutePath(router: Router, url = router.url) {
return '/' + (primaryGroup || emptyGroup).segments.map(({ path }) => path).join('/');
}
export function reloadRoute(router: Router, ngZone: NgZone) {
const { shouldReuseRoute } = router.routeReuseStrategy;
const setRouteReuse = (reuse: typeof shouldReuseRoute) => {
router.routeReuseStrategy.shouldReuseRoute = reuse;
};
setRouteReuse(() => false);
router.navigated = false;
ngZone.run(async () => {
await router.navigateByUrl(router.url).catch(noop);
setRouteReuse(shouldReuseRoute);
});
}

@ -1,4 +1,4 @@
import { AddReplaceableComponent, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core';
import { ReplaceableComponentsService, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core';
import { APP_INITIALIZER } from '@angular/core';
import { Store } from '@ngxs/store';
import { AccountLayoutComponent } from '../components/account-layout/account-layout.component';
@ -11,32 +11,33 @@ export const BASIC_THEME_STYLES_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: configureStyles,
deps: [DomInsertionService, Store],
deps: [DomInsertionService, ReplaceableComponentsService],
multi: true,
},
];
export function configureStyles(domInsertion: DomInsertionService, store: Store) {
export function configureStyles(
domInsertion: DomInsertionService,
replaceableComponents: ReplaceableComponentsService,
) {
return () => {
domInsertion.insertContent(CONTENT_STRATEGY.AppendStyleToHead(styles));
initLayouts(store);
initLayouts(replaceableComponents);
};
}
function initLayouts(store: Store) {
store.dispatch([
new AddReplaceableComponent({
key: eThemeBasicComponents.ApplicationLayout,
component: ApplicationLayoutComponent,
}),
new AddReplaceableComponent({
key: eThemeBasicComponents.AccountLayout,
component: AccountLayoutComponent,
}),
new AddReplaceableComponent({
key: eThemeBasicComponents.EmptyLayout,
component: EmptyLayoutComponent,
}),
]);
function initLayouts(replaceableComponents: ReplaceableComponentsService) {
replaceableComponents.add({
key: eThemeBasicComponents.ApplicationLayout,
component: ApplicationLayoutComponent,
});
replaceableComponents.add({
key: eThemeBasicComponents.AccountLayout,
component: AccountLayoutComponent,
});
replaceableComponents.add({
key: eThemeBasicComponents.EmptyLayout,
component: EmptyLayoutComponent,
});
}

Loading…
Cancel
Save