diff --git a/examples/app1/src/app/app.module.ts b/examples/app1/src/app/app.module.ts index 5c56a06..973ce95 100644 --- a/examples/app1/src/app/app.module.ts +++ b/examples/app1/src/app/app.module.ts @@ -10,9 +10,10 @@ import { AppRootComponent } from './root/root.component'; import { DemoCommonModule } from '@demo/common'; import { NgxPlanetModule } from 'ngx-planet'; import { UserModule } from './user/user.module'; +import { ProjectsComponent } from './projects/projects.component'; @NgModule({ - declarations: [AppComponent, AppRootComponent, DashboardComponent], + declarations: [AppComponent, AppRootComponent, DashboardComponent, ProjectsComponent], imports: [ BrowserModule, RouterModule.forRoot(routers), diff --git a/examples/app1/src/app/app.routing.ts b/examples/app1/src/app/app.routing.ts index 6708b2f..fd50bf0 100644 --- a/examples/app1/src/app/app.routing.ts +++ b/examples/app1/src/app/app.routing.ts @@ -2,6 +2,7 @@ import { DashboardComponent } from './dashboard/dashboard.component'; import { Route } from '@angular/router'; import { AppRootComponent } from './root/root.component'; import { EmptyComponent } from 'ngx-planet'; +import { ProjectsComponent } from './projects/projects.component'; export const routers: Route[] = [ { @@ -20,6 +21,10 @@ export const routers: Route[] = [ { path: 'users', loadChildren: () => import('./user/user.module').then(mod => mod.UserModule) + }, + { + path: 'projects', + component: ProjectsComponent } // { // path: 'users', diff --git a/examples/app1/src/app/dashboard/dashboard.component.html b/examples/app1/src/app/dashboard/dashboard.component.html index da318f6..fec665b 100644 --- a/examples/app1/src/app/dashboard/dashboard.component.html +++ b/examples/app1/src/app/dashboard/dashboard.component.html @@ -17,8 +17,8 @@
count: {{ counter.count }}
- - + +
diff --git a/examples/app1/src/app/dashboard/dashboard.component.ts b/examples/app1/src/app/dashboard/dashboard.component.ts index b0f5ec8..b00c436 100644 --- a/examples/app1/src/app/dashboard/dashboard.component.ts +++ b/examples/app1/src/app/dashboard/dashboard.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, ViewChild, ElementRef } from '@angular/core'; +import { Component, Inject, ViewChild, ElementRef, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { CounterService } from '../counter.service'; import { AppRootContext } from '@demo/common'; @@ -9,7 +9,7 @@ import { ThyDialog } from 'ngx-tethys'; selector: 'app-dashboard', templateUrl: './dashboard.component.html' }) -export class DashboardComponent { +export class DashboardComponent implements OnInit { @ViewChild('container', { static: true }) containerElementRef: ElementRef; private componentRef: PlanetComponentRef; @@ -24,15 +24,21 @@ export class DashboardComponent { private planetComponentLoader: PlanetComponentLoader ) {} + ngOnInit() {} + openADetail() { this.globalEventDispatcher.dispatch('openADetail'); } openApp2Component() { - this.componentRef = this.planetComponentLoader.load('app2', 'project1', { - container: this.containerElementRef, - initialState: {} - }); + this.planetComponentLoader + .load('app2', 'project1', { + container: this.containerElementRef, + initialState: {} + }) + .subscribe(componentRef => { + this.componentRef = componentRef; + }); } disposeApp2Component() { diff --git a/examples/app1/src/app/projects/projects.component.ts b/examples/app1/src/app/projects/projects.component.ts new file mode 100644 index 0000000..7084178 --- /dev/null +++ b/examples/app1/src/app/projects/projects.component.ts @@ -0,0 +1,32 @@ +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { PlanetComponentLoader } from 'ngx-planet'; + +@Component({ + selector: 'app-projects', + template: ` + + +
+
+ ` +}) +export class ProjectsComponent implements OnInit { + @ViewChild('container', { static: true }) elementRef: ElementRef; + + loadingDone = false; + + constructor(private planetComponentLoader: PlanetComponentLoader) {} + + ngOnInit() { + this.planetComponentLoader + .load('app2', 'project1', { + container: this.elementRef + }) + .subscribe(componentRef => { + this.loadingDone = true; + componentRef.componentInstance.click.subscribe(() => { + console.log('project item clicked'); + }); + }); + } +} diff --git a/examples/app1/src/app/root/root.component.html b/examples/app1/src/app/root/root.component.html index 80c58ae..2a4b9d4 100644 --- a/examples/app1/src/app/root/root.component.html +++ b/examples/app1/src/app/root/root.component.html @@ -8,6 +8,7 @@ App1 Dashboard App1 Users + App2 Projects diff --git a/examples/app2/src/app/projects/project-list.component.ts b/examples/app2/src/app/projects/project-list.component.ts index ccfd1eb..94ea586 100644 --- a/examples/app2/src/app/projects/project-list.component.ts +++ b/examples/app2/src/app/projects/project-list.component.ts @@ -43,13 +43,12 @@ export class ProjectListComponent implements OnInit, DoCheck { openDetail(event: ThyGridRowEvent) { this.router.navigateByUrl(`/app2/projects/${event.row.id}`); + this.click.emit(); // this.dialog.open(ProjectDetailComponent, { // hasBackdrop: true, // backdropClosable: true, // closeOnNavigation: true // }); - - // this.click.emit(); } ngDoCheck() { diff --git a/package-lock.json b/package-lock.json index 0a02841..a1ddda9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -457,8 +457,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -479,14 +478,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -501,20 +498,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -631,8 +625,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -644,7 +637,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -659,7 +651,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -667,14 +658,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -693,7 +682,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -783,8 +771,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -796,7 +783,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -882,8 +868,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -919,7 +904,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -939,7 +923,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -983,14 +966,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -8690,8 +8671,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -8712,14 +8692,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8734,20 +8712,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -8864,8 +8839,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -8877,7 +8851,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8892,7 +8865,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8900,14 +8872,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8926,7 +8896,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -9016,8 +8985,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -9029,7 +8997,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -9115,8 +9082,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -9152,7 +9118,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9172,7 +9137,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9216,14 +9180,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -10293,8 +10255,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -10315,14 +10276,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10337,20 +10296,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -10467,8 +10423,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -10480,7 +10435,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10495,7 +10449,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10503,14 +10456,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10529,7 +10480,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -10619,8 +10569,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -10632,7 +10581,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -10718,8 +10666,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -10755,7 +10702,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10775,7 +10721,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10819,14 +10764,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -14256,9 +14199,9 @@ } }, "sortablejs": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz", - "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==" + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.8.4.tgz", + "integrity": "sha512-Brqnzelu1AhFuc0Fn3N/qFex1tlIiuQIUsfu2J8luJ4cRgXYkWrByxa+y5mWEBlj8A0YoABukflIJwvHyrwJ6Q==" }, "source-list-map": { "version": "2.0.1", @@ -15683,8 +15626,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -15705,14 +15647,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -15727,20 +15667,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -15857,8 +15794,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -15870,7 +15806,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -15885,7 +15820,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -15893,14 +15827,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -15919,7 +15851,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -16009,8 +15940,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -16022,7 +15952,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -16108,8 +16037,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -16145,7 +16073,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -16165,7 +16092,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -16209,14 +16135,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -16531,8 +16455,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -16553,14 +16476,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -16575,20 +16496,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -16705,8 +16623,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -16718,7 +16635,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -16733,7 +16649,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -16741,14 +16656,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -16767,7 +16680,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -16857,8 +16769,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -16870,7 +16781,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -16956,8 +16866,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -16993,7 +16902,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -17013,7 +16921,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -17057,14 +16964,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index bd86f1b..5667752 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "ngx-bootstrap": "^5.2.0", "ngx-tethys": "^7.6.16", "rxjs": "~6.4.0", - "sortablejs": "^1.10.1", + "sortablejs": "1.8.4", "tslib": "^1.10.0", "zone.js": "~0.9.1", "bootstrap": "4.3.1" diff --git a/packages/planet/src/application/planet-application-ref.ts b/packages/planet/src/application/planet-application-ref.ts index 9d61c67..0a4c9a8 100644 --- a/packages/planet/src/application/planet-application-ref.ts +++ b/packages/planet/src/application/planet-application-ref.ts @@ -10,11 +10,13 @@ import { Observable, from } from 'rxjs'; declare const window: any; export interface GlobalPlanet { apps: { [key: string]: PlanetApplicationRef }; + registerApps: PlanetApplication[]; portalApplication: PlanetPortalApplication; } const globalPlanet: GlobalPlanet = (window.planet = window.planet || { - apps: {} + apps: {}, + registerApps: [] }); export type BootstrapAppModule = (portalApp?: PlanetPortalApplication) => Promise>; @@ -88,12 +90,12 @@ export class PlanetApplicationRef { }); } - registerComponentFactory(componentFactory: PlantComponentFactory) { - this.componentFactory = componentFactory; + getComponentFactory() { + return this.componentFactory; } - loadPlantComponent(componentName: string, config: PlantComponentConfig) { - return this.componentFactory(componentName, config); + registerComponentFactory(componentFactory: PlantComponentFactory) { + this.componentFactory = componentFactory; } destroy(): void { diff --git a/packages/planet/src/application/planet-application.service.spec.ts b/packages/planet/src/application/planet-application.service.spec.ts index 71e0cd6..2c77aa7 100644 --- a/packages/planet/src/application/planet-application.service.spec.ts +++ b/packages/planet/src/application/planet-application.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { PlanetApplicationService } from './planet-application.service'; +import { PlanetApplicationService, getPlanetApplicationByName } from './planet-application.service'; import { SwitchModes } from '../planet.class'; import { HttpClient } from '@angular/common/http'; import { app1, app2, app2WithPreload } from '../test/applications'; @@ -154,4 +154,13 @@ describe('PlanetApplicationService', () => { expect(appsToPreload).toEqual([]); }); }); + + describe('getPlanetApplicationByName', () => { + it('should get planet application by name', () => { + planetApplicationService.register(app1); + const app = getPlanetApplicationByName(app1.name); + expect(app).toBe(app1); + expect(getPlanetApplicationByName(app2.name)).toEqual(undefined); + }); + }); }); diff --git a/packages/planet/src/application/planet-application.service.ts b/packages/planet/src/application/planet-application.service.ts index f68aac1..a3ba236 100644 --- a/packages/planet/src/application/planet-application.service.ts +++ b/packages/planet/src/application/planet-application.service.ts @@ -5,6 +5,7 @@ import { shareReplay, map, switchMap, startWith } from 'rxjs/operators'; import { coerceArray } from '../helpers'; import { Observable, of } from 'rxjs'; import { AssetsLoadResult, AssetsLoader } from '../assets-loader'; +import { globalPlanet } from './planet-application-ref'; @Injectable({ providedIn: 'root' @@ -25,6 +26,7 @@ export class PlanetApplicationService { this.apps.push(app); this.appsMap[app.name] = app; }); + globalPlanet.registerApps = this.apps; } registerByUrl(url: string): Observable { @@ -48,11 +50,12 @@ export class PlanetApplicationService { this.apps = this.apps.filter(app => { return app.name !== name; }); + globalPlanet.registerApps = this.apps; } } getAppsByMatchedUrl(url: string): PlanetApplication[] { - return this.apps.filter(app => { + return this.getApps().filter(app => { if (app.routerPathPrefix instanceof RegExp) { return app.routerPathPrefix.test(url); } else { @@ -62,7 +65,7 @@ export class PlanetApplicationService { } getAppByMatchedUrl(url: string): PlanetApplication { - return this.apps.find(app => { + return this.getApps().find(app => { if (app.routerPathPrefix instanceof RegExp) { return app.routerPathPrefix.test(url); } else { @@ -72,7 +75,7 @@ export class PlanetApplicationService { } getAppsToPreload(excludeAppNames?: string[]) { - return this.apps.filter(app => { + return this.getApps().filter(app => { if (excludeAppNames) { return app.preload && !excludeAppNames.includes(app.name); } else { @@ -85,3 +88,9 @@ export class PlanetApplicationService { return this.apps; } } + +export function getPlanetApplicationByName(name: string) { + return globalPlanet.registerApps.find(app => { + return app.name === name; + }); +} diff --git a/packages/planet/src/component/planet-component-loader.spec.ts b/packages/planet/src/component/planet-component-loader.spec.ts new file mode 100644 index 0000000..844b69c --- /dev/null +++ b/packages/planet/src/component/planet-component-loader.spec.ts @@ -0,0 +1,140 @@ +import { TestBed, inject, tick, fakeAsync } from '@angular/core/testing'; +import { Compiler, Injector, Type, NgModuleRef } from '@angular/core'; +import { app1Name, App1Module, App1ProjectsComponent } from './test/app1.module'; +import { app2Name, App2Module } from './test/app2.module'; +import { defineApplication, getPlanetApplicationRef } from '../application/planet-application-ref'; +import { PlanetPortalApplication } from '../application/portal-application'; +import { PlanetComponentLoader } from './planet-component-loader'; +import { PlanetApplicationLoader } from '../application/planet-application-loader'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterModule } from '@angular/router'; +import { of } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { PlantComponentConfig } from './plant-component.config'; + +describe('PlanetComponentLoader', () => { + let compiler: Compiler; + let injector: Injector; + + function defineAndBootstrapApplication(name: string, appModule: Type) { + const ngModuleFactory = compiler.compileModuleSync(appModule); + const ngModuleRef = ngModuleFactory.create(injector); + defineApplication(name, (portalApp?: PlanetPortalApplication) => { + return new Promise(resolve => { + resolve(ngModuleRef); + }); + }); + const appRef = getPlanetApplicationRef(name); + const portalApplication = new PlanetPortalApplication(); + appRef.bootstrap(portalApplication); + return ngModuleRef; + } + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterModule.forRoot([])] + }); + }); + + afterEach(() => { + (window as any).planet.apps = {}; + }); + + beforeEach(inject([Compiler, Injector], (_compiler: Compiler, _injector: Injector) => { + compiler = _compiler; + injector = _injector; + })); + + it('should register component success', fakeAsync(() => { + // mock app1 and app2 bootstrap + const app1ModuleRef = defineAndBootstrapApplication(app1Name, App1Module); + registerAppComponents(app1ModuleRef); + const app1Ref = getPlanetApplicationRef(app1Name); + expect(app1Ref.getComponentFactory()).toBeTruthy(); + })); + + it('should app2 load app1 component', fakeAsync(() => { + // mock app1 and app2 bootstrap + const app1ModuleRef = defineAndBootstrapApplication(app1Name, App1Module); + const app2ModuleRef = defineAndBootstrapApplication(app2Name, App2Module); + tick(); + + expect(() => { + loadApp1Component(app2ModuleRef); + }).toThrowError(`${app1Name} not registered components`); + + registerAppComponents(app1ModuleRef); + + expect(() => { + loadApp1Component(app2ModuleRef, { container: null }); + }).toThrowError(`config 'container' cannot be null`); + + loadApp1ComponentAndExpectHtml(app2ModuleRef); + })); + + it('should app2 load app1 component and preload app1', fakeAsync(() => { + // mock app2 bootstrap + const app2ModuleRef = defineAndBootstrapApplication(app2Name, App1Module); + // mock app1 preload + const applicationLoader = app2ModuleRef.injector.get(PlanetApplicationLoader); + const applicationLoaderSpy = spyOn(applicationLoader, 'preload'); + const preload$ = of(getPlanetApplicationRef(app1Name)).pipe( + tap(() => { + const app1ModuleRef = defineAndBootstrapApplication(app1Name, App1Module); + registerAppComponents(app1ModuleRef); + }) + ); + applicationLoaderSpy.and.returnValue(preload$); + expect(applicationLoaderSpy).not.toHaveBeenCalled(); + loadApp1ComponentAndExpectHtml(app2ModuleRef); + expect(applicationLoaderSpy).toHaveBeenCalled(); + })); + + it('should app2 dispose app1 component ', fakeAsync(() => { + // mock app1 and app2 bootstrap + const app1ModuleRef = defineAndBootstrapApplication(app1Name, App1Module); + const app2ModuleRef = defineAndBootstrapApplication(app2Name, App2Module); + registerAppComponents(app1ModuleRef); + loadApp1Component(app2ModuleRef).subscribe(componentRef => { + const parent = componentRef.container.parentElement; + componentRef.dispose(); + expect(parent.innerHTML).toEqual(''); + }); + })); +}); + +function registerAppComponents(appModuleRef: NgModuleRef) { + const componentLoader = appModuleRef.injector.get(PlanetComponentLoader); + componentLoader.register([{ name: 'app1-projects', component: App1ProjectsComponent }]); + tick(); +} + +function loadApp1Component(appModuleRef: NgModuleRef, config?: PlantComponentConfig) { + const componentLoader = appModuleRef.injector.get(PlanetComponentLoader); + const hostElement = createComponentHostElement(); + const result = componentLoader.load(app1Name, 'app1-projects', config ? config : { container: hostElement }); + tick(30); + return result; +} + +function loadApp1ComponentAndExpectHtml(app2ModuleRef: NgModuleRef) { + loadApp1Component(app2ModuleRef).subscribe(componentRef => { + expect(componentRef.container.outerHTML).toEqual( + `
projects is work
` + ); + }); +} + +function createComponentHostElement(): HTMLElement { + const componentHostClass = 'component-host'; + let element = document.body.getElementsByClassName(componentHostClass)[0]; + if (element) { + element.innerHTML = ''; + return element as HTMLElement; + } else { + element = document.createElement('DIV'); + element.classList.add('component-host'); + document.body.appendChild(element); + return element as HTMLElement; + } +} diff --git a/packages/planet/src/component/planet-component-loader.ts b/packages/planet/src/component/planet-component-loader.ts index 111e614..cafd78b 100644 --- a/packages/planet/src/component/planet-component-loader.ts +++ b/packages/planet/src/component/planet-component-loader.ts @@ -1,9 +1,14 @@ -import { Injectable, ApplicationRef, NgModuleRef, NgZone, ChangeDetectorRef, ElementRef } from '@angular/core'; +import { Injectable, ApplicationRef, NgModuleRef, NgZone, ElementRef, Inject } from '@angular/core'; import { ComponentType, DomPortalOutlet, ComponentPortal, PortalInjector } from '@angular/cdk/portal'; -import { globalPlanet } from '../application/planet-application-ref'; +import { globalPlanet, PlanetApplicationRef } from '../application/planet-application-ref'; import { PlanetComponentRef } from './planet-component-ref'; import { PlantComponentConfig } from './plant-component.config'; import { coerceArray } from '../helpers'; +import { getPlanetApplicationByName } from '../application/planet-application.service'; +import { PlanetApplicationLoader } from '../application/planet-application-loader'; +import { delay, map, shareReplay, finalize } from 'rxjs/operators'; +import { of, Observable } from 'rxjs'; +import { DOCUMENT } from '@angular/common'; export interface PlanetComponent { name: string; @@ -19,14 +24,23 @@ export class PlanetComponentLoader { constructor( private applicationRef: ApplicationRef, private ngModuleRef: NgModuleRef, - private ngZone: NgZone + private ngZone: NgZone, + private applicationLoader: PlanetApplicationLoader, + @Inject(DOCUMENT) private document: any ) {} - private getPlantAppRef(app: string) { - if (globalPlanet.apps[app]) { - return globalPlanet.apps[app]; + private getPlantAppRef(name: string): Observable { + if (globalPlanet.apps[name] && globalPlanet.apps[name].appModuleRef) { + return of(globalPlanet.apps[name]); } else { - throwAppNotDefineError(app); + const app = getPlanetApplicationByName(name); + return this.applicationLoader.preload(app).pipe( + // Because register use 'setTimeout',so delay 20 + delay(20), + map(() => { + return globalPlanet.apps[name]; + }) + ); } } @@ -40,17 +54,25 @@ export class PlanetComponentLoader { } private getContainerElement(config: PlantComponentConfig): HTMLElement { - if (config.container) { + if (!config.container) { + throw new Error(`config 'container' cannot be null`); + } else { if ((config.container as ElementRef).nativeElement) { return (config.container as ElementRef).nativeElement; } else { return config.container as HTMLElement; } - } else { - throw new Error(`config 'container' cannot be null`); } } + private createContainerElement(config: PlantComponentConfig) { + const container = this.getContainerElement(config); + const element = this.document.createElement('div'); + element.classList.add('planet-component-container'); + container.appendChild(element); + return element; + } + private attachComponent( plantComponent: PlanetComponent, appModuleRef: NgModuleRef, @@ -60,7 +82,7 @@ export class PlanetComponentLoader { const componentFactoryResolver = appModuleRef.componentFactoryResolver; const appRef = this.applicationRef; const injector = this.createInjector(appModuleRef, plantComponentRef); - const container = this.getContainerElement(config); + const container = this.createContainerElement(config); let portalOutlet = this.domPortalOutletCache.get(container); if (portalOutlet) { portalOutlet.detach(); @@ -85,17 +107,18 @@ export class PlanetComponentLoader { private registerComponentFactory(componentOrComponents: PlanetComponent | PlanetComponent[]) { const app = this.ngModuleRef.instance.appName; - const planetAppRef = this.getPlantAppRef(app); - planetAppRef.registerComponentFactory((componentName: string, config: PlantComponentConfig) => { - const components = coerceArray(componentOrComponents); - const component = components.find(item => item.name === componentName); - if (component) { - return this.ngZone.run(() => { - return this.attachComponent(component, planetAppRef.appModuleRef, config); - }); - } else { - throw Error(`unregistered component ${componentName} in app ${app}`); - } + this.getPlantAppRef(app).subscribe(appRef => { + appRef.registerComponentFactory((componentName: string, config: PlantComponentConfig) => { + const components = coerceArray(componentOrComponents); + const component = components.find(item => item.name === componentName); + if (component) { + return this.ngZone.run(() => { + return this.attachComponent(component, appRef.appModuleRef, config); + }); + } else { + throw Error(`unregistered component ${componentName} in app ${app}`); + } + }); }); } @@ -105,17 +128,22 @@ export class PlanetComponentLoader { }); } - load( - app: string, - componentName: string, - config: PlantComponentConfig, - error: (error) => void = e => {} - ) { - const planetAppRef = this.getPlantAppRef(app); - return planetAppRef.loadPlantComponent(componentName, config); + load(app: string, componentName: string, config: PlantComponentConfig) { + const result = this.getPlantAppRef(app).pipe( + map(appRef => { + const componentFactory = appRef.getComponentFactory(); + if (componentFactory) { + return componentFactory(componentName, config); + } else { + throw new Error(`${app} not registered components`); + } + }), + finalize(() => { + this.applicationRef.tick(); + }), + shareReplay() + ); + result.subscribe(); + return result; } } - -function throwAppNotDefineError(app: string) { - throw new Error(`${app} app is not define`); -} diff --git a/packages/planet/src/component/test/app1.module.ts b/packages/planet/src/component/test/app1.module.ts new file mode 100644 index 0000000..a7f5028 --- /dev/null +++ b/packages/planet/src/component/test/app1.module.ts @@ -0,0 +1,35 @@ +import { Component, NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { PlanetComponentLoader } from '../planet-component-loader'; +import { of } from 'rxjs'; +import { PlanetApplicationLoader } from '../../application/planet-application-loader'; + +export const app1Name = 'app1'; + +@Component({ + selector: 'app1-projects', + template: ` + projects is work + ` +}) +export class App1ProjectsComponent {} + +@NgModule({ + declarations: [App1ProjectsComponent], + entryComponents: [App1ProjectsComponent], + imports: [RouterModule.forChild([])], + providers: [ + PlanetComponentLoader, + { + provide: PlanetApplicationLoader, + useValue: { + preload: () => {} + } + } + ] +}) +export class App1Module { + constructor(private componentLoader: PlanetComponentLoader) { + // componentLoader.register([{ name: 'app1-projects', component: App1ProjectsComponent }]); + } +} diff --git a/packages/planet/src/component/test/app2.module.ts b/packages/planet/src/component/test/app2.module.ts new file mode 100644 index 0000000..852be6b --- /dev/null +++ b/packages/planet/src/component/test/app2.module.ts @@ -0,0 +1,25 @@ +import { Component, NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { PlanetApplicationLoader } from '../../application/planet-application-loader'; +import { of } from 'rxjs'; +import { PlanetComponentLoader } from '../planet-component-loader'; + +export const app2Name = 'app2'; + +@NgModule({ + declarations: [], + entryComponents: [], + imports: [RouterModule.forChild([])], + providers: [ + PlanetComponentLoader, + { + provide: PlanetApplicationLoader, + useValue: { + preload: () => {} + } + } + ] +}) +export class App2Module { + constructor() {} +}