Fill material table with data from API using angular DataSource

Viewed 3486

I have an api which send data from backend, so when I try to get this data in my angular the data is not loaded in the first time, and my table stay empty, but if for example print the data that come from banckend before setting it in my DataSource, my table is fill correctly.

My code is:

user-datasource.ts

export class UserListDataSource extends DataSource<User> {
  data: User[];
  paginator: MatPaginator;
  sort: MatSort;

  constructor(private usersService: UsersService) {
    super();
  }

  connect(): Observable<User[]> {
    this.data = this.usersService.usersList();
    //this.data.forEach(u => console.log(u.id)); <<---------- When I log this, My data is fill corectly
    const dataMutations = [
      observableOf(this.data),
      this.paginator.page,
      this.sort.sortChange
    ];

    return merge(...dataMutations).pipe(map(() => {
      return this.getPagedData(this.getSortedData([...this.data]));
    }));
  }
  ...

}

userService

export class UsersService {

  users: User[];

  constructor(private http: HttpClient) {}

  usersList() {    
    this.http.get(`${environment.apiUrl}/users`).pipe(
      map((jsonArray: Object[]) =>  jsonArray.map(jsonItem => User.fromJson(jsonItem)))
    ).subscribe((users: User[]) => { this.users = users; });

    return this.users;
  }

}

component

export class UserListComponent implements AfterViewInit, OnInit {
  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatTable) table: MatTable<User>;

  dataSource: UserListDataSource = new UserListDataSource(this.usersService);

  displayedColumns = ['id', 'email', 'username'];

  constructor(private usersService: UsersService) { }

  ngOnInit() {}

  ngAfterViewInit() {
    this.dataSource.sort = this.sort;
    this.dataSource.paginator = this.paginator;
    this.table.dataSource = this.dataSource;
  }
}

I'm not sure if I use the correct way to fill my table, or I miss something.

From what I see, I think the backend took a long time than the frontend to load data, in another word, frontend is not waiting the necessary time to get data from backend.

Is there any way to fix this please?


Note: I use angular material schematics to create all this, in the default code, it use a static data, which is not the solution for me, which I need to use dynamic data.

2 Answers

You should await for the server response before you send the users collections back.

UsersService

usersList(): Observable<User[]> {    
    return this.http.get(`${environment.apiUrl}/users`).pipe(
      map((jsonArray: Object[]) =>  jsonArray.map(jsonItem => User.fromJson(jsonItem)))
    );
}

UserService just returns the promise of the user's collection.
In your UserListComponent, create a MatTableDataSource of type User and await the data from the your service. Then, once it is fulfilled, add that data to the datasource.

export class UserListComponent implements OnInit, AfterViewInit {

  private users: User[] = [];
  dataSource = new MatTableDataSource<User>();
  columns: string[] = ['...'];

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  constructor(private service: UsersService) {}

  ngOnInit(): void {
    this.service.usersList().subscribe(users => {
      this.users = users;
      this.dataSource.data = this.users;
    });
  }

  ngAfterViewInit(): void {
    this.dataSource.sort = this.sort;
    this.dataSource.paginator = this.paginator;
  }
}

And, the last point. Use the dataSource input from the table tag to bind your MatTableDataSource to the table.

<table [dataSource]="dataSource">
  <!-- ... -->
</table>

I'm not quite sure what exactly the problem is but I recently wrote a couple tables with angular material. Here is the simple way that I used to connect with a table.

export class UserDataSource extends DataSource<any> {

constructor (
    private userSettingsService: UserSettingsService) 
{
    super();
}

  public usersDataStream = new BehaviorSubject<IUser[]>([]);

  connect(): Observable<IUser[]> {
    return this.usersDataStream.asObservable();
  }
  disconnect() {
    this.usersDataStream.complete();
  }
}

And then in the component class:

public dataSource: UserDataSource;

ngOnInit(): void {
this.dataSource = new UserDataSource(this._userSettingsService);
}

and then the HTML template:

<mat-table [dataSource]="dataSource">
...
</mat-table>
Related