27

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;
        });
}
Tushar
  • 1,602
  • 1
  • 15
  • 29
Christoph Hummler
  • 681
  • 2
  • 6
  • 15
  • 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

38

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,347
  • 5
  • 41
  • 54
  • 2
    Please note that it's quite a bit slower to set the sort after setting data: https://stackoverflow.com/a/51296374/1596352 (in my own app, it hasn't been quite as extreme as in the link, but still made a noticeable difference. – DennisK Sep 15 '20 at 08:54
  • 1
    Yes, i suggest to put the subscription under the `ngAfterViewInit` function. :D – Kristian Mark Surat Feb 08 '21 at 08:31
  • 2
    What if I don't want to subscribe to it in the component and use async in the template to get the data? – Jeremy Scott Peters Feb 16 '21 at 00:54
  • setting paginator and sort MUST be done in ngAfterViewInit – JiBi Mar 10 '21 at 11:04
5

To 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;
}));

As mentioned above you can use an async pipe on your observable in the template as datasource:

[dataSource]="thingsAsMatTableDataSource$ | async"

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

H3AR7B3A7
  • 2,383
  • 1
  • 9
  • 23
  • 2
    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
4

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
  • 91
  • 5
1

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
  • 39,135
  • 4
  • 22
  • 51
1

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
  • 1,863
  • 19
  • 22