license import { _isNumberValue } from '@angular/cdk/coercion' ; import { DataSource } from '@angular/cdk/table' ; import { BehaviorSubject , combineLatest , merge , Observable , of as observableOf , Subscription } from 'rxjs' ; import { MatPaginator , PageEvent } from '@angular/material/paginator' ; import { MatSort , Sort } from '@angular/material/sort' ; import { map } from 'rxjs/operators' ; /**
 * Corresponds to `Number.MAX_SAFE_INTEGER`. Moved out into a variable here due to
 * flaky browser support and the value not being defined in Closure's typings.
 */ const MAX_SAFE_INTEGER = 9007199254740991 ; /**
 * Data source that accepts a client-side data array and includes native support of filtering,
 * sorting (using MatSort), and pagination (using MatPaginator).
 *
 * Allows for sort customization by overriding sortingDataAccessor, which defines how data
 * properties are accessed. Also allows for filter customization by overriding filterTermAccessor,
 * which defines how row data is converted to a string for filter matching.
 */ export class MatTableDataSource < T > extends DataSource < T > { /** Stream that emits when a new data array is set on the data source. */ private readonly _data : BehaviorSubject < T [ ] > ; /** Stream emitting render data to the table (depends on ordered data changes). */ private readonly _renderData = new BehaviorSubject < T [ ] > ( [ ] ) ; /** Stream that emits when a new filter string is set on the data source. */ private readonly _filter = new BehaviorSubject < string > ( '' ) ; /**
     * Subscription to the changes that should trigger an update to the table's rendered rows, such
     * as filtering, sorting, pagination, or base data changes.
     */ _renderChangesSubscription = Subscription . EMPTY ; /**
     * The filtered set of data that has been matched by the filter string, or all the data if there
     * is no filter. Useful for knowing the set of data the table represents.
     * For example, a 'selectAll()' function would likely want to select the set of filtered data
     * shown to the user rather than all the data.
     */ filteredData : T [ ] ; /** Array of data that should be rendered by the table, where each object represents one row. */ get data ( ) { return this . _data . value ; } set data ( data ) { this . _data . next ( data ) ; } /**
     * Filter term that should be used to filter out objects from the data array. To override how
     * data objects match to this filter string, provide a custom function for filterPredicate.
     */ get filter ( ) : string { return this . _filter . value ; } set filter ( filter ) { this . _filter . next ( filter ) ; } /**
     * Instance of the MatSort directive used by the table to control its sorting. Sort changes
     * emitted by the MatSort will trigger an update to the table's rendered data.
     */ get sort ( ) : MatSort | null { return this . _sort ; } set sort ( sort ) { this . _sort = sort ; this . _updateChangeSubscription ( ) ; } private _sort : MatSort | null ; /**
     * Instance of the MatPaginator component used by the table to control what page of the data is
     * displayed. Page changes emitted by the MatPaginator will trigger an update to the
     * table's rendered data.
     *
     * Note that the data source uses the paginator's properties to calculate which page of data
     * should be displayed. If the paginator receives its properties as template inputs,
     * e.g. `[pageLength]=100` or `[pageIndex]=1`, then be sure that the paginator's view has been
     * initialized before assigning it to this data source.
     */ get paginator ( ) : MatPaginator | null { return this . _paginator ; } set paginator ( paginator ) { this . _paginator = paginator ; this . _updateChangeSubscription ( ) ; } private _paginator : MatPaginator | null ; param data param sortHeaderId sortingDataAccessor : ( ( data : T , sortHeaderId : string ) => string | number ) = ( data , sortHeaderId ) : string | number => { const value = ( data as { [ key ] : any ; } ) [ sortHeaderId ] ; if ( _isNumberValue ( value ) ) { const numberValue = Number ( value ) ; return numberValue < MAX_SAFE_INTEGER ? numberValue : value ; } return value ; } ; param data param sort sortData : ( ( data : T [ ] , sort : MatSort ) => T [ ] ) = ( data , sort ) : T [ ] => { const active = sort . active ; const direction = sort . direction ; if ( ! active || direction == '' ) { return data ; } return data . sort ( ( a , b ) => { let valueA = this . sortingDataAccessor ( a , active ) ; let valueB = this . sortingDataAccessor ( b , active ) ; let comparatorResult = 0 ; if ( valueA != null && valueB != null ) { if ( valueA > valueB ) { comparatorResult = 1 ; } else if ( valueA < valueB ) { comparatorResult = - 1 ; } } else if ( valueA != null ) { comparatorResult = 1 ; } else if ( valueB != null ) { comparatorResult = - 1 ; } return comparatorResult * ( direction == 'asc' ? 1 : - 1 ) ; } ) ; } ; param data param filter returns filterPredicate : ( ( data : T , filter : string ) => boolean ) = ( data , filter ) : boolean => { const dataStr = Object . keys ( data ) . reduce ( ( currentTerm , key ) => { return currentTerm + ( data as { [ key ] : any ; } ) [ key ] + '◬' ; } , '' ) . toLowerCase ( ) ; const transformedFilter = filter . trim ( ) . toLowerCase ( ) ; return dataStr . indexOf ( transformedFilter ) != - 1 ; } ; constructor ( initialData = [ ] ) { super ( ) ; this . _data = new BehaviorSubject < T [ ] > ( initialData ) ; this . _updateChangeSubscription ( ) ; } /**
     * Subscribe to changes that should trigger an update to the table's rendered rows. When the
     * changes occur, process the current state of the filter, sort, and pagination along with
     * the provided base data and send it to the table for rendering.
     */ _updateChangeSubscription ( ) { const sortChange = this . _sort ? merge < Sort | void > ( this . _sort . sortChange , this . _sort . initialized ) : observableOf ( null ) ; const pageChange = this . _paginator ? merge < PageEvent | void > ( this . _paginator . page , this . _paginator . initialized ) : observableOf ( null ) ; const dataStream = this . _data ; const filteredData = combineLatest ( dataStream , this . _filter ) . pipe ( map ( ( [ data ] ) => this . _filterData ( data ) ) ) ; const orderedData = combineLatest ( filteredData , sortChange ) . pipe ( map ( ( [ data ] ) => this . _orderData ( data ) ) ) ; const paginatedData = combineLatest ( orderedData , pageChange ) . pipe ( map ( ( [ data ] ) => this . _pageData ( data ) ) ) ; this . _renderChangesSubscription . unsubscribe ( ) ; this . _renderChangesSubscription = paginatedData . subscribe ( data => this . _renderData . next ( data ) ) ; } /**
     * Returns a filtered data array where each filter object contains the filter string within
     * the result of the filterTermAccessor function. If no filter is set, returns the data array
     * as provided.
     */ _filterData ( data ) { this . filteredData = ! this . filter ? data : data . filter ( obj => this . filterPredicate ( obj , this . filter ) ) ; if ( this . paginator ) { this . _updatePaginator ( this . filteredData . length ) ; } return this . filteredData ; } /**
     * Returns a sorted copy of the data if MatSort has a sort applied, otherwise just returns the
     * data array as provided. Uses the default data accessor for data lookup, unless a
     * sortDataAccessor function is defined.
     */ _orderData ( data ) { if ( ! this . sort ) { return data ; } return this . sortData ( data . slice ( ) , this . sort ) ; } /**
     * Returns a paged splice of the provided data array according to the provided MatPaginator's page
     * index and length. If there is no paginator provided, returns the data array as provided.
     */ _pageData ( data ) { if ( ! this . paginator ) { return data ; } const startIndex = this . paginator . pageIndex * this . paginator . pageSize ; return data . slice ( ) . splice ( startIndex , this . paginator . pageSize ) ; } /**
     * Updates the paginator to reflect the length of the filtered data, and makes sure that the page
     * index does not exceed the paginator's last page. Values are changed in a resolved promise to
     * guard against making property changes within a round of change detection.
     */ _updatePaginator ( filteredDataLength ) { Promise . resolve ( ) . then ( ( ) => { if ( ! this . paginator ) { return ; } this . paginator . length = filteredDataLength ; if ( this . paginator . pageIndex > 0 ) { const lastPageIndex = Math . ceil ( this . paginator . length / this . paginator . pageSize ) - 1 || 0 ; this . paginator . pageIndex = Math . min ( this . paginator . pageIndex , lastPageIndex ) ; } } ) ; } docs-private connect ( ) { return this . _renderData ; } docs-private disconnect ( ) { } }