feat(tabs): add ability to lazy load tab content (#8921)

* feat(tabs): add ability to lazy load tab content

* fix: lint

* chore: nit

* chore: pr feedback

* fix: presubmit error
This commit is contained in:
Austin
2018-02-08 15:44:06 -06:00
committed by mmalerba
parent 6a8ce02775
commit 6feaf62585
10 changed files with 137 additions and 7 deletions
+4 -1
View File
@@ -52,7 +52,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';
@@ -87,6 +89,7 @@ import {TableDemoModule} from '../table/table-demo-module';
ExpansionDemo,
FocusOriginDemo,
FoggyTabContent,
Counter,
GesturesDemo,
GridListDemo,
Home,
+17
View File
@@ -293,3 +293,20 @@
</div>
</mat-tab>
</mat-tab-group>
<h1>Lazy Loaded Tabs</h1>
<mat-tab-group>
<mat-tab label="First">
<ng-template matTabContent>
<counter></counter>
</ng-template>
</mat-tab>
<mat-tab label="Second">
<ng-template matTabContent>
<counter></counter>
</ng-template>
</mat-tab>
<mat-tab label="Third">
<counter></counter>
</mat-tab>
</mat-tab-group>
+11
View File
@@ -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: `<span>Content</span>`
})
export class Counter {
ngOnInit() {
console.log('Tab Loaded');
}
}
+1
View File
@@ -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';
+13 -2
View File
@@ -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<void> = new EventEmitter<void>(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;
+15
View File
@@ -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<any>) { }
}
+34
View File
@@ -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: `
<mat-tab-group>
<mat-tab label="One">
Eager
</mat-tab>
<mat-tab label="Two">
<ng-template matTabContent>
<div class="child">Hi</div>
</ng-template>
</mat-tab>
</mat-tab-group>
`,
})
class TemplateTabs {}
+10 -3
View File
@@ -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 `<ng-template mat-tab-label>`. */
@ContentChild(MatTabLabel) templateLabel: MatTabLabel;
/** Template inside the MatTab view that contains an `<ng-content>`. */
@ViewChild(TemplateRef) _content: TemplateRef<any>;
/**
* Template provided in the tab content that will be used if present, used to enable lazy-loading
*/
@ContentChild(MatTabContent, {read: TemplateRef}) _explicitContent: TemplateRef<any>;
/** Template inside the MatTab view that contains an <ng-content>. */
@ViewChild(TemplateRef) _implicitContent: TemplateRef<any>;
/** 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);
}
}
+4 -1
View File
@@ -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],
})
+28
View File
@@ -80,6 +80,34 @@ The `tab-nav-bar` is not tied to any particular router; it works with normal `<a
the `active` property to determine which tab is currently active. The corresponding
`<router-outlet>` 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
<mat-tab-group>
<mat-tab label="First">
<ng-template matTabContent>
The First Content
</ng-template>
</mat-tab>
<mat-tab label="Second">
<ng-template matTabContent>
The Second Content
</ng-template>
</mat-tab>
</mat-tab-group>
```
### Accessibility
Tabs without text or labels should be given a meaningful label via `aria-label` or
`aria-labelledby`. For `MatTabNav`, the `<nav>` element should have a label as well.