How to use the MatTableDataSource with an observable?
Asked Answered
U

6

43

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;
    });
}
Unsay answered 3/9, 2019 at 11:6 Comment(2)
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()Watersoak
See my answer on this post #54692041 It is completely possible to use an observable with MatTableDataSourceOntogeny
R
51

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;
    });
}
Radiotelephone answered 11/2, 2020 at 9:59 Comment(5)
Please note that it's quite a bit slower to set the sort after setting data: https://mcmap.net/q/366243/-angular-6-mattable-performance-in-1000-rows (in my own app, it hasn't been quite as extreme as in the link, but still made a noticeable difference.Phillada
Yes, i suggest to put the subscription under the ngAfterViewInit function. :DConfined
What if I don't want to subscribe to it in the component and use async in the template to get the data?Merger
setting paginator and sort MUST be done in ngAfterViewInitPropman
How's that the answer to a question on using Observable!?Misquote
F
40

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.

Fidelafidelas answered 6/1, 2022 at 11:20 Comment(7)
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.Cutcheon
Thanks it work but can't use Observable<MatTableDataSource<Thing>> can use Observable<any> Error as below Type 'MatTableDataSource<Service> | null' is not assignable to type 'CdkTableDataSourceInput<Service>'.Genteel
I try to use this solution, But getting this error in the HTML file: Type 'MatTableDataSource<Thing> | null' is not assignable to type 'CdkTableDataSourceInput<Thing>'. You know why?Willdon
Same problem here as @PuneetSharma and levi... updating this answer would be greatDidactic
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<Thing> | null. But in the second case your template should be able to handle null values.Fidelafidelas
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.Doit
to get reed of this error, in you template make sure datasource is not null <table *ngIf="thingsAsMatTableDataSource$ | async as DS" mat-table [dataSource]="DS" ...Malchy
D
7

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

Douglassdougy answered 10/3, 2021 at 10:44 Comment(2)
can you add some explanations to your code ?Dulaney
i made some changes in my post and i added example on stackblitzDouglassdougy
N
3

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));
Nonmetal answered 3/9, 2019 at 11:24 Comment(2)
But then it is not that easy to use the sorting and pagination features.Unsay
sorry, you're ok, dataSource can be, a MatTableDataSource, and array or an observable, but you only can sort it if is a MatTableDataSource;Nonmetal
S
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 ;)

Stalag answered 10/9, 2019 at 17:2 Comment(1)
Your example at Stackblitz doesn't work anymore.Doit
T
0

<mat-table [dataSource]="(things$ | async) ?? []">

  things$: Observable<MatTableDataSource<Thing>> =
    this.thingService.getThings().pipe(map(things => new MatTableDataSource<Thing>(things))); ``` 

with this approach async pipe and MatTableDataSource works without error. Sort and pagination can be used afterwards.
Torietorii answered 7/9, 2023 at 11:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.