@Viewchild 在 ngOnInit 期间未初始化

@Viewchild not initializing during ngOnInit

我是 运行 不同组件中的两个 MatTable,数据源来自不同的可观察对象。我的 table 排序功能之一工作正常,但在我的第二个 table 上,似乎 MatSort 的 @ViewChild 在 ngOnInit 期间没有初始化。

数据呈现和 material table 有排序按钮,但功能是什么。检查了我的导入和模块,一切正常。
在记录 MatSort 时,一个组件记录一个 MatSort 对象,另一个未定义

排序无效。

Feed.component:

   import { PostService } from './../../services/post.service';
   import { Post } from './../../models/post';
   import { Component, OnInit, ViewChild, ChangeDetectorRef} from 
     '@angular/core';
   import { MatSort, MatTableDataSource, MatCheckbox, MatPaginator, 
     MatTabChangeEvent, MatDialog, MatDialogActions, MatTable}  from 
   "@angular/material"



export class FeedComponent implements OnInit {
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatPaginator) paginator: MatPaginator;
  postData: Post[] =[];
  dataSource : MatTableDataSource<any> 
  currentUser = JSON.parse(localStorage.getItem('user'))
  displayedColumns:string[] = ['User','Title', "Description", 
  "Contact" ]
  posts = this.ps.getPosts();

  constructor(private ps: PostService, public dialog:MatDialog, 
    public change:ChangeDetectorRef, public ms:MessageService) { 

  }



refreshPosts(){
   console.log(this.sort) < -------comes back undefined
  this.posts.subscribe(posts=>{
    this.dataSource.sort = this.sort
     this.postData = posts.filter(post => post.uid != 
       `${this.currentUser.uid}` && post.claimedBy 
        !=`${this.currentUser.uid}`);
     this.dataSource= new MatTableDataSource(this.postData)
     this.dataSource.paginator = this.paginator;
    });

  }
ngOnInit() {
   this.refreshPosts()
   console.log(this.sort)
   }


Post.service
  getPosts(){
    return  this.afs.collection('posts').snapshotChanges()
     .pipe(map(actions => 
     actions.map(this.documentToDomainObject)))
  }
 documentToDomainObject = _ => {
  const object = _.payload.doc.data();
  object.id = _.payload.doc.id;
  return object;
}

现在我的下一个组件以相同的方式初始化,但 @ViewChild 显示为 MatSort 对象

Message.component:

export class MessageComponent implements OnInit {


 @ViewChild(MatSort) sort: MatSort;
  userReceived: MatTableDataSource<any>;
  userSent: MatTableDataSource<any>;
  displayedColumns:string[] = ["createdAt",'author',"title", "Delete"]
  sentColumns:string[] = ["createdAt","recipient", "title", "Delete"]


  currentUserId= this.currentUser['uid']
  currentUsername = this.currentUser['displayName']
  recipient:any;
  selectedMessage: MatTableDataSource<Message>;
  messageColumns= ['From','Title',"Body"];




  constructor(public ms:MessageService, public change:ChangeDetectorRef, public dialog: MatDialog  ) { }

  ngOnInit() {
    console.log(this.sort)
    this.updateMessages()
    this.currentUserId = this.currentUserId;
    this.currentUsername = this.currentUsername;

 }


updateMessages(){
    this.ms.getUserSent().subscribe(messages => {
      console.log(this.sort) <------logs MatSort object
      this.userSent = new MatTableDataSource(messages)
      this.userSent.sort = this.sort
      console.log(this.userSent.sort)
      console.log(this.userSent.data)

    })

message.service

 getUserSent() {
    let messages:any[] = [];
    this.userSent = this.afs
      .collection('messages', ref => ref.where('uid', '==', `${this.currentUser.uid}`)).snapshotChanges() 
return this.userSent
  }

feed.component.html

<div class = "mat-elevation-z8">
    <mat-form-field>
        <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search Posts">
      </mat-form-field>
  <table matSort mat-table [dataSource]="dataSource" style="text-align:left">
      <ng-container matColumnDef="User">
          <th mat-header-cell *matHeaderCellDef mat-sort-header>User</th>
          <td mat-cell *matCellDef="let post">{{post.displayName}}</td>
       </ng-container>

  <ng-container matColumnDef="Title">
    <th mat-header-cell *matHeaderCellDef>Title</th>
    <td mat-cell *matCellDef="let post">{{post.title | truncate:15:false }}</td>
 </ng-container>

  <ng-container matColumnDef="Description">
    <th mat-header-cell *matHeaderCellDef >Description</th>
    <td mat-cell *matCellDef="let post">{{post.description | truncate: 20 : false}}</td>
  </ng-container>





  <ng-container matColumnDef="Contact">
    <th mat-header-cell *matHeaderCellDef> Contact </th>
    <td mat-cell *matCellDef="let post">
      <button  id="{{post.id}}" color="primary" (click)="openDialog($event.target.id)" style = "outline:none" value={{post.id}}>Claim</button>
    </td>

  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef='let row; columns: displayedColumns'></tr>
</table>
</div>
  <mat-paginator [length]="this.postData.length" [pageSize]="5" [pageSizeOptions]="[5,10,25]"></mat-paginator>

我真的找不到为什么在我的第一个组件中排序 returns 未定义,而在我的第二个工作组件中它 returns 和对象。我是不是漏掉了@ViewChild 的顺序?

来自官方文档:https://angular.io/api/core/ViewChild#description

View queries are set before the ngAfterViewInit callback is called.

为了让 @ViewChild 属性 初始化,你需要在 ngAfterViewInit 生命周期挂钩中调用它。

export class MessageComponent implements OnInit, AfterViewInit {

   @ViewChild(MatSort) sort: MatSort;

   ngAfterViewInit(){
      console.log(this.sort)
   }
}

如果您使用的是 Angular 8,则需要重构 @ViewChild 属性的实现,因为需要 static 标志

FeedComponent 中的问题是您在初始化 this.dataSource

之前进行 this.dataSource.sort = this.sort 赋值
refreshPosts(){
  console.log(this.sort) < -------comes back undefined
  this.posts.subscribe(posts=>{
     this.postData = posts.filter(post => post.uid != `${this.currentUser.uid}` && post.claimedBy !=`${this.currentUser.uid}`);
     this.dataSource= new MatTableDataSource(this.postData)
     this.dataSource.sort = this.sort // make assignment after initializing this.dataSource
     this.dataSource.paginator = this.paginator;
    });
  }

请注意,由于生命周期顺序,console.log(this.sort) 仍将打印 undefined。在 ngOnInit 上未设置视图查询。

所以问题来了;那么 this.dataSource.sort = this.sortngOnInit 中如何在 MessageComponent 中工作?

答案很长,但简单来说;因为该代码在 subscribe 回调中执行。由于 subscribe 回调中的代码在 observable 发出时执行,因此发生异步操作。 并且该异步操作发生在随后的变更检测周期中 在执行 ngAfterViewInit 挂钩的周期之后。

您没有在第二个组件中得到未定义的输出,因为该 console.log 语句也在订阅回调中执行。将该日志语句移出订阅回调也会打印 undefined。

如果你在 ngAfterViewInit 钩子中放置 console.log 语句,无论它们是否放置在订阅回调中,它们都会打印实际值。

作为总结;

初始化后赋值this.datasource

this.dataSource= new MatTableDataSource(this.postData)
this.dataSource.sort = this.sort // make assignment after initializing 

并在 ngAfterViewInit 挂钩中执行,即使由于异步操作它在 ngOnInit 中工作。