@Viewchild не инициализируется во время ngOnInit

Я запускаю две таблицы MatTables в разных компонентах с источниками данных из разных наблюдаемых. Одна из моих функций сортировки таблиц работает нормально, но на моей второй таблице кажется, что @ViewChild для MatSort не инициализируется во время 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)

    })

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>

Я действительно не могу понять, почему в моем первом компоненте сортировка возвращает undefined, тогда как во втором рабочем компоненте он возвращает и объект. Мне что-то не хватает порядка @ViewChild?


person Timotronadon    schedule 09.07.2019    source источник
comment
На какой версии Angular вы сейчас находитесь?   -  person visrey    schedule 09.07.2019
comment
TL; DR ваш вопрос, но попробуйте использовать крючок жизненного цикла AfterViewInit, предоставляемый angular, который инициализирует представление.   -  person Nicholas K    schedule 09.07.2019
comment
попробовал AfterViewInit с такими же результатами. Угловая версия 7.3.3   -  person Timotronadon    schedule 09.07.2019


Ответы (2)


Из официальных документов: 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

person Harun Yilmaz    schedule 09.07.2019
comment
Спасибо, Харун, но у моего второго компонента тоже нет ngAfterViewInit - если бы это было так, разве мы не должны ожидать, что он не инициализируется, хотя это так? - person Timotronadon; 09.07.2019
comment
ngAfterViewInit гарантирует, что свойство инициализировано (если в шаблоне есть дочерний элемент представления). Однако вы можете инициировать свойство, например, после нажатия кнопки или вызова службы, поскольку оно может быть инициировано в это время (но это не гарантируется). И не могли бы вы поделиться шаблоном, где вы разместили MatSort, к которому хотите получить доступ? - person Harun Yilmaz; 09.07.2019
comment
Это должно сработать. Отрисовывается ли представление до того, как будет возвращено наблюдаемое? Все еще не уверен, почему это происходит в одном компоненте, а не в следующем. - person Timotronadon; 09.07.2019
comment
Не могли бы вы мне помочь? Я получаю эту ошибку: ERROR Error: "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'model: undefined'. - person Tanzeel; 15.02.2020
comment
Привет @Tanzeel, я бы хотел помочь, но сейчас я очень занят. Вы можете взглянуть на эту тему SO: stackoverflow.com/questions/43375532/ - person Harun Yilmaz; 15.02.2020

проблема в FeedComponent заключается в том, что вы выполняете this.dataSource.sort = this.sort присваивание перед инициализацией this.dataSource

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 присваивание будет работать в ngOnInit в MessageComponent?

ответ длинный, но если говорить простыми словами; потому что этот код выполняется в обратном вызове subscribe. поскольку код в обратном вызове подписки выполняется, когда наблюдаемый испускает, происходит асинхронная операция. и эта асинхронная операция происходит в последующем цикле обнаружения изменений после цикла, в котором выполняется ngAfterViewInit hook.

вы не получаете неопределенный вывод во втором компоненте, потому что этот оператор 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 из-за асинхронной операции.

person ysf    schedule 09.07.2019
comment
Но почему я получаю эту ошибку: "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. - person Tanzeel; 15.02.2020
comment
ExpressionChangedAfterItHasBeenCheckedError может иметь несколько причин, и трудно догадаться, почему вы его получаете. если вы можете опубликовать свою проблему и код в качестве вопроса, вы можете получить помощь. и это хорошая статья, чтобы получить некоторое представление об ExpressionChangedAfterItHasBeenCheckedError - person ysf; 21.02.2020