Skip to content

Commit 7d81309

Browse files
gkalpakjasonaden
authored andcommitted
feat(aio): lazy-load embedded components (angular#18428)
Fixes angular#16127 PR Close angular#18428
1 parent 225baf4 commit 7d81309

18 files changed

+1356
-480
lines changed

aio/scripts/_payload-limits.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22
"aio": {
33
"master": {
44
"gzip7": {
5-
"inline": 925,
6-
"main": 119519,
7-
"polyfills": 11863
5+
"inline": 941,
6+
"main": 116124,
7+
"polyfills": 11860
88
},
99
"gzip9": {
10-
"inline": 925,
11-
"main": 119301,
12-
"polyfills": 11861
10+
"inline": 941,
11+
"main": 115954,
12+
"polyfills": 11858
1313
},
1414
"uncompressed": {
15-
"inline": 1533,
16-
"main": 486493,
17-
"polyfills": 37068
15+
"inline": 1558,
16+
"main": 456432,
17+
"polyfills": 37070
1818
}
1919
}
2020
}

aio/src/app/app.component.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AppComponent } from './app.component';
1212
import { AppModule } from './app.module';
1313
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
1414
import { Deployment } from 'app/shared/deployment.service';
15+
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
1516
import { GaService } from 'app/shared/ga.service';
1617
import { LocationService } from 'app/shared/location.service';
1718
import { Logger } from 'app/shared/logger.service';
@@ -24,7 +25,7 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
2425
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
2526
import { SearchService } from 'app/search/search.service';
2627
import { SelectComponent } from 'app/shared/select/select.component';
27-
import { TocComponent } from 'app/embedded/toc/toc.component';
28+
import { TocComponent } from 'app/layout/toc/toc.component';
2829
import { TocItem, TocService } from 'app/shared/toc.service';
2930

3031
const sideBySideBreakPoint = 992;
@@ -1033,6 +1034,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
10331034
imports: [ AppModule ],
10341035
providers: [
10351036
{ provide: APP_BASE_HREF, useValue: '/' },
1037+
{ provide: EmbedComponentsService, useClass: TestEmbedComponentsService },
10361038
{ provide: GaService, useClass: TestGaService },
10371039
{ provide: HttpClient, useClass: TestHttpClient },
10381040
{ provide: LocationService, useFactory: () => mockLocationService },
@@ -1047,6 +1049,10 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
10471049
});
10481050
}
10491051

1052+
class TestEmbedComponentsService {
1053+
embedInto = jasmine.createSpy('embedInto').and.returnValue(of([]));
1054+
}
1055+
10501056
class TestGaService {
10511057
locationChanged = jasmine.createSpy('locationChanged');
10521058
}

aio/src/app/app.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, ElementRef, HostBinding, HostListener, OnInit,
22
QueryList, ViewChild, ViewChildren } from '@angular/core';
3-
import { MatSidenav } from '@angular/material';
3+
import { MatSidenav } from '@angular/material/sidenav';
44

55
import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
66
import { DocumentService, DocumentContents } from 'app/documents/document.service';

aio/src/app/app.module.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { AppModule } from 'app/app.module';
3+
import { ComponentsOrModulePath, EMBEDDED_COMPONENTS } from 'app/embed-components/embed-components.service';
4+
import { embeddedComponents } from 'app/embedded/embedded.module';
5+
6+
describe('AppModule', () => {
7+
let componentsMap: {[multiSelectorstring: string]: ComponentsOrModulePath};
8+
9+
beforeEach(() => {
10+
TestBed.configureTestingModule({imports: [AppModule]});
11+
componentsMap = TestBed.get(EMBEDDED_COMPONENTS);
12+
});
13+
14+
it('should provide a map of selectors to embedded components (or module)', () => {
15+
const allSelectors = Object.keys(componentsMap);
16+
17+
expect(allSelectors.length).toBeGreaterThan(1);
18+
allSelectors.forEach(selector => {
19+
const value = componentsMap[selector];
20+
const isArrayOrString = Array.isArray(value) || (typeof value === 'string');
21+
expect(isArrayOrString).toBe(true);
22+
});
23+
});
24+
25+
it('should provide a list of eagerly-loaded embedded components', () => {
26+
const eagerSelector = Object.keys(componentsMap).find(selector => Array.isArray(componentsMap[selector]));
27+
const selectorCount = eagerSelector.split(',').length;
28+
29+
expect(eagerSelector).not.toBeNull();
30+
expect(selectorCount).toBe(componentsMap[eagerSelector].length);
31+
32+
// For example...
33+
expect(eagerSelector).toContain('aio-toc');
34+
});
35+
36+
it('should provide a list of lazy-loaded embedded components', () => {
37+
const lazySelector = Object.keys(componentsMap).find(selector => selector.includes('code-example'));
38+
const selectorCount = lazySelector.split(',').length;
39+
40+
expect(lazySelector).not.toBeNull();
41+
expect(selectorCount).toBe(embeddedComponents.length);
42+
43+
// For example...
44+
expect(lazySelector).toContain('code-example');
45+
expect(lazySelector).toContain('code-tabs');
46+
expect(lazySelector).toContain('live-example');
47+
});
48+
});

aio/src/app/app.module.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,26 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
55

66
import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
77

8-
import {
9-
MatButtonModule,
10-
MatIconModule,
11-
MatIconRegistry,
12-
MatInputModule,
13-
MatProgressBarModule,
14-
MatSidenavModule,
15-
MatTabsModule,
16-
MatToolbarModule
17-
} from '@angular/material';
18-
19-
import {
20-
Platform
21-
} from '@angular/cdk/platform';
8+
import { MatButtonModule } from '@angular/material/button';
9+
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
10+
import { MatProgressBarModule } from '@angular/material/progress-bar';
11+
import { MatSidenavModule } from '@angular/material/sidenav';
12+
import { MatToolbarModule } from '@angular/material/toolbar';
2213

14+
import { ROUTES } from '@angular/router';
2315

2416
// Temporary fix for MatSidenavModule issue:
2517
// crashes with "missing first" operator when SideNav.mode is "over"
2618
import 'rxjs/add/operator/first';
2719

28-
import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';
2920

3021
import { AppComponent } from 'app/app.component';
31-
import { ApiService } from 'app/embedded/api/api.service';
22+
import { EMBEDDED_COMPONENTS, EmbeddedComponentsMap } from 'app/embed-components/embed-components.service';
3223
import { CustomIconRegistry, SVG_ICONS } from 'app/shared/custom-icon-registry';
3324
import { Deployment } from 'app/shared/deployment.service';
3425
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
3526
import { DtComponent } from 'app/layout/doc-viewer/dt.component';
3627
import { ModeBannerComponent } from 'app/layout/mode-banner/mode-banner.component';
37-
import { EmbeddedModule } from 'app/embedded/embedded.module';
3828
import { GaService } from 'app/shared/ga.service';
3929
import { Logger } from 'app/shared/logger.service';
4030
import { LocationService } from 'app/shared/location.service';
@@ -47,11 +37,18 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
4737
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
4838
import { ScrollService } from 'app/shared/scroll.service';
4939
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
50-
import { SearchBoxComponent } from './search/search-box/search-box.component';
40+
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
41+
import { TocComponent } from 'app/layout/toc/toc.component';
5142
import { TocService } from 'app/shared/toc.service';
5243
import { WindowToken, windowProvider } from 'app/shared/window';
5344

45+
import { EmbedComponentsModule } from 'app/embed-components/embed-components.module';
5446
import { SharedModule } from 'app/shared/shared.module';
47+
import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';
48+
49+
50+
// The path to the `EmbeddedModule`.
51+
const embeddedModulePath = 'app/embedded/embedded.module#EmbeddedModule';
5552

5653
// These are the hardcoded inline svg sources to be used by the `<mat-icon>` component
5754
export const svgIconProviders = [
@@ -78,15 +75,13 @@ export const svgIconProviders = [
7875
@NgModule({
7976
imports: [
8077
BrowserModule,
81-
EmbeddedModule,
82-
HttpClientModule,
8378
BrowserAnimationsModule,
79+
EmbedComponentsModule,
80+
HttpClientModule,
8481
MatButtonModule,
8582
MatIconModule,
86-
MatInputModule,
8783
MatProgressBarModule,
8884
MatSidenavModule,
89-
MatTabsModule,
9085
MatToolbarModule,
9186
SwUpdatesModule,
9287
SharedModule
@@ -100,10 +95,10 @@ export const svgIconProviders = [
10095
NavMenuComponent,
10196
NavItemComponent,
10297
SearchBoxComponent,
98+
TocComponent,
10399
TopMenuComponent,
104100
],
105101
providers: [
106-
ApiService,
107102
Deployment,
108103
DocumentService,
109104
GaService,
@@ -113,15 +108,32 @@ export const svgIconProviders = [
113108
LocationService,
114109
{ provide: MatIconRegistry, useClass: CustomIconRegistry },
115110
NavigationService,
116-
Platform,
117111
ScrollService,
118112
ScrollSpyService,
119113
SearchService,
120114
svgIconProviders,
121115
TocService,
122116
{ provide: WindowToken, useFactory: windowProvider },
117+
118+
{
119+
provide: EMBEDDED_COMPONENTS,
120+
useValue: {
121+
/* tslint:disable: max-line-length */
122+
'aio-toc': [TocComponent],
123+
'aio-api-list, aio-contributor-list, aio-file-not-found-search, aio-resource-list, code-example, code-tabs, current-location, live-example': embeddedModulePath,
124+
/* tslint:enable: max-line-length */
125+
} as EmbeddedComponentsMap,
126+
},
127+
{
128+
// This is currently the only way to get `@angular/cli`
129+
// to split `EmbeddedModule` into a separate chunk :(
130+
provide: ROUTES,
131+
useValue: [{ path: '/embedded', loadChildren: embeddedModulePath }],
132+
multi: true,
133+
},
123134
],
124-
bootstrap: [AppComponent]
135+
entryComponents: [ TocComponent ],
136+
bootstrap: [ AppComponent ]
125137
})
126138
export class AppModule {
127139
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core';
2+
3+
import { EmbedComponentsService } from './embed-components.service';
4+
5+
6+
@NgModule({
7+
providers: [
8+
EmbedComponentsService,
9+
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
10+
],
11+
})
12+
export class EmbedComponentsModule {
13+
}

0 commit comments

Comments
 (0)