/**
* @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 {
ChangeDetectionStrategy,
Component,
Directive,
IterableChanges,
IterableDiffer,
IterableDiffers,
OnChanges,
OnDestroy,
SimpleChanges,
TemplateRef,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import {CanStick, CanStickCtor, mixinHasStickyInput} from './can-stick';
import {CdkCellDef, CdkColumnDef} from './cell';
/**
* The row template that can be used by the mat-table. Should not be used outside of the
* material library.
*/
export const CDK_ROW_TEMPLATE = ``;
/**
* Base class for the CdkHeaderRowDef and CdkRowDef that handles checking their columns inputs
* for changes and notifying the table.
*/
export abstract class BaseRowDef implements OnChanges {
/** The columns to be displayed on this row. */
columns: Iterable;
/** Differ used to check if any changes were made to the columns. */
protected _columnsDiffer: IterableDiffer;
constructor(
/** @docs-private */ public template: TemplateRef, protected _differs: IterableDiffers) {
}
ngOnChanges(changes: SimpleChanges): void {
// Create a new columns differ if one does not yet exist. Initialize it based on initial value
// of the columns property or an empty array if none is provided.
if (!this._columnsDiffer) {
const columns = (changes['columns'] && changes['columns'].currentValue) || [];
this._columnsDiffer = this._differs.find(columns).create();
this._columnsDiffer.diff(columns);
}
}
/**
* Returns the difference between the current columns and the columns from the last diff, or null
* if there is no difference.
*/
getColumnsDiff(): IterableChanges|null {
return this._columnsDiffer.diff(this.columns);
}
/** Gets this row def's relevant cell template from the provided column def. */
extractCellTemplate(column: CdkColumnDef): TemplateRef {
if (this instanceof CdkHeaderRowDef) {
return column.headerCell.template;
}
if (this instanceof CdkFooterRowDef) {
return column.footerCell.template;
} else {
return column.cell.template;
}
}
}
// Boilerplate for applying mixins to CdkHeaderRowDef.
/** @docs-private */
class CdkHeaderRowDefBase extends BaseRowDef {}
const _CdkHeaderRowDefBase: CanStickCtor&typeof CdkHeaderRowDefBase =
mixinHasStickyInput(CdkHeaderRowDefBase);
/**
* Header row definition for the CDK table.
* Captures the header row's template and other header properties such as the columns to display.
*/
@Directive({
selector: '[cdkHeaderRowDef]',
inputs: ['columns: cdkHeaderRowDef', 'sticky: cdkHeaderRowDefSticky'],
})
export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements CanStick, OnChanges {
constructor(template: TemplateRef, _differs: IterableDiffers) {
super(template, _differs);
}
// Prerender fails to recognize that ngOnChanges in a part of this class through inheritance.
// Explicitly define it so that the method is called as part of the Angular lifecycle.
ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
}
}
// Boilerplate for applying mixins to CdkFooterRowDef.
/** @docs-private */
class CdkFooterRowDefBase extends BaseRowDef {}
const _CdkFooterRowDefBase: CanStickCtor&typeof CdkFooterRowDefBase =
mixinHasStickyInput(CdkFooterRowDefBase);
/**
* Footer row definition for the CDK table.
* Captures the footer row's template and other footer properties such as the columns to display.
*/
@Directive({
selector: '[cdkFooterRowDef]',
inputs: ['columns: cdkFooterRowDef', 'sticky: cdkFooterRowDefSticky'],
})
export class CdkFooterRowDef extends _CdkFooterRowDefBase implements CanStick, OnChanges {
constructor(template: TemplateRef, _differs: IterableDiffers) {
super(template, _differs);
}
// Prerender fails to recognize that ngOnChanges in a part of this class through inheritance.
// Explicitly define it so that the method is called as part of the Angular lifecycle.
ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
}
}
/**
* Data row definition for the CDK table.
* Captures the header row's template and other row properties such as the columns to display and
* a when predicate that describes when this row should be used.
*/
@Directive({
selector: '[cdkRowDef]',
inputs: ['columns: cdkRowDefColumns', 'when: cdkRowDefWhen'],
})
export class CdkRowDef extends BaseRowDef {
/**
* Function that should return true if this row template should be used for the provided index
* and row data. If left undefined, this row will be considered the default row template to use
* when no other when functions return true for the data.
* For every row, there must be at least one when function that passes or an undefined to default.
*/
when: (index: number, rowData: T) => boolean;
// TODO(andrewseguin): Add an input for providing a switch function to determine
// if this template should be used.
constructor(template: TemplateRef, _differs: IterableDiffers) {
super(template, _differs);
}
}
/** Context provided to the row cells when `multiTemplateDataRows` is false */
export interface CdkCellOutletRowContext {
/** Data for the row that this cell is located within. */
$implicit?: T;
/** Index of the data object in the provided data array. */
index?: number;
/** Length of the number of total rows. */
count?: number;
/** True if this cell is contained in the first row. */
first?: boolean;
/** True if this cell is contained in the last row. */
last?: boolean;
/** True if this cell is contained in a row with an even-numbered index. */
even?: boolean;
/** True if this cell is contained in a row with an odd-numbered index. */
odd?: boolean;
}
/**
* Context provided to the row cells when `multiTemplateDataRows` is true. This context is the same
* as CdkCellOutletRowContext except that the single `index` value is replaced by `dataIndex` and
* `renderIndex`.
*/
export interface CdkCellOutletMultiRowContext {
/** Data for the row that this cell is located within. */
$implicit?: T;
/** Index of the data object in the provided data array. */
dataIndex?: number;
/** Index location of the rendered row that this cell is located within. */
renderIndex?: number;
/** Length of the number of total rows. */
count?: number;
/** True if this cell is contained in the first row. */
first?: boolean;
/** True if this cell is contained in the last row. */
last?: boolean;
/** True if this cell is contained in a row with an even-numbered index. */
even?: boolean;
/** True if this cell is contained in a row with an odd-numbered index. */
odd?: boolean;
}
/**
* Outlet for rendering cells inside of a row or header row.
* @docs-private
*/
@Directive({selector: '[cdkCellOutlet]'})
export class CdkCellOutlet implements OnDestroy {
/** The ordered list of cells to render within this outlet's view container */
cells: CdkCellDef[];
/** The data context to be provided to each cell */
context: any;
/**
* Static property containing the latest constructed instance of this class.
* Used by the CDK table when each CdkHeaderRow and CdkRow component is created using
* createEmbeddedView. After one of these components are created, this property will provide
* a handle to provide that component's cells and context. After init, the CdkCellOutlet will
* construct the cells with the provided context.
*/
static mostRecentCellOutlet: CdkCellOutlet|null = null;
constructor(public _viewContainer: ViewContainerRef) {
CdkCellOutlet.mostRecentCellOutlet = this;
}
ngOnDestroy() {
// If this was the last outlet being rendered in the view, remove the reference
// from the static property after it has been destroyed to avoid leaking memory.
if (CdkCellOutlet.mostRecentCellOutlet === this) {
CdkCellOutlet.mostRecentCellOutlet = null;
}
}
}
/** Header template container that contains the cell outlet. Adds the right class and role. */
@Component({
moduleId: module.id,
selector: 'cdk-header-row, tr[cdk-header-row]',
template: CDK_ROW_TEMPLATE,
host: {
'class': 'cdk-header-row',
'role': 'row',
},
// See note on CdkTable for explanation on why this uses the default change detection strategy.
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.None,
})
export class CdkHeaderRow {
}
/** Footer template container that contains the cell outlet. Adds the right class and role. */
@Component({
moduleId: module.id,
selector: 'cdk-footer-row, tr[cdk-footer-row]',
template: CDK_ROW_TEMPLATE,
host: {
'class': 'cdk-footer-row',
'role': 'row',
},
// See note on CdkTable for explanation on why this uses the default change detection strategy.
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.None,
})
export class CdkFooterRow {
}
/** Data row template container that contains the cell outlet. Adds the right class and role. */
@Component({
moduleId: module.id,
selector: 'cdk-row, tr[cdk-row]',
template: CDK_ROW_TEMPLATE,
host: {
'class': 'cdk-row',
'role': 'row',
},
// See note on CdkTable for explanation on why this uses the default change detection strategy.
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.None,
})
export class CdkRow {
}