Skip to content

Instantly share code, notes, and snippets.

@jhades
Last active July 6, 2022 11:22
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jhades/9cdcff38c5f5092069363ddf69bda5bf to your computer and use it in GitHub Desktop.
Save jhades/9cdcff38c5f5092069363ddf69bda5bf to your computer and use it in GitHub Desktop.
Angular Material Data Table blog post - https://blog.angular-university.io/angular-material-data-table
import { MatInputModule, MatPaginatorModule, MatProgressSpinnerModule,
MatSortModule, MatTableModule } from "@angular/material";
@NgModule({
declarations: [
...
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
MatInputModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatProgressSpinnerModule
],
providers: [
...
],
bootstrap: [AppComponent]
})
export class AppModule {
}
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource">
<ng-container matColumnDef="seqNo">
<div *matHeaderCellDef>#</div>
<div *matCellDef="let lesson">{{lesson.seqNo}}</div>
</ng-container>
<ng-container matColumnDef="description">
<div *matHeaderCellDef>Description</div>
<div class="description-cell"
*matCellDef="let lesson">{{lesson.description}}</div>
</ng-container>
<ng-container matColumnDef="duration">
<div *matHeaderCellDef>Duration</div>
<div class="duration-cell"
*matCellDef="let lesson">{{lesson.duration}}</div>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource">
<ng-container matColumnDef="seqNo">
<mat-header-cell *matHeaderCellDef>#</mat-header-cell>
<mat-cell *matCellDef="let lesson">{{lesson.seqNo}}</mat-cell>
</ng-container>
<ng-container matColumnDef="description">
<mat-header-cell *matHeaderCellDef>Description</mat-header-cell>
<mat-cell class="description-cell"
*matCellDef="let lesson">{{lesson.description}}</mat-cell>
</ng-container>
<ng-container matColumnDef="duration">
<mat-header-cell *matHeaderCellDef>Duration</mat-header-cell>
<mat-cell class="duration-cell"
*matCellDef="let lesson">{{lesson.duration}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
displayedColumns = ["seqNo", "description", "duration"];
<mat-row *matRowDef="let row; columns: displayedColumns"
(click)="onRowClicked(row)">
</mat-row>
onRowClicked(row) {
console.log('Row clicked: ', row);
}
@Injectable()
export class CoursesService {
constructor(private http:HttpClient) {}
findLessons(
courseId:number, filter = '', sortOrder = 'asc',
pageNumber = 0, pageSize = 3): Observable<Lesson[]> {
return this.http.get('/api/lessons', {
params: new HttpParams()
.set('courseId', courseId.toString())
.set('filter', filter)
.set('sortOrder', sortOrder)
.set('pageNumber', pageNumber.toString())
.set('pageSize', pageSize.toString())
}).pipe(
map(res => res["payload"])
);
}
}
import {CollectionViewer, DataSource} from "@angular/cdk/collections";
export class LessonsDataSource implements DataSource<Lesson> {
private lessonsSubject = new BehaviorSubject<Lesson[]>([]);
constructor(private coursesService: CoursesService) {}
connect(collectionViewer: CollectionViewer): Observable<Lesson[]> {
...
}
disconnect(collectionViewer: CollectionViewer): void {
...
}
loadLessons(courseId: number, filter: string,
sortDirection: string, pageIndex: number, pageSize: number) {
...
}
}
export class LessonsDataSource implements DataSource<Lesson> {
private lessonsSubject = new BehaviorSubject<Lesson[]>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);
public loading$ = this.loadingSubject.asObservable();
constructor(private coursesService: CoursesService) {}
connect(collectionViewer: CollectionViewer): Observable<Lesson[]> {
return this.lessonsSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.lessonsSubject.complete();
this.loadingSubject.complete();
}
loadLessons(courseId: number, filter = '',
sortDirection = 'asc', pageIndex = 0, pageSize = 3) {
this.loadingSubject.next(true);
this.coursesService.findLessons(courseId, filter, sortDirection,
pageIndex, pageSize).pipe(
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false))
)
.subscribe(lessons => this.lessonsSubject.next(lessons));
}
}
@Component({
selector: 'course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit {
dataSource: LessonsDataSource;
displayedColumns= ["seqNo", "description", "duration"];
constructor(private coursesService: CoursesService) {}
ngOnInit() {
this.dataSource = new LessonsDataSource(this.coursesService);
this.dataSource.loadLessons(1);
}
}
<div class="course">
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
<mat-spinner></mat-spinner>
</div>
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource">
....
</mat-table>
</div>
<div class="course">
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
<mat-spinner></mat-spinner>
</div>
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource">
....
</mat-table>
<mat-paginator [length]="course?.lessonsCount" [pageSize]="3"
[pageSizeOptions]="[3, 5, 10]"></mat-paginator>
</div>
@Component({
selector: 'course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements AfterViewInit, OnInit {
course:Course;
dataSource: LessonsDataSource;
displayedColumns= ["seqNo", "description", "duration"];
@ViewChild(MatPaginator) paginator: MatPaginator;
constructor(private coursesService: CoursesService, private route: ActivatedRoute) {}
ngOnInit() {
this.course = this.route.snapshot.data["course"];
this.dataSource = new LessonsDataSource(this.coursesService);
this.dataSource.loadLessons(this.course.id, '', 'asc', 0, 3);
}
ngAfterViewInit() {
this.paginator.page
.pipe(
tap(() => this.loadLessonsPage())
)
.subscribe();
}
loadLessonsPage() {
this.dataSource.loadLessons(
this.course.id,
'',
'asc',
this.paginator.pageIndex,
this.paginator.pageSize);
}
}
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource"
matSort matSortActive="seqNo" matSortDirection="asc" matSortDisableClear>
<ng-container matColumnDef="seqNo">
<mat-header-cell *matHeaderCellDef mat-sort-header>#</mat-header-cell>
<mat-cell *matCellDef="let lesson">{{lesson.seqNo}}</mat-cell>
</ng-container>
....
</mat-table>
@Component({
selector: 'course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements AfterViewInit, OnInit {
course:Course;
dataSource: LessonsDataSource;
displayedColumns= ["seqNo", "description", "duration"];
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(private coursesService: CoursesService, private route: ActivatedRoute) {}
ngOnInit() {
this.course = this.route.snapshot.data["course"];
this.dataSource = new LessonsDataSource(this.coursesService);
this.dataSource.loadLessons(this.course.id, '', 'asc', 0, 3);
}
ngAfterViewInit() {
// reset the paginator after sorting
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
tap(() => this.loadLessonsPage())
)
.subscribe();
}
loadLessonsPage() {
this.dataSource.loadLessons(
this.course.id, '', this.sort.direction,
this.paginator.pageIndex, this.paginator.pageSize);
}
}
<div class="course">
<!-- New part: this is the search box -->
<mat-input-container>
<input matInput placeholder="Search lessons" #input>
</mat-input-container>
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
<mat-spinner></mat-spinner>
</div>
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource"
matSort matSortActive="seqNo" matSortDirection="asc" matSortDisableClear>
<ng-container matColumnDef="seqNo">
<mat-header-cell *matHeaderCellDef mat-sort-header>#</mat-header-cell>
<mat-cell *matCellDef="let lesson">{{lesson.seqNo}}</mat-cell>
</ng-container>
<ng-container matColumnDef="description">
<mat-header-cell *matHeaderCellDef>Description</mat-header-cell>
<mat-cell class="description-cell"
*matCellDef="let lesson">{{lesson.description}}</mat-cell>
</ng-container>
<ng-container matColumnDef="duration">
<mat-header-cell *matHeaderCellDef>Duration</mat-header-cell>
<mat-cell class="duration-cell"
*matCellDef="let lesson">{{lesson.duration}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
<mat-paginator [length]="course?.lessonsCount" [pageSize]="3"
[pageSizeOptions]="[3, 5, 10]"></mat-paginator>
</div>
@Component({
selector: 'course',
templateUrl: './course.component.html',
styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit, AfterViewInit {
course:Course;
dataSource: LessonsDataSource;
displayedColumns= ["seqNo", "description", "duration"];
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
@ViewChild('input') input: ElementRef;
constructor(
private route: ActivatedRoute,
private coursesService: CoursesService) {}
ngOnInit() {
this.course = this.route.snapshot.data["course"];
this.dataSource = new LessonsDataSource(this.coursesService);
this.dataSource.loadLessons(this.course.id, '', 'asc', 0, 3);
}
ngAfterViewInit() {
// server-side search
fromEvent(this.input.nativeElement,'keyup')
.pipe(
debounceTime(150),
distinctUntilChanged(),
tap(() => {
this.paginator.pageIndex = 0;
this.loadLessonsPage();
})
)
.subscribe();
// reset the paginator after sorting
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
// on sort or paginate events, load a new page
merge(this.sort.sortChange, this.paginator.page)
.pipe(
tap(() => this.loadLessonsPage())
)
.subscribe();
}
loadLessonsPage() {
this.dataSource.loadLessons(
this.course.id,
this.input.nativeElement.value,
this.sort.direction,
this.paginator.pageIndex,
this.paginator.pageSize);
}
}
// server-side search
fromEvent(this.input.nativeElement,'keyup')
.pipe(
debounceTime(150),
distinctUntilChanged(),
tap(() => {
this.paginator.pageIndex = 0;
this.loadLessonsPage();
})
)
.subscribe();
connect(collectionViewer: CollectionViewer): Observable<Lesson[]> {
return this.lessonsSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.lessonsSubject.complete();
this.loadingSubject.complete();
}
loadLessons(courseId: number, filter = '',
sortDirection = 'asc', pageIndex = 0, pageSize = 3) {
this.loadingSubject.next(true);
this.coursesService.findLessons(courseId, filter, sortDirection,
pageIndex, pageSize).pipe(
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false))
)
.subscribe(lessons => this.lessonsSubject.next(lessons));
}
ngAfterViewInit() {
this.paginator.page
.pipe(
tap(() => this.loadLessonsPage())
)
.subscribe();
}
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
<mat-spinner></mat-spinner>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment