【问题标题】:@Viewchild not initializing during ngOnInit@Viewchild 在 ngOnInit 期间未初始化
【发布时间】:2019-11-19 08:15:25
【问题描述】:

我在不同的组件中运行两个 MatTable,其数据源来自不同的 observable。我的一个表格排序功能工作正常,但在我的第二个表格上,似乎 MatSort 的 @ViewChild 在 ngOnInit 期间没有初始化。

数据渲染和材料表有排序按钮,但功能什么都没有。检查了我的导入和模块,一切都很好。
在记录 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)

    })

消息服务

 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>

我真的找不到为什么在我的第一个组件中排序返回 undefined 而在我的第二个工作组件中它返回和对象。我错过了@ViewChild 的顺序吗?

【问题讨论】:

  • 你目前使用的是哪个 Angular 版本?
  • TL;DR 你的问题,但尝试使用 Angular 提供的 AfterViewInit 生命周期钩子来初始化视图。
  • 用相同的结果尝试了 AfterViewInit。 Angular 版本 7.3.3

标签: angular typescript angular-material viewchild ngoninit


【解决方案1】:

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

在调用 ngAfterViewInit 回调之前设置视图查询。

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

export class MessageComponent implements OnInit, AfterViewInit {

   @ViewChild(MatSort) sort: MatSort;

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

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

【讨论】:

  • 感谢 Harun,但我的第二个组件也没有 ngAfterViewInit - 如果是这种情况,我们难道不应该期望它不会初始化吗?
  • ngAfterViewInit 确保属性已初始化(如果模板中有视图子项)。但是,您可以在单击按钮或服务调用后初始化属性,例如,因为它可能同时被初始化(但不能保证)。您能否分享您放置MatSort 并希望访问的模板?
  • 应该可以。是在 observable 返回之前的视图渲染吗?仍然不确定为什么会在一个组件中发生这种情况,而在下一个组件中却没有。
  • 你能帮帮我吗?我收到此错误:ERROR Error: "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'model: undefined'.
  • 你好@Tanzeel 我想帮忙,但这些天我真的很忙。你可以看看这个 SO 线程:stackoverflow.com/questions/43375532/…
【解决方案2】:

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.sort 分配在ngOnInitMessageComponent 中如何工作?

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

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

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

作为总结;

初始化this.datasource后赋值

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

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

【讨论】:

  • 但是为什么我会收到这个错误:"ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
  • ExpressionChangedAfterItHasBeenCheckedError 可能有多种原因,很难猜出你为什么会得到它。如果您可以将问题和代码作为问题发布,您可能会得到帮助。和here is a good article 了解 ExpressionChangedAfterItHasBeenCheckedError
猜你喜欢
  • 1970-01-01
  • 2019-10-14
  • 1970-01-01
  • 1970-01-01
  • 2018-06-10
  • 2013-02-26
  • 1970-01-01
  • 2020-07-18
  • 2021-09-25
相关资源
最近更新 更多