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 `