Angular Material Table own datasource with filter

2019-03-01 00:11发布

With the help of this tutorial I wrote my own datasource for the angular material table. I get the list of data from an api and display it in a material table.

Code MyDataSource

export class MyDataSource extends DataSource<any> {

  private users: Observable<User[]>;

  constructor(private userService: UserService) {
    super();
  }

  connect(): Observable<User[]> {
    this.users = this.userService.getAll();
    return this.users;
  }

  disconnect() { }

  filterWithCriteria(filterData: any): any {
    return this.users
      .filter(element => element[filterData.criteria].toLocaleLowerCase().includes(filterData.searchTerm.toLocaleLowerCase()))
      .map(element => element)
      .subscribe(
        element => console.log(element),
        err => console.error(err),
        () => console.log('Streaming is over')
      );
   }
}

The api delivers an observable with user objects.

Code User

export class User implements models.User {
  constructor(
    public name?: string,
    public adress?: string,
    public email?: string,
    public telefon?: string,
    public birthday?: Date
  ) {}
}

The objective is to search in the list of the user with a criteria. The criteria can be name, adress, ...

.filter(element => element[filterData.criteria].toLocaleLowerCase().includes(filterData.searchTerm.toLocaleLowerCase()))

This line gives me the error the toLocaleLowerCase is not defined in User and

 console.log(this.users[filterData.criteria]);

gives me 'undefined'.

Thanks for the help,

Best Regards

2条回答
一夜七次
2楼-- · 2019-03-01 00:53

@Pierre Mallet your filter solution works, but I don't really know where to call

this.filter$.next({criteria: <theCriteria>, searchTerm: <theTerm>});

and I did a little change because of the angular material select.

My actual code

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import 'rxjs/add/observable/combineLatest';
import { DataSource } from '@angular/cdk/collections';
import { UserService } from '../shared/service/user.service';
import { User } from '../shared/user';
import { TransponderFormComponent } from '../transponder-form/transponder-form.component';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Component({
  selector: 'app-transponder-list',
  templateUrl: './transponder-list.component.html',
  styleUrls: ['./transponder-list.component.scss']
})

export class TransponderListComponent implements OnInit {

  public users: User[];
  public displayedColumns = ['name', 'adress', 'city', 'email', 'telephon', 'birthday'];
  public dataSource;
  public selectedCriteria: String = '';

  public searchCriteria = [
    { value: 'name', view: 'Name' },
    { value: 'city', view: 'City' },
    { value: 'adress', view: 'Adress' },
    { value: 'email', view: 'Email' },
    { value: 'telephon', view: 'Telephon' },
    { value: 'birthday', view: 'Birthday' }
  ];

  constructor(private userService: UserService) { }

  ngOnInit() {
    this.dataSource = new MyDataSource(this.userService);
  }

  // get data from material select
  public setSearchCriteria() { }

  public filter(data: any) {
    this.dataSource.filter(data.searchTerm, this.selectedColumn);
  }
}

export class MyDataSource extends DataSource<any> {

  private users: Observable<User[]>;
  private filter$ = new BehaviorSubject({ criteria: null, searchTerm: null });

  constructor(private userService: UserService) {
    super();
  }

  connect(): Observable<User[]> {
    this.users = this.userService.getAll();
    return Observable.combineLatest(this.userService.getAll(), this.filter$)
      .map(latestValues => {
         const [users, filterData] = latestValues; // change
         if (!filterData.criteria || !filterData.searchTerm) {
           return users;
         }
         return users.filter(user => {
           return user[filterData.criteria].toLocaleLowerCase().includes(filterData.searchTerm.toLocaleLowerCase());
         });
       });
  }

  disconnect() { }

  filter(searchTerm: String, criteria: String) {
    if (searchTerm !== '') {
      // there is a tern to search
      if ((criteria !== undefined) && (criteria !== '')) {
        console.log('Search with term and criteria');
        this.filterWithCriteria(searchTerm, criteria);
      }
    }
  }

  filterWithCriteria(searchTerm: String, criteria: any): any {
    return this.users
      .map((users: User[]) => {
        return users.filter(user => {
          return user[criteria].toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase());
        });
      })
      .subscribe(
        users => console.log(users),
        err => console.error(err),
        () => console.log('Streaming is over')
      );
  }
}

Thanks for your help.

Best regards.

查看更多
做自己的国王
3楼-- · 2019-03-01 01:13

There are (I think) two mistakes in your example :

1/ it seems your userService.getAll() return an Observable to an observable wich emit an array of users.

So when you do return this.users.filter(element ... element is an array of User, not a User

then the good filtering chain should be

filterWithCriteria(filterData: any): any {
    return this.users
      .map((users: User[] => {
          return users.filter(user => {
              return user[filterData.criteria].toLocaleLowerCase().includes(filterData.searchTerm.toLocaleLowerCase());
          });  
      })
      .subscribe(
        users => console.log(users),
        err => console.error(err),
        () => console.log('Streaming is over')
      );
   }

2/ The connect of your DataSource should be an Observable emitting the Users you want to display. So the connect method need to return an Observable which will emit new data when the filter is changed (or list of Users is changed). We lack information on how you filter is implemented but here is an example of implementation

export class MyDataSource extends DataSource<User[]> {

        private users: Observable<User[]>;
        private filter$ = new BehaviorSubject({criteria: null, searchTerm: null});
        /*
            Every time your filter change you have to call
            this.filter$.next({criteria: <theCriteria>, searchTerm: <theTerm>});
        */

        constructor(private userService: UserService) {
            super();
        }

        connect(): Observable<User[]> {
            return Observable.combineLatest(this.userService.getAll(), this.filter$)
                .map(latestValues => {
                    // every time filter$ or the observable from userService.getAll() emit a new data
                    const [users, filterData] = latestValues;
                    if (!filterData.criteria || !filterData.searchTerm) return users;

                    return users.filter(user => {
                        return user[filterData.criteria].toLocaleLowerCase().includes(filterData.searchTerm.toLocaleLowerCase());
                    });
                });
        }

        disconnect() {}
    }

Note: the best way to handle this could be to have your filter$ Subject in your wrapping component, and pass it in the constructor of your DataSource.

Hope it helps !

查看更多
登录 后发表回答