代码之家  ›  专栏  ›  技术社区  ›  Denis Barzanov

角度材质表TypeError:无法读取未定义的属性“cars”

  •  1
  • Denis Barzanov  · 技术社区  · 6 年前

    我有问题的角度材料表( Angular Material Table

    我跑了 ng generate @angular/material:material-table --name=car-table 生成默认的角度表,效果很好。 但是如果我尝试将数据(汽车)注入到 CarsTableDataSource 它停止工作了。它必须与异步函数和 ngOnInit 生命周期挂钩。

    StackBlitz . 关键在于 src/app/cars/ 文件夹。

    汽车.component.ts

    import {Component, OnInit, ViewChild} from '@angular/core';
    import {Car} from '../car';
    import {CarService} from '../car.service';
    import {MatPaginator, MatSort, MatTable} from '@angular/material';
    import {CarsTableDataSource} from './cars-table-datasource';
    
    @Component({
      selector: 'app-cars',
      templateUrl: './cars.component.html',
      styleUrls: ['./cars.component.css']
    })
    export class CarsComponent implements OnInit {
      cars: Car[];
    
      @ViewChild(MatPaginator) paginator: MatPaginator;
      @ViewChild(MatSort) sort: MatSort;
      @ViewChild(MatTable) table: MatTable<Car>;
      dataSource: CarsTableDataSource;
    
      /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
      displayedColumns = ['id', 'name', 'img_url'];
    
      constructor(private carService: CarService) {
      }
    
      async ngOnInit() {
        console.log('before getting cars: ');
        console.log(this.cars);
        this.cars = await this.carService.getCars().toPromise();
        console.log('got cars:');
        console.log(this.cars);
        this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
      }
    
      add(name: string) {
        name = name.trim();
        if (!name) {
          return;
        }
        this.carService.addCar({name} as Car)
          .subscribe(car => {
            this.cars = [...this.cars, car];
            console.log(this.cars);
            console.log('rendering rows');
            this.table.renderRows();
          });
      }
    
      delete(car: Car) {
        this.cars = this.cars.filter(c => c !== car);
        this.carService.deleteCar(car).subscribe();
        this.table.renderRows();
      }
    }
    

    cars表-数据源.ts

    import {DataSource} from '@angular/cdk/collections';
    import {MatPaginator, MatSort} from '@angular/material';
    import {map} from 'rxjs/operators';
    import {merge, Observable, of as observableOf} from 'rxjs';
    import {Car} from '../car';
    
    /**
     * Data source for the CarsTable view. This class should
     * encapsulate all logic for fetching and manipulating the displayed cars
     * (including sorting, pagination, and filtering).
     */
    export class CarsTableDataSource extends DataSource<CarsTableItem> {
      // cars: CarsTableItem[];
    
      constructor(private paginator: MatPaginator, private sort: MatSort, public cars: Car[]) {
        super();
      }
    
      /**
       * Connect this cars source to the table. The table will only update when
       * the returned stream emits new items.
       * @returns A stream of the items to be rendered.
       */
      connect(): Observable<CarsTableItem[]> {
        // Combine everything that affects the rendered cars into one update
        // stream for the cars-table to consume.
        const dataMutations = [
          observableOf(this.cars),
          this.paginator.page,
          this.sort.sortChange
        ];
    
        // Set the paginator's length
        this.paginator.length = this.cars.length;
    
        return merge(...dataMutations).pipe(map(() => {
          return this.getPagedData(this.getSortedData([...this.cars]));
        }));
      }
    
      /**
       *  Called when the table is being destroyed. Use this function, to clean up
       * any open connections or free any held resources that were set up during connect.
       */
      disconnect() {
      }
    
      /**
       * Paginate the cars (client-side). If you're using server-side pagination,
       * this would be replaced by requesting the appropriate cars from the server.
       */
      private getPagedData(data: CarsTableItem[]) {
        const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
        return data.splice(startIndex, this.paginator.pageSize);
      }
    
      /**
       * Sort the cars (client-side). If you're using server-side sorting,
       * this would be replaced by requesting the appropriate cars from the server.
       */
      private getSortedData(data: CarsTableItem[]) {
        if (!this.sort.active || this.sort.direction === '') {
          return data;
        }
    
        return data.sort((a, b) => {
          const isAsc = this.sort.direction === 'asc';
          switch (this.sort.active) {
            case 'name':
              return compare(a.name, b.name, isAsc);
            case 'id':
              return compare(+a.id, +b.id, isAsc);
            default:
              return 0;
          }
        });
      }
    }
    
    /** Simple sort comparator for example ID/Name columns (for client-side sorting). */
    function compare(a, b, isAsc) {
      return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
    }
    

    <div>
      <label>Car name:
        <input #carName />
      </label>
      <!-- (click) passes input value to add() and then clears the input -->
      <button (click)="add(carName.value); carName.value=''">
        add
      </button>
    </div>
    
    <h2>My Cars</h2>
    <div class="mat-elevation-z8 centered-table-div">
      <table mat-table class="full-width-table" [dataSource]="dataSource" matSort aria-label="Elements">
    
        <!-- Image Column -->
        <ng-container matColumnDef="img_url">
          <th mat-header-cell *matHeaderCellDef mat-sort-header>Image</th>
          <td mat-cell *matCellDef="let row">
            <img [src]="row.img_url" alt="car image" class="car-image"/>
          </td>
        </ng-container>
    
        <!-- Id Column -->
        <ng-container matColumnDef="id">
          <th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
          <td mat-cell *matCellDef="let row">{{row.id}}</td>
        </ng-container>
    
        <!-- Name Column -->
        <ng-container matColumnDef="name">
          <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
          <td mat-cell *matCellDef="let row">{{row.name}}</td>
        </ng-container>
    
    
        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
      </table>
    
      <mat-paginator #paginator
                     [length]="dataSource.cars.length"
                     [pageIndex]="0"
                     [pageSize]="5"
                     [pageSizeOptions]="[3, 5, 25, 50]">
      </mat-paginator>
    </div>
    

    问题在于

      <mat-paginator #paginator
                     [length]="dataSource.cars.length"
                     [pageIndex]="0"
                     [pageSize]="5"
                     [pageSizeOptions]="[3, 5, 25, 50]">
      </mat-paginator>
    

    ERROR TypeError: Cannot read property 'cars' of undefined ,也就是说 dataSource 解析模板时未定义,但函数 :

      async ngOnInit() {
        console.log('before getting cars: ');
        console.log(this.cars);
        this.cars = await this.carService.getCars().toPromise();
        console.log('got cars:');
        console.log(this.cars);
        this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
      }
    

    打印输出:

    enter image description here enter image description here

    页面仍然加载所有内容,但是我不能通过这个方法添加cars,因为它们确实添加到数据库中,但是尽管调用了 this.table.renderRows() 如文件所述:

    因为表优化了性能,所以它不会自动检查数据数组的更改。相反,在数据数组上添加、删除或移动对象时,可以通过调用 renderRows()

    我试着做了 使用 Observable async/await ,但也不起作用:

      ngOnInit() {
        console.log('before getting cars: ');
        console.log(this.cars);
        this.carService.getCars().subscribe(cars => {
          this.cars = cars;
          console.log('got cars:');
          console.log(this.cars);
          this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
        });
      }
    

    恩戈尼特 ,则不存在任何错误。

    我也不能添加任何具有 add()

    如果你需要任何其他信息-请随时问我,我会确保尽快回答。

    编辑

    如果我编辑代码如下所示:

    async ngOnInit() {
      console.log('before getting cars: ');
      console.log(this.cars);
      console.log('got cars:');
      this.cars = await this.carService.getCars().toPromise();
      console.log(this.cars);
      this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
    }
    

    enter image description here enter image description here

    这意味着错误发生在

    this.cars = await this.carService.getCars().toPromise();
    

    我已经试过了 .subscribe() 在那个街区什么事都做,但运气不好。

    编辑2

    here (stackoverflow) 你必须初始化 因为视图是在中的所有微任务之前解析的 在视图初始化之后初始化分页器。

      async ngOnInit() {
        this.dataSource = new CarsTableDataSource(this.paginator, this.sort, []);
        console.log('before getting cars: ');
        console.log(this.cars);
        this.cars = await this.carService.getCars().toPromise();
        console.log('got cars:');
        console.log(this.cars);
        this.dataSource = new CarsTableDataSource(this.paginator, this.sort, this.cars);
      }
    

    现在它工作了,但这是一种黑客。我不知道为什么,但是每当Angular中的生命周期钩子中有asyc代码时,钩子都会在异步代码完成之前完成。我不知道为什么。 在它看到 await 数据源

    编辑3

      <mat-paginator #paginator
                     [length]="dataSource?.cars.length"
                     [pageIndex]="0"
                     [pageSize]="5"
                     [pageSizeOptions]="[3, 5, 25, 50]">
      </mat-paginator>
    

    使用此行:

    [length]="dataSource?.cars.length"
    

    由于视图是在ngOnInit完成一半时执行的,因此必须在使用该属性的任何地方添加该属性,这样在解析视图时它就不会出现在最终的html中。

    编辑4

    我更新了Stackblitz应用程序的链接,现在它尽可能地简化了这个问题。

    2 回复  |  直到 6 年前
        1
  •  3
  •   Yoarthur    6 年前

    cars 对象之前的 constructor

    cars: Car[] = [new Car()]
    
    constructor () { }
    

    这只是告诉angular模板将包含一个cars类型的数组。

    编辑

    CarsTableDataSource 同上。

    cars:Car[]=[新车()]

    从构造函数中删除它们。 (1)

    另一个解决办法是 一个@Injectable so将DI委托给Angular。

    ...
    @Injectable({providedIn: 'root'})
    export class CarsTableDataSource extends DataSource<CarsTableItem> {
    
    constructor ( private paginator: MatPaginator, private sort: MatSort, public cars: Car[] )
    
    ...
    
    }
    

    (1)

        2
  •  1
  •   Ali Ben Messaoud    6 年前

    connect()方法正在返回 Observable<CarsTableItem[]> . 作为 getPagedData getSortedData CarsTableDataSource 和材料表。

    尝试添加 .asObservable() 或者其他方法。

    作为最佳实践,您应该 CarsService 在执行 CarsTableDataSource公司

    推荐文章