mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>) { }
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user