Skip to content

Commit

Permalink
fix(app-loader): should active subapp when subapp is bootstrapping by…
Browse files Browse the repository at this point in the history
… preload #OSP-127
  • Loading branch information
why520crazy authored and walkerkay committed May 21, 2021
1 parent ed2621d commit db1f4f9
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 69 deletions.
2 changes: 1 addition & 1 deletion examples/portal/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@
</div>
</div>

<thy-loading *ngIf="!loadingDone" [thyDone]="loadingDone"></thy-loading>
<thy-loading class="portal" *ngIf="!loadingDone" [thyDone]="loadingDone"></thy-loading>
</thy-layout>
85 changes: 77 additions & 8 deletions packages/planet/src/application/planet-application-loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,9 @@ import { NgZone, Injector, ApplicationRef } from '@angular/core';
import { BootstrapOptions, PlanetApplicationRef } from './planet-application-ref';
import { app1, app2 } from '../testing/applications';
import { Planet } from 'ngx-planet/planet';
import {
getApplicationLoader,
getApplicationService,
clearGlobalPlanet,
globalPlanet,
getPlanetApplicationRef
} from 'ngx-planet/global-planet';
import { getApplicationLoader, getApplicationService, clearGlobalPlanet } from 'ngx-planet/global-planet';
import { RouterTestingModule } from '@angular/router/testing';
import { sample } from '../testing';
import { sample } from '../testing/utils';

class PlanetApplicationRefFaker {
planetAppRef: PlanetApplicationRef;
Expand Down Expand Up @@ -854,6 +848,81 @@ describe('PlanetApplicationLoader', () => {
tick();
}));

it(`should active app1 success when app1 is bootstrapping by preload`, fakeAsync(() => {
const newApp1 = {
...app1,
preload: true
};

planetApplicationService.unregister(app1.name);
planetApplicationService.register(newApp1);

const loadApp1Assets$ = new Subject<[AssetsLoadResult[], AssetsLoadResult[]]>();
const loadApp2Assets$ = new Subject<[AssetsLoadResult[], AssetsLoadResult[]]>();

const app1RefFaker = PlanetApplicationRefFaker.create(app1.name);

const assetsLoaderSpy = spyOn(assetsLoader, 'loadAppAssets');
assetsLoaderSpy.and.returnValues(loadApp1Assets$, loadApp2Assets$);

const appStatusChangeFaker = AppStatusChangeFaker.create(planetApplicationLoader);

expect(appStatusChangeFaker.spy).not.toHaveBeenCalled();
planetApplicationLoader.reroute({ url: '/dashboard' });

flush();
loadApp1Assets$.next();
loadApp1Assets$.complete();

planetApplicationLoader.reroute({ url: '/app1' });

flush();

// 测试在 app1 被预加载处于 bootstrapping 状态,然后路由跳转加载 app1 订阅 bootstrapped 事件后激活应用
app1RefFaker.bootstrap();

appStatusChangeFaker.expectAppStatus('app1', ApplicationStatus.active);
}));

it(`should active app1 success when app1 is bootstrapping by preload and bootstrapped in setTimeout`, fakeAsync(() => {
const newApp1 = {
...app1,
preload: true
};

planetApplicationService.unregister(app1.name);
planetApplicationService.register(newApp1);

const loadApp1Assets$ = new Subject<[AssetsLoadResult[], AssetsLoadResult[]]>();
const loadApp2Assets$ = new Subject<[AssetsLoadResult[], AssetsLoadResult[]]>();

const app1RefFaker = PlanetApplicationRefFaker.create(app1.name);

const assetsLoaderSpy = spyOn(assetsLoader, 'loadAppAssets');
assetsLoaderSpy.and.returnValues(loadApp1Assets$, loadApp2Assets$);

const appStatusChangeFaker = AppStatusChangeFaker.create(planetApplicationLoader);

expect(appStatusChangeFaker.spy).not.toHaveBeenCalled();
planetApplicationLoader.reroute({ url: '/dashboard' });

flush();
loadApp1Assets$.next();
loadApp1Assets$.complete();

planetApplicationLoader.reroute({ url: '/app1' });
// It must be bootstrap first and then flush (setTimeout)
// Because app1 may be bootstrapping before setTimeout
// But app1 is bootstrapped after setTimeout, expect to load success in this case
// 先启用,后 flush,测试在 flush 之前应用是 bootstrapping 状态,但是 flush 之后是 bootstrapped 状态的场景
app1RefFaker.bootstrap();

// start forkJoin(apps$) in setTimeout
flush();

appStatusChangeFaker.expectAppStatus('app1', ApplicationStatus.active);
}));

it('should preload app when status is in assetsLoading, assetsLoaded or bootstrapping', fakeAsync(() => {
[ApplicationStatus.assetsLoading, ApplicationStatus.assetsLoaded, ApplicationStatus.bootstrapping].forEach(
status => {
Expand Down
141 changes: 81 additions & 60 deletions packages/planet/src/application/planet-application-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,34 @@ export class PlanetApplicationLoader {

private setAppStatus(app: PlanetApplication, status: ApplicationStatus) {
this.ngZone.run(() => {
const fromStatus = this.appsStatus.get(app);
debug(
`app(${app.name}) status change: ${fromStatus ? ApplicationStatus[fromStatus] : 'empty'} => ${
ApplicationStatus[status]
}`
);
this.appsStatus.set(app, status);
this.appStatusChange$.next({
app: app,
status: status
});
this.appsStatus.set(app, status);
});
}

private getAppStatusChange$(
app: PlanetApplication,
status = ApplicationStatus.bootstrapped
): Observable<PlanetApplication> {
return this.appStatusChange.pipe(
filter(event => {
return event.app === app && event.status === status;
}),
map(() => {
return app;
})
);
}

private switchModeIsCoexist(app: PlanetApplication) {
if (app && app.switchMode) {
return app.switchMode === SwitchModes.coexist;
Expand Down Expand Up @@ -167,9 +187,11 @@ export class PlanetApplicationLoader {
appStatus === ApplicationStatus.assetsLoading ||
appStatus === ApplicationStatus.loadError
) {
debug(
`app(${app.name}) status is ${ApplicationStatus[appStatus]}, start load assets`
);
hasAppsNeedLoadingAssets = true;
return this.ngZone.runOutsideAngular(() => {
debug(`start load app(${app.name}) assets`);
return this.startLoadAppAssets(app);
});
} else {
Expand All @@ -183,60 +205,61 @@ export class PlanetApplicationLoader {
}),
// Bootstrap or show apps
map(apps => {
const apps$: Observable<PlanetApplication>[] = [];
apps.forEach(app => {
const appStatus = this.appsStatus.get(app);
if (appStatus === ApplicationStatus.bootstrapped) {
apps$.push(
of(app).pipe(
tap(() => {
debug(`app(${app.name}) status is bootstrapped, show app and active`);
this.showApp(app);
const appRef = getPlanetApplicationRef(app.name);
const apps$: Observable<PlanetApplication>[] = apps.map(app => {
return of(app).pipe(
switchMap(app => {
const appStatus = this.appsStatus.get(app);
if (appStatus === ApplicationStatus.bootstrapped) {
debug(
`[routeChange] app(${app.name}) status is bootstrapped, show app and active`
);
this.showApp(app);
const appRef = getPlanetApplicationRef(app.name);
appRef.navigateByUrl(event.url);
this.setAppStatus(app, ApplicationStatus.active);
this.setLoadingDone();
return of(app);
} else if (appStatus === ApplicationStatus.assetsLoaded) {
debug(
`[routeChange] app(${app.name}) status is assetsLoaded, start bootstrapping`
);
return this.bootstrapApp(app).pipe(
map(() => {
debug(`app(${app.name}) bootstrapped success, active it`);
this.setAppStatus(app, ApplicationStatus.active);
this.setLoadingDone();
return app;
})
);
} else if (appStatus === ApplicationStatus.active) {
debug(`[routeChange] app(${app.name}) is active, do nothings`);
const appRef = getPlanetApplicationRef(app.name);
// Backwards compatibility sub app use old version which has not getCurrentRouterStateUrl
const currentUrl = appRef.getCurrentRouterStateUrl
? appRef.getCurrentRouterStateUrl()
: '';
if (currentUrl !== event.url) {
appRef.navigateByUrl(event.url);
this.setAppStatus(app, ApplicationStatus.active);
this.setLoadingDone();
})
)
);
} else if (appStatus === ApplicationStatus.assetsLoaded) {
apps$.push(
of(app).pipe(
switchMap(() => {
debug(`app(${app.name}) status is assetsLoaded, start bootstrapping`);
return this.bootstrapApp(app).pipe(
map(() => {
debug(`app(${app.name}) bootstrapped success, active it`);
this.setAppStatus(app, ApplicationStatus.active);
this.setLoadingDone();
return app;
})
);
})
)
);
} else if (appStatus === ApplicationStatus.active) {
apps$.push(
of(app).pipe(
tap(() => {
debug(`app(${app.name}) is active, do nothings`);
const appRef = getPlanetApplicationRef(app.name);
// Backwards compatibility sub app use old version which has not getCurrentRouterStateUrl
const currentUrl = appRef.getCurrentRouterStateUrl
? appRef.getCurrentRouterStateUrl()
: '';
if (currentUrl !== event.url) {
appRef.navigateByUrl(event.url);
}
})
)
);
} else {
debug(`app(${app.name}) status is ${ApplicationStatus[appStatus]}`);
throw new Error(
`app(${app.name})'s status is ${ApplicationStatus[appStatus]}, can't be show or bootstrap`
);
}
}
return of(app);
} else {
debug(
`[routeChange] app(${app.name}) status is ${ApplicationStatus[appStatus]}`
);
return this.getAppStatusChange$(app).pipe(
take(1),
map(() => {
debug(
`app(${app.name}) status is bootstrapped by subscribe status change, active it`
);
this.setAppStatus(app, ApplicationStatus.active);
this.showApp(app);
return app;
})
);
}
})
);
});

if (apps$.length > 0) {
Expand All @@ -258,6 +281,7 @@ export class PlanetApplicationLoader {
}
});
} else {
debug(`no apps need to be loaded, ensure preload apps`);
this.ensurePreloadApps(apps);
this.setLoadingDone();
}
Expand Down Expand Up @@ -362,7 +386,7 @@ export class PlanetApplicationLoader {
}
return result.pipe(
tap(() => {
debug(`app(${app.name}) bootstrapped success`);
debug(`app(${app.name}) bootstrapped success for ${defaultStatus}`);
this.setAppStatus(app, ApplicationStatus.bootstrapped);
if (defaultStatus === 'display' && appRootElement) {
appRootElement.removeAttribute('style');
Expand Down Expand Up @@ -489,10 +513,7 @@ export class PlanetApplicationLoader {
)
) {
debug(`preload app(${app.name}), status is ${ApplicationStatus[status]}, return until bootstrapped`);
return this.appStatusChange.pipe(
filter(event => {
return event.app === app && event.status === ApplicationStatus.bootstrapped;
}),
return this.getAppStatusChange$(app).pipe(
take(1),
map(() => {
return getPlanetApplicationRef(app.name);
Expand Down
1 change: 1 addition & 0 deletions packages/planet/src/debug.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('debug', () => {
it('should bypass create debug and log message', () => {
const debug = createDebug('app-loader');
debug('this is debug message');
expect(true).toBe(true);
});

it('should set debug factory success', () => {
Expand Down
16 changes: 16 additions & 0 deletions packages/planet/src/testing/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { setDebugFactory, clearDebugFactory } from '../debug';

export function roundNumber(minNum: number, maxNum?: number): number {
switch (arguments.length) {
case 1:
Expand All @@ -13,3 +15,17 @@ export function sample<T>(items: T[]): T {
const num = roundNumber(1, items.length);
return items[num - 1];
}

export function enableDebugForTest() {
const debugFactory = (namespace: string) => {
return (formatter: string) => {
console.log(`[${namespace}] ${formatter}`);
};
};

setDebugFactory(debugFactory as any);
}

export function disableDebugForTest() {
clearDebugFactory();
}

0 comments on commit db1f4f9

Please sign in to comment.