Skip to content

Commit

Permalink
fix(material/tabs): enable hydration (#28366)
Browse files Browse the repository at this point in the history
Adds a workaround that allows us to enable hydration in the tab group component.
  • Loading branch information
crisbeto committed Jan 4, 2024
1 parent 4bae885 commit a7f87a8
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 7 deletions.
10 changes: 10 additions & 0 deletions src/material/tabs/tab-group.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@
}
</mat-tab-header>

<!--
We need to project the content somewhere to avoid hydration errors. Some observations:
1. This is only necessary on the server.
2. We get a hydration error if there aren't any nodes after the `ng-content`.
3. We get a hydration error if `ng-content` is wrapped in another element.
-->
@if (_isServer) {
<ng-content/>
}

<div
class="mat-mdc-tab-body-wrapper"
[class._mat-animation-noopable]="_animationMode === 'NoopAnimations'"
Expand Down
6 changes: 5 additions & 1 deletion src/material/tabs/tab-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ViewChild,
ViewEncapsulation,
booleanAttribute,
inject,
numberAttribute,
} from '@angular/core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
Expand All @@ -38,6 +39,7 @@ import {MatTabBody} from './tab-body';
import {CdkPortalOutlet} from '@angular/cdk/portal';
import {NgClass} from '@angular/common';
import {MatTabLabelWrapper} from './tab-label-wrapper';
import {Platform} from '@angular/cdk/platform';

/** Used to generate unique ID's for each tab component */
let nextId = 0;
Expand Down Expand Up @@ -75,7 +77,6 @@ const ENABLE_BACKGROUND_INPUT = true;
},
],
host: {
'ngSkipHydration': '',
'class': 'mat-mdc-tab-group',
'[class]': '"mat-" + (color || "primary")',
'[class.mat-mdc-tab-group-dynamic-height]': 'dynamicHeight',
Expand Down Expand Up @@ -248,6 +249,9 @@ export class MatTabGroup implements AfterContentInit, AfterContentChecked, OnDes

private _groupId: number;

/** Whether the tab group is rendered on the server. */
protected _isServer: boolean = !inject(Platform).isBrowser;

constructor(
readonly _elementRef: ElementRef,
private _changeDetectorRef: ChangeDetectorRef,
Expand Down
5 changes: 5 additions & 0 deletions src/material/tabs/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export const MAT_TAB_GROUP = new InjectionToken<any>('MAT_TAB_GROUP');
exportAs: 'matTab',
providers: [{provide: MAT_TAB, useExisting: MatTab}],
standalone: true,
host: {
// This element will be rendered on the server in order to support hydration.
// Hide it so it doesn't cause a layout shift when it's removed on the client.
'hidden': '',
},
})
export class MatTab implements OnInit, OnChanges, OnDestroy {
/** whether the tab is disabled. */
Expand Down
34 changes: 29 additions & 5 deletions src/universal-app/kitchen-sink/kitchen-sink.html
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,37 @@ <h2>Tabs</h2>
-->
<mat-tab-group [selectedIndex]="1">
<mat-tab label="Overview">
The overview
<h3>The overview</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Doloremque assumenda doloribus,
rerum temporibus fugit aliquid adipisci aliquam eaque sint voluptas dolore cumque voluptatibus
quam quod. Quasi adipisci officia similique in?</p>
<p>Deleniti neque placeat magnam, voluptatibus eligendi illo consectetur dolore minima dolorem
nemo suscipit dolorum accusantium? Numquam officia culpa itaque qui repudiandae nulla,
laboriosam nihil molestiae ad aut perferendis alias amet.</p>
<p>Officia esse temporibus consequatur ipsa! Veritatis alias facere amet reiciendis sint
impedit atque iste doloremque dolor? Ullam, aspernatur? Alias, fuga! At dolorum odio
molestiae laudantium nihil alias inventore veritatis voluptatum.</p>
<button mat-raised-button color="primary">See the overview</button>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
API docs
</ng-template>
The API docs
<ng-template mat-tab-label>API docs</ng-template>
<h3>The API docs</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum facere quasi natus rerum
quae, nisi, quis, voluptate assumenda necessitatibus labore illo. Illum ipsum consequatur,
excepturi aspernatur odio veritatis sint perferendis!</p>
<p>Dicta ex laborum repudiandae nesciunt. Ea asperiores quo totam velit! Aliquid cum laudantium
officiis molestias, excepturi odio, autem magni dignissimos perspiciatis, amet qui! Dolorem
molestiae similique necessitatibus cupiditate ipsa aspernatur?</p>
<button mat-raised-button color="accent">See the API docs</button>
</mat-tab>

<mat-tab>
<ng-template mat-tab-label>Examples</ng-template>
<h3>The examples</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Modi animi saepe, optio sequi
accusantium, eos perspiciatis reprehenderit, nobis exercitationem sunt ducimus molestiae
laborum inventore itaque incidunt. Neque dolorum adipisci quidem.</p>
<button mat-raised-button color="warn">See the examples</button>
</mat-tab>
</mat-tab-group>

Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/material/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export class MatTabGroup implements AfterContentInit, AfterContentChecked, OnDes
_getTabLabelId(i: number): string;
_handleClick(tab: MatTab, tabHeader: MatTabGroupBaseHeader, index: number): void;
headerPosition: MatTabHeaderPosition;
protected _isServer: boolean;
// (undocumented)
static ngAcceptInputType_contentTabIndex: unknown;
// (undocumented)
Expand Down Expand Up @@ -319,7 +320,7 @@ export class MatTabGroup implements AfterContentInit, AfterContentChecked, OnDes
_tabs: QueryList<MatTab>;
updatePagination(): void;
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<MatTabGroup, "mat-tab-group", ["matTabGroup"], { "color": { "alias": "color"; "required": false; }; "fitInkBarToContent": { "alias": "fitInkBarToContent"; "required": false; }; "stretchTabs": { "alias": "mat-stretch-tabs"; "required": false; }; "dynamicHeight": { "alias": "dynamicHeight"; "required": false; }; "selectedIndex": { "alias": "selectedIndex"; "required": false; }; "headerPosition": { "alias": "headerPosition"; "required": false; }; "animationDuration": { "alias": "animationDuration"; "required": false; }; "contentTabIndex": { "alias": "contentTabIndex"; "required": false; }; "disablePagination": { "alias": "disablePagination"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "preserveContent": { "alias": "preserveContent"; "required": false; }; "backgroundColor": { "alias": "backgroundColor"; "required": false; }; }, { "selectedIndexChange": "selectedIndexChange"; "focusChange": "focusChange"; "animationDone": "animationDone"; "selectedTabChange": "selectedTabChange"; }, ["_allTabs"], never, true, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MatTabGroup, "mat-tab-group", ["matTabGroup"], { "color": { "alias": "color"; "required": false; }; "fitInkBarToContent": { "alias": "fitInkBarToContent"; "required": false; }; "stretchTabs": { "alias": "mat-stretch-tabs"; "required": false; }; "dynamicHeight": { "alias": "dynamicHeight"; "required": false; }; "selectedIndex": { "alias": "selectedIndex"; "required": false; }; "headerPosition": { "alias": "headerPosition"; "required": false; }; "animationDuration": { "alias": "animationDuration"; "required": false; }; "contentTabIndex": { "alias": "contentTabIndex"; "required": false; }; "disablePagination": { "alias": "disablePagination"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "preserveContent": { "alias": "preserveContent"; "required": false; }; "backgroundColor": { "alias": "backgroundColor"; "required": false; }; }, { "selectedIndexChange": "selectedIndexChange"; "focusChange": "focusChange"; "animationDone": "animationDone"; "selectedTabChange": "selectedTabChange"; }, ["_allTabs"], ["*"], true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatTabGroup, [null, null, { optional: true; }, { optional: true; }]>;
}
Expand Down

0 comments on commit a7f87a8

Please sign in to comment.