Я запускаю две таблицы MatTable в разных компонентах с источниками данных из разных наблюдаемых. Одна из моих функций сортировки таблиц работает нормально, но для моей второй таблицы кажется, что @ViewChild для MatSort не инициализируется во время ngOnInit.
Данные отображаются, а в таблице материалов есть кнопки сортировки, но функциональность ничего. Проверил мой импорт и модуль, все в порядке.
При регистрации MatSort один компонент регистрирует объект MatSort, а другой не определен.
Сортировка не работает.
Фид.компонент:
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.info(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.info(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.
Сообщение.компонент:
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.info(this.sort)
this.updateMessages()
this.currentUserId = this.currentUserId;
this.currentUsername = this.currentUsername;
}
updateMessages(){
this.ms.getUserSent().subscribe(messages => {
console.info(this.sort) <------logs MatSort object
this.userSent = new MatTableDataSource(messages)
this.userSent.sort = this.sort
console.info(this.userSent.sort)
console.info(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>
Я действительно не могу понять, почему в моем первом компоненте сортировка возвращает неопределенное значение, когда в моем втором рабочем компоненте он возвращает и объект. Я что-то упустил в порядке @ViewChild?
TL; DR ваш вопрос, но попробуйте использовать хук жизненного цикла AfterViewInit
, предоставленный angular, который инициализирует представление.
попробовал AfterViewInit с теми же результатами. Угловая версия 7.3.3
Из официальных документов: 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.info(this.sort)
}
}
Если вы используете Angular 8, вам необходимо реорганизовать реализацию свойств @ViewChild
, поскольку требуется флаг static
.
Спасибо, Харун, но мой второй компонент также не имеет ngAfterViewInit - если это так, не следует ли нам ожидать, что он не инициализируется, даже если это так?
ngAfterViewInit
следит за тем, чтобы свойство было инициализировано (если в шаблоне есть дочернее представление). Однако вы можете получить свойство, инициированное, например, после нажатия кнопки или вызова службы, потому что оно мощь будет инициировано в то же время (но не гарантируется). И не могли бы вы поделиться шаблоном, где вы разместили MatSort
, к которому хотите получить доступ?
Это должно сработать. Отрисовывается ли представление до того, как будет возвращено наблюдаемое? До сих пор не уверен, почему это происходит в одном компоненте, но не в другом.
Не могли бы вы мне помочь? Я получаю эту ошибку: ERROR Error: "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'model: undefined'.
Привет @Tanzeel, я хотел бы помочь, но я очень занят в эти дни. Вы можете посмотреть эту ветку SO: stackoverflow.com/questions/43375532/…
проблема в FeedComponent
заключается в том, что вы делаете this.dataSource.sort = this.sort
присваивание перед инициализацией this.dataSource
refreshPosts(){
console.info(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.info(this.sort)
по-прежнему будет печатать undefined из-за последовательностей жизненного цикла. на ngOnInit
запросы на просмотр не заданы.
поэтому возникает вопрос; тогда как this.dataSource.sort = this.sort
задание будет работать в ngOnInit
в MessageComponent
?
ответ длинный, но если говорить простыми словами; потому что этот код выполняется в обратном вызове subscribe
. поскольку код в обратном вызове подписки выполняется, когда наблюдаемое испускает, происходит асинхронная операция. и эта асинхронная операция происходит в последующем цикле обнаружения изменений после цикла, в котором выполняется хук ngAfterViewInit
.
вы не получаете неопределенный вывод во втором компоненте, потому что этот оператор console.info также выполняется в обратном вызове подписки. перемещение этого оператора журнала из обратного вызова подписки также будет печатать неопределенное.
если вы поместите операторы console.info в хук 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 может иметь несколько причин, и трудно догадаться, почему вы ее получаете. если вы можете опубликовать свою проблему и код в виде вопроса, вы можете получить помощь. и вот хорошая статья, чтобы получить представление об ExpressionChangedAfterItHasBeenCheckedError.
Какая у вас сейчас версия Angular?