feat(aio): lazy-load embedded components (#18428)
Fixes #16127 PR Close #18428
This commit is contained in:

committed by
Jason Aden

parent
225baf4686
commit
7d81309e11
85
aio/src/testing/doc-viewer-utils.ts
Normal file
85
aio/src/testing/doc-viewer-utils.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { Component, ComponentRef, NgModule, ViewChild } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { DocumentContents } from 'app/documents/document.service';
|
||||
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// `TestDocViewerComponent` (for exposing internal `DocViewerComponent` methods as public). ///
|
||||
/// Only used for type-casting; the actual implementation is irrelevant. ///
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export class TestDocViewerComponent extends DocViewerComponent {
|
||||
embeddedComponentRefs: ComponentRef<any>[];
|
||||
|
||||
addTitleAndToc(docId: string): void { return null as any; }
|
||||
destroyEmbeddedComponents(): void { return null as any; }
|
||||
render(doc: DocumentContents): Observable<void> { return null as any; }
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// `TestModule` and `TestParentComponent`. ///
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Test parent component.
|
||||
@Component({
|
||||
selector: 'aio-test',
|
||||
template: '<aio-doc-viewer [doc]="currentDoc">Test Component</aio-doc-viewer>',
|
||||
})
|
||||
export class TestParentComponent {
|
||||
currentDoc: DocumentContents;
|
||||
@ViewChild(DocViewerComponent) docViewer: DocViewerComponent;
|
||||
}
|
||||
|
||||
// Mock services.
|
||||
export class MockEmbedComponentsService {
|
||||
embedInto = jasmine.createSpy('EmbedComponentsService#embedInto');
|
||||
}
|
||||
|
||||
export class MockTitle {
|
||||
setTitle = jasmine.createSpy('Title#reset');
|
||||
}
|
||||
|
||||
export class MockTocService {
|
||||
genToc = jasmine.createSpy('TocService#genToc');
|
||||
reset = jasmine.createSpy('TocService#reset');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DocViewerComponent,
|
||||
TestParentComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
{ provide: EmbedComponentsService, useClass: MockEmbedComponentsService },
|
||||
{ provide: Title, useClass: MockTitle },
|
||||
{ provide: TocService, useClass: MockTocService },
|
||||
],
|
||||
})
|
||||
export class TestModule { }
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// An observable with spies to test subscribing/unsubscribing. ///
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export class ObservableWithSubscriptionSpies<T = void> extends Observable<T> {
|
||||
unsubscribeSpies: jasmine.Spy[] = [];
|
||||
subscribeSpy = spyOn(this, 'subscribe').and.callFake((...args) => {
|
||||
const subscription = super.subscribe(...args);
|
||||
const unsubscribeSpy = spyOn(subscription, 'unsubscribe').and.callThrough();
|
||||
this.unsubscribeSpies.push(unsubscribeSpy);
|
||||
return subscription;
|
||||
});
|
||||
|
||||
constructor(subscriber = () => undefined) { super(subscriber); }
|
||||
}
|
138
aio/src/testing/embed-components-utils.ts
Normal file
138
aio/src/testing/embed-components-utils.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import {
|
||||
Component, ComponentFactoryResolver, ComponentRef, CompilerFactory, ElementRef, NgModule,
|
||||
NgModuleFactoryLoader, OnInit, Type, ViewChild, getPlatform
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ComponentsOrModulePath, EMBEDDED_COMPONENTS, EmbedComponentsService, EmbeddedComponentFactory,
|
||||
WithEmbeddedComponents
|
||||
} from 'app/embed-components/embed-components.service';
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// `TestEmbedComponentsService` (for exposing internal methods as public). ///
|
||||
/// Only used for type-casting; the actual implementation is irrelevant. ///
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export class TestEmbedComponentsService extends EmbedComponentsService {
|
||||
componentFactories: Map<string, EmbeddedComponentFactory>;
|
||||
|
||||
createComponentFactories(components: Type<any>[], resolver: ComponentFactoryResolver): void { return null as any; }
|
||||
createComponents(elem: HTMLElement): ComponentRef<any>[] { return null as any; }
|
||||
prepareComponentFactories(compsOrPath: ComponentsOrModulePath): Promise<void> { return null as any; }
|
||||
selectorToContentPropertyName(selector: string): string { return null as any; }
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Mock `EmbeddedModule` and test components. ///
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Test embedded components.
|
||||
@Component({
|
||||
selector: 'aio-eager-foo',
|
||||
template: `Eager Foo Component`,
|
||||
})
|
||||
class EagerFooComponent { }
|
||||
|
||||
@Component({
|
||||
selector: 'aio-eager-bar',
|
||||
template: `
|
||||
<hr>
|
||||
<h2>Eager Bar Component</h2>
|
||||
<p #content></p>
|
||||
<hr>
|
||||
`,
|
||||
})
|
||||
class EagerBarComponent implements OnInit {
|
||||
@ViewChild('content') contentRef: ElementRef;
|
||||
|
||||
constructor(public elementRef: ElementRef) { }
|
||||
|
||||
// Project content in `ngOnInit()` just like in `CodeExampleComponent`.
|
||||
ngOnInit() {
|
||||
// Security: This is a test component; never deployed.
|
||||
this.contentRef.nativeElement.innerHTML = this.elementRef.nativeElement.aioEagerBarContent;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'aio-lazy-foo',
|
||||
template: `Lazy Foo Component`,
|
||||
})
|
||||
class LazyFooComponent { }
|
||||
|
||||
@Component({
|
||||
selector: 'aio-lazy-bar',
|
||||
template: `
|
||||
<hr>
|
||||
<h2>Lazy Bar Component</h2>
|
||||
<p #content></p>
|
||||
<hr>
|
||||
`,
|
||||
})
|
||||
class LazyBarComponent implements OnInit {
|
||||
@ViewChild('content') contentRef: ElementRef;
|
||||
|
||||
constructor(public elementRef: ElementRef) { }
|
||||
|
||||
// Project content in `ngOnInit()` just like in `CodeExampleComponent`.
|
||||
ngOnInit() {
|
||||
// Security: This is a test component; never deployed.
|
||||
this.contentRef.nativeElement.innerHTML = this.elementRef.nativeElement.aioLazyBarContent;
|
||||
}
|
||||
}
|
||||
|
||||
// Export test embedded selectors and components.
|
||||
export const testEagerEmbeddedSelectors = ['aio-eager-foo', 'aio-eager-bar'];
|
||||
export const testEagerEmbeddedComponents = [EagerFooComponent, EagerBarComponent];
|
||||
export const testLazyEmbeddedSelectors = ['aio-lazy-foo', 'aio-lazy-bar'];
|
||||
export const testLazyEmbeddedComponents = [LazyFooComponent, LazyBarComponent];
|
||||
|
||||
// Export mock `EmbeddedModule` and path.
|
||||
export const mockEmbeddedModulePath = 'mock/mock-embedded#MockEmbeddedModule';
|
||||
|
||||
@NgModule({
|
||||
declarations: [testLazyEmbeddedComponents],
|
||||
entryComponents: [testLazyEmbeddedComponents],
|
||||
})
|
||||
class MockEmbeddedModule implements WithEmbeddedComponents {
|
||||
embeddedComponents = testLazyEmbeddedComponents;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// `TestModule`. ///
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Mock services.
|
||||
export class MockNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||
loadedPaths: string[] = [];
|
||||
|
||||
load(path: string) {
|
||||
this.loadedPaths.push(path);
|
||||
|
||||
const platformRef = getPlatform();
|
||||
const compilerFactory = platformRef.injector.get(CompilerFactory) as CompilerFactory;
|
||||
const compiler = compilerFactory.createCompiler([]);
|
||||
|
||||
return compiler.compileModuleAsync(MockEmbeddedModule);
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
EmbedComponentsService,
|
||||
{ provide: NgModuleFactoryLoader, useClass: MockNgModuleFactoryLoader },
|
||||
{
|
||||
provide: EMBEDDED_COMPONENTS,
|
||||
useValue: {
|
||||
[testEagerEmbeddedSelectors.join(',')]: testEagerEmbeddedComponents,
|
||||
[testLazyEmbeddedSelectors.join(',')]: mockEmbeddedModulePath,
|
||||
},
|
||||
},
|
||||
],
|
||||
declarations: [testEagerEmbeddedComponents],
|
||||
entryComponents: [testEagerEmbeddedComponents],
|
||||
})
|
||||
export class TestModule { }
|
Reference in New Issue
Block a user