Reputation: 365
I'm running two MatTables in different components with data sources from different observables. One of my tables sort functionality is working fine and but on my second table it seems as if the @ViewChild for MatSort doesn't initialize during ngOnInit.
Data renders and the material table has sort buttons but the functionality is nothing. Checked my imports and the module and everything is fine.
On logging the MatSort one component logs a MatSort object and the other is undefined
Sorting not working.
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;
}
Now my next component initializes in the same way but the @ViewChild shows up as a MatSort Object
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>
I really cant find why in my first component the sort returns undefined when in my second, working component it returns and object. Am I missing something about the order of @ViewChild?
Upvotes: 14
Views: 34688
Reputation: 4864
the problem in FeedComponent
is that you are making this.dataSource.sort = this.sort
assignment before initializing 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;
});
}
please note that console.log(this.sort)
will still print undefined because of lifecycle sequences. on ngOnInit
view queries are not set.
so the question arises; then how would this.dataSource.sort = this.sort
assignment work in ngOnInit
in MessageComponent
?
the answer is long but to put it in simple terms; because that code gets executed in subscribe
callback. since the code in subscribe callback gets executed when observable emits, an asynchronous operation happens. and that async operation happens in a subsequent change detection cycle after a cycle where ngAfterViewInit
hook gets executed.
you are not getting undefined output in your second component because that console.log statement is also executed in a subscribe callback. moving that log statement out of subscribe callback will also print undefined.
if you place console.log statements in ngAfterViewInit
hook, they both will print actual values whether they are placed in a subscribe callback or not.
as a summary;
make assignment after initializing this.datasource
this.dataSource= new MatTableDataSource(this.postData)
this.dataSource.sort = this.sort // make assignment after initializing
and do it in ngAfterViewInit
hook, even though it works in ngOnInit
due to the async operation.
Upvotes: 1
Reputation: 8558
From official docs: https://angular.io/api/core/ViewChild#description
View queries are set before the ngAfterViewInit callback is called.
In order to get @ViewChild
property inited, you need to call it in ngAfterViewInit
lifecycle hook.
export class MessageComponent implements OnInit, AfterViewInit {
@ViewChild(MatSort) sort: MatSort;
ngAfterViewInit(){
console.log(this.sort)
}
}
If you are using Angular 8, you need to refactor the implementation of @ViewChild
properties since static
flag is required
Upvotes: 24