42

I am using the mat-table and I am trying to use the MatTableDataSource with an observable (I get the data from a web service), but I don't know how to configure the MatTableDataSource to use an observable instead of an array.

Is the only solution to this problem, to subscribe to the observable in the ngOnInit method and always create a new MatTableDataSource when new data arrives?

This is what I have until now, but I don't know if this is the correct solution for working with the MatTableDataSource with an observable.

dataSource: MatTableDataSource<Thing>;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;
    
ngOnInit() {
    getThings().subscribe(things => {
        this.dataSource = new MatTableDataSource(things);
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
    });
}
H3AR7B3A7
  • 4,366
  • 2
  • 14
  • 37
Christoph Hummler
  • 971
  • 2
  • 7
  • 20
  • 1
    I think this is correct solution as per Angular Material documentation. If you want to store data into dataSource then you have to use new MatTableDataSource() – Tushar Sep 03 '19 at 11:16
  • See my answer on this post https://stackoverflow.com/questions/54691541/how-to-enable-sorting-in-angular-material-data-table-when-the-datasource-is-obs/69329180#69329180 It is completely possible to use an observable with MatTableDataSource – Crash1hd Sep 25 '21 at 19:10

5 Answers5

48

You should be able to new up the MatTableDataSource once at the class level and then use the data setter in ngOnInit.

dataSource = new MatTableDataSource<Thing>();
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;

ngOnInit() {
    getThings().subscribe(things => {
        this.dataSource.data = things;
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
    });
}
Stuart Hallows
  • 8,795
  • 5
  • 45
  • 57
32

Use MatTableDataSource as Observable

You can just pipe your observable:

thingsAsMatTableDataSource$: Observable<MatTableDataSource<Thing>>  =
  getThings().pipe(
    map(things => {
      const dataSource = new MatTableDataSource<Thing>();
      dataSource.data = things;
      return dataSource;
}));

You can use an async pipe on your observable in the template:

[dataSource]="thingsAsMatTableDataSource$ | async"

This way you do not have to subscribe, and can still enjoy mat-table sorting etc...

Prevent Repeated Constructor Calls

Just instantiate it once as a private member and use that instead:

private dataSource = new MatTableDataSource<Thing>();

thingsAsMatTableDataSource$: Observable<MatTableDataSource<Thing>>  =
  getThings().pipe(
    map(things => {
      const dataSource = this.dataSource;
      dataSource.data = things
      return dataSource;
}));

Here's a simple example on Stackblitz.

H3AR7B3A7
  • 4,366
  • 2
  • 14
  • 37
  • 4
    This seems to be the tidiest, safest and most idiomatic approach, and I can confirm that it works. I'm surprised it has so few upvotes. – bighairdave May 17 '22 at 12:58
  • 6
    Thanks it work but can't use Observable> can use Observable Error as below Type 'MatTableDataSource | null' is not assignable to type 'CdkTableDataSourceInput'. – Puneet Sharma Sep 17 '22 at 08:38
  • 1
    I try to use this solution, But getting this error in the HTML file: Type 'MatTableDataSource | null' is not assignable to type 'CdkTableDataSourceInput'. You know why? – levi Nov 07 '22 at 14:16
  • 1
    Same problem here as @PuneetSharma and levi... updating this answer would be great – Lewis Cianci Nov 08 '22 at 04:24
  • You can either make sure you are always returning the expected object, by returning a default empty source, or just make it of type `MatTableDataSource | null`. But in the second case your template should be able to handle null values. – H3AR7B3A7 Nov 09 '22 at 20:15
  • I'm using this solution also with `paginator` and `sort` and everything works like a charm. But, I'd like to use `filter` (based on input value) but have no idea, how to 'merge' the filter string with the `dataSource.merge` property. – Frimlik Dec 20 '22 at 12:17
  • to get reed of this error, in you template make sure datasource is not null `
    – Shay May 18 '23 at 10:00
6

this is a workaround, because MatTableDataSource doesn't support Observable as data source

import { MatTableDataSource } from '@angular/material';
import { Observable, Subscription } from 'rxjs';
import { SomeInterface} from './some.interface';

export class CustomDataSource extends MatTableDataSource<SomeInterface> {

    private collection: SomeInterface[] = [];

    private collection$: Subscription;

    constructor(collection: Observable<SomeInterface[]>) {
        super();
        this.collection$ = collection.subscribe(data => {
           this.data = data; // here you have to adjust the behavior as needed
        });
    }

   disconnect() {
     this.collection$.unsubscribe();
     super.disconnect();
   }
}

then in component:

dataSource: CustomDataSource;

ngOnInit(): void {
  const observableData$ = getData();
  this.dataSource = new CustomDataSource(observableData$);
  // this.dataSource.sort = this.sort; // add sorting or filter
}

example: stackblitz

burasuk
  • 162
  • 2
  • 8
2

You can use an observable too, just (*)

[dataSource]="dataSource|async"

(*) really you needn't use the pipe async

See an example in stackblitz, where I replace the first example of the doc by

dataSource = of(ELEMENT_DATA).pipe(delay(1000));
Eliseo
  • 50,109
  • 4
  • 29
  • 67
2

I've released a library for that: @matheo/datasource

I explain the basic concepts in this article:
https://medium.com/@matheo/reactive-datasource-for-angular-1d869b0155f6

and in the example code, you can see how I fetch items from a Firebase database and manipulate the pagination. sorting and filters in the example repo
I hope that you like it and give me your opinion about it ;)

Mateo Tibaquira
  • 2,059
  • 22
  • 23