diff --git a/src/cdk/collections/data-source.ts b/src/cdk/collections/data-source.ts index f0b68d66a77..d531bb67970 100644 --- a/src/cdk/collections/data-source.ts +++ b/src/cdk/collections/data-source.ts @@ -29,3 +29,11 @@ export abstract class DataSource { */ abstract disconnect(collectionViewer: CollectionViewer): void; } + +/** Checks whether an object is a data source. */ +export function isDataSource(value: any): value is DataSource { + // Check if the value is a DataSource by observing if it has a connect function. Cannot + // be checked as an `instanceof DataSource` since people could create their own sources + // that match the interface, but don't extend DataSource. + return value && typeof value.connect === 'function'; +} diff --git a/src/cdk/scrolling/virtual-for-of.ts b/src/cdk/scrolling/virtual-for-of.ts index 82c00a8bb3a..cfefe041d19 100644 --- a/src/cdk/scrolling/virtual-for-of.ts +++ b/src/cdk/scrolling/virtual-for-of.ts @@ -6,7 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {ArrayDataSource, CollectionViewer, DataSource, ListRange} from '@angular/cdk/collections'; +import { + ArrayDataSource, + CollectionViewer, + DataSource, + ListRange, + isDataSource, +} from '@angular/cdk/collections'; import { Directive, DoCheck, @@ -82,7 +88,7 @@ export class CdkVirtualForOf implements CollectionViewer, DoCheck, OnDestroy } set cdkVirtualForOf(value: DataSource | Observable | NgIterable) { this._cdkVirtualForOf = value; - const ds = value instanceof DataSource ? value : + const ds = isDataSource(value) ? value : // Slice the value if its an NgIterable to ensure we're working with an array. new ArrayDataSource( value instanceof Observable ? value : Array.prototype.slice.call(value || [])); diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index 4919071119b..75e773844a1 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {CollectionViewer, DataSource} from '@angular/cdk/collections'; +import {CollectionViewer, DataSource, isDataSource} from '@angular/cdk/collections'; import { AfterContentChecked, Attribute, @@ -449,7 +449,7 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes this._onDestroy.next(); this._onDestroy.complete(); - if (this.dataSource instanceof DataSource) { + if (isDataSource(this.dataSource)) { this.dataSource.disconnect(this); } } @@ -769,7 +769,7 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes private _switchDataSource(dataSource: CdkTableDataSourceInput) { this._data = []; - if (this.dataSource instanceof DataSource) { + if (isDataSource(this.dataSource)) { this.dataSource.disconnect(this); } @@ -796,12 +796,8 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes let dataStream: Observable> | undefined; - // Check if the datasource is a DataSource object by observing if it has a connect function. - // Cannot check this.dataSource['connect'] due to potential property renaming, nor can it - // checked as an instanceof DataSource since the table should allow for data sources - // that did not explicitly extend DataSource. - if ((this.dataSource as DataSource).connect instanceof Function) { - dataStream = (this.dataSource as DataSource).connect(this); + if (isDataSource(this.dataSource)) { + dataStream = this.dataSource.connect(this); } else if (this.dataSource instanceof Observable) { dataStream = this.dataSource; } else if (Array.isArray(this.dataSource)) { diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 002d583ea56..77f2339170f 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {FocusableOption} from '@angular/cdk/a11y'; -import {CollectionViewer, DataSource} from '@angular/cdk/collections'; +import {CollectionViewer, DataSource, isDataSource} from '@angular/cdk/collections'; import { AfterContentChecked, ChangeDetectionStrategy, @@ -185,10 +185,8 @@ export class CdkTree private _observeRenderChanges() { let dataStream: Observable> | undefined; - // Cannot use `instanceof DataSource` since the data source could be a literal with - // `connect` function and may not extends DataSource. - if (typeof (this._dataSource as DataSource).connect === 'function') { - dataStream = (this._dataSource as DataSource).connect(this); + if (isDataSource(this._dataSource)) { + dataStream = this._dataSource.connect(this); } else if (this._dataSource instanceof Observable) { dataStream = this._dataSource; } else if (Array.isArray(this._dataSource)) { diff --git a/tools/public_api_guard/cdk/collections.d.ts b/tools/public_api_guard/cdk/collections.d.ts index d0e35acad26..b5c6d6bd22a 100644 --- a/tools/public_api_guard/cdk/collections.d.ts +++ b/tools/public_api_guard/cdk/collections.d.ts @@ -15,6 +15,8 @@ export declare abstract class DataSource { export declare function getMultipleValuesInSingleSelectionError(): Error; +export declare function isDataSource(value: any): value is DataSource; + export declare type ListRange = { start: number; end: number;