diff --git a/src/demo-app/demo-app/demo-module.ts b/src/demo-app/demo-app/demo-module.ts index 566e52b3d27c..3bfdbe2fd92a 100644 --- a/src/demo-app/demo-app/demo-module.ts +++ b/src/demo-app/demo-app/demo-module.ts @@ -51,7 +51,9 @@ import {SnackBarDemo} from '../snack-bar/snack-bar-demo'; import {StepperDemo} from '../stepper/stepper-demo'; import {ScreenTypeDemo} from '../screen-type/screen-type-demo'; import {LayoutModule} from '@angular/cdk/layout'; -import {FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo} from '../tabs/tabs-demo'; +import { + FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo, Counter +} from '../tabs/tabs-demo'; import {ToolbarDemo} from '../toolbar/toolbar-demo'; import {TooltipDemo} from '../tooltip/tooltip-demo'; import {TypographyDemo} from '../typography/typography-demo'; @@ -85,6 +87,7 @@ import {TableDemoModule} from '../table/table-demo-module'; ExpansionDemo, FocusOriginDemo, FoggyTabContent, + Counter, GesturesDemo, GridListDemo, Home, diff --git a/src/demo-app/tabs/tabs-demo.html b/src/demo-app/tabs/tabs-demo.html index 8b85ef763f3d..627046038dfe 100644 --- a/src/demo-app/tabs/tabs-demo.html +++ b/src/demo-app/tabs/tabs-demo.html @@ -288,3 +288,20 @@

Tabs with autosize textarea

+ +

Lazy Loaded Tabs

+ + + + + + + + + + + + + + + diff --git a/src/demo-app/tabs/tabs-demo.ts b/src/demo-app/tabs/tabs-demo.ts index fe7a952b8a7f..1f9263215e3c 100644 --- a/src/demo-app/tabs/tabs-demo.ts +++ b/src/demo-app/tabs/tabs-demo.ts @@ -133,3 +133,14 @@ export class RainyTabContent {} template: 'This is the routed body of the foggy tab.', }) export class FoggyTabContent {} + +@Component({ + moduleId: module.id, + selector: 'counter', + template: `Content` + }) + export class Counter { + ngOnInit() { + console.log('Tab Loaded'); + } + } diff --git a/src/lib/tabs/public-api.ts b/src/lib/tabs/public-api.ts index e02e25709874..f837434ba87b 100644 --- a/src/lib/tabs/public-api.ts +++ b/src/lib/tabs/public-api.ts @@ -20,4 +20,5 @@ export {MatTabLabelWrapper} from './tab-label-wrapper'; export {MatTab} from './tab'; export {MatTabLabel} from './tab-label'; export {MatTabNav, MatTabLink} from './tab-nav-bar/index'; +export {MatTabContent} from './tab-content'; export * from './tabs-animations'; diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index ec5178efed17..78aec0428ce6 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -22,9 +22,17 @@ import { ComponentFactoryResolver, ViewContainerRef, forwardRef, + ViewChild, } from '@angular/core'; -import {AnimationEvent} from '@angular/animations'; -import {TemplatePortal, CdkPortalOutlet} from '@angular/cdk/portal'; +import { + trigger, + state, + style, + animate, + transition, + AnimationEvent, +} from '@angular/animations'; +import {TemplatePortal, CdkPortalOutlet, PortalHostDirective} from '@angular/cdk/portal'; import {Directionality, Direction} from '@angular/cdk/bidi'; import {Subscription} from 'rxjs/Subscription'; import {matTabsAnimations} from './tabs-animations'; @@ -130,6 +138,9 @@ export class MatTabBody implements OnInit { /** Event emitted when the tab completes its animation towards the center. */ @Output() readonly _onCentered: EventEmitter = new EventEmitter(true); + /** The portal host inside of this container into which the tab body content will be loaded. */ + @ViewChild(PortalHostDirective) _portalHost: PortalHostDirective; + /** The tab body content to display. */ @Input('content') _content: TemplatePortal; diff --git a/src/lib/tabs/tab-content.ts b/src/lib/tabs/tab-content.ts new file mode 100644 index 000000000000..369a97ff318d --- /dev/null +++ b/src/lib/tabs/tab-content.ts @@ -0,0 +1,15 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, TemplateRef} from '@angular/core'; + +/** Decorates the `ng-template` tags and reads out the template from it. */ +@Directive({selector: '[matTabContent]'}) +export class MatTabContent { + constructor(public template: TemplateRef) { } +} diff --git a/src/lib/tabs/tab-group.spec.ts b/src/lib/tabs/tab-group.spec.ts index a881988d0281..04cb79a366de 100644 --- a/src/lib/tabs/tab-group.spec.ts +++ b/src/lib/tabs/tab-group.spec.ts @@ -18,6 +18,7 @@ describe('MatTabGroup', () => { AsyncTabsTestApp, DisabledTabsTestApp, TabGroupWithSimpleApi, + TemplateTabs, ], }); @@ -387,6 +388,23 @@ describe('MatTabGroup', () => { }); }); + describe('lazy loaded tabs', () => { + it('should lazy load the second tab', async () => { + let fixture = TestBed.createComponent(TemplateTabs); + fixture.detectChanges(); + + let secondLabel = fixture.debugElement.queryAll(By.css('.mat-tab-label'))[1]; + secondLabel.nativeElement.click(); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + let child = fixture.debugElement.query(By.css('.child')); + expect(child.nativeElement).toBeDefined(); + }); + }); + }); + /** * Checks that the `selectedIndex` has been updated; checks that the label and body have their * respective `active` classes @@ -616,3 +634,19 @@ class TabGroupWithSimpleApi { }) class NestedTabs {} +@Component({ + selector: 'template-tabs', + template: ` + + + Eager + + + +
Hi
+
+
+
+ `, + }) + class TemplateTabs {} diff --git a/src/lib/tabs/tab.ts b/src/lib/tabs/tab.ts index bf0e91fb9d95..c8d69c278716 100644 --- a/src/lib/tabs/tab.ts +++ b/src/lib/tabs/tab.ts @@ -24,6 +24,7 @@ import { import {CanDisable, mixinDisabled} from '@angular/material/core'; import {Subject} from 'rxjs/Subject'; import {MatTabLabel} from './tab-label'; +import {MatTabContent} from './tab-content'; // Boilerplate for applying mixins to MatTab. @@ -45,8 +46,13 @@ export class MatTab extends _MatTabMixinBase implements OnInit, CanDisable, OnCh /** Content for the tab label given by ``. */ @ContentChild(MatTabLabel) templateLabel: MatTabLabel; - /** Template inside the MatTab view that contains an ``. */ - @ViewChild(TemplateRef) _content: TemplateRef; + /** + * Template provided in the tab content that will be used if present, used to enable lazy-loading + */ + @ContentChild(MatTabContent, {read: TemplateRef}) _explicitContent: TemplateRef; + + /** Template inside the MatTab view that contains an . */ + @ViewChild(TemplateRef) _implicitContent: TemplateRef; /** The plain text label for the tab, used when there is no template label. */ @Input('label') textLabel: string = ''; @@ -102,6 +108,7 @@ export class MatTab extends _MatTabMixinBase implements OnInit, CanDisable, OnCh } ngOnInit(): void { - this._contentPortal = new TemplatePortal(this._content, this._viewContainerRef); + this._contentPortal = new TemplatePortal( + this._explicitContent || this._implicitContent, this._viewContainerRef); } } diff --git a/src/lib/tabs/tabs-module.ts b/src/lib/tabs/tabs-module.ts index 5defcedd2e58..58492d0ec4de 100644 --- a/src/lib/tabs/tabs-module.ts +++ b/src/lib/tabs/tabs-module.ts @@ -20,6 +20,7 @@ import {MatTabHeader} from './tab-header'; import {MatTabLabel} from './tab-label'; import {MatTabLabelWrapper} from './tab-label-wrapper'; import {MatTabLink, MatTabNav} from './tab-nav-bar/tab-nav-bar'; +import {MatTabContent} from './tab-content'; @NgModule({ @@ -39,6 +40,7 @@ import {MatTabLink, MatTabNav} from './tab-nav-bar/tab-nav-bar'; MatTab, MatTabNav, MatTabLink, + MatTabContent, ], declarations: [ MatTabGroup, @@ -50,7 +52,8 @@ import {MatTabLink, MatTabNav} from './tab-nav-bar/tab-nav-bar'; MatTabLink, MatTabBody, MatTabBodyPortal, - MatTabHeader + MatTabHeader, + MatTabContent, ], providers: [VIEWPORT_RULER_PROVIDER], }) diff --git a/src/lib/tabs/tabs.md b/src/lib/tabs/tabs.md index 4ff541e86bfa..0fc995b9ab49 100644 --- a/src/lib/tabs/tabs.md +++ b/src/lib/tabs/tabs.md @@ -80,6 +80,34 @@ The `tab-nav-bar` is not tied to any particular router; it works with normal `` can be placed anywhere in the view. +## Lazy Loading +By default, the tab contents are eagerly loaded. Eagerly loaded tabs +will initalize the child components but not inject them into the DOM +until the tab is activated. + + +If the tab contains several complex child components or the tab's contents +rely on DOM calculations during initialization, it is advised +to lazy load the tab's content. + +Tab contents can be lazy loaded by declaring the body in a `ng-template` +with the `matTabContent` attribute. + +```html + + + + The First Content + + + + + The Second Content + + + +``` + ### Accessibility Tabs without text or labels should be given a meaningful label via `aria-label` or `aria-labelledby`. For `MatTabNav`, the `