Add custom material table and list components

Automatically querying the specified endpoint for the list data
This commit is contained in:
Norbi Peti 2022-01-21 17:10:36 +01:00
parent e4ff4f5acf
commit fc995858e6
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
14 changed files with 228 additions and 7 deletions

View file

@ -1,7 +1,7 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from '../environments/environment';
import {LoginService} from './auth/login.service';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';
import { LoginService } from './auth/login.service';
@Injectable({
providedIn: 'root'
@ -18,6 +18,18 @@ export class ApiService {
}).toPromise();
}
requestPage<T>(url: string, limit: number, page: number): Promise<T[]> {
const c = url.indexOf('?') === -1 ? '?' : '&';
return this.request('get', url + c + 'filter=' + encodeURI(JSON.stringify({
limit,
offset: (page - 1) * limit
})), {});
}
requestItemCount(url: string): Promise<number> {
return this.request('get', url + '/count', {});
}
async logout(): Promise<void> {
await this.request('post', '/users/logout', '');
this.loginService.token = null;

View file

@ -0,0 +1,4 @@
<app-table [items]="items" (pageChange)="handlePageChange($event)" [paginationData]="paginationData" [columns]="columns"
[loading]="loading">
</app-table>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ListComponent } from './list.component';
describe('ListComponent', () => {
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ListComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,48 @@
import { Component, Input, OnInit, Type } from '@angular/core';
import { PageEvent } from '@angular/material/paginator';
import { PaginationData } from '../../utility/pagination-data';
import { ApiService } from '../../api.service';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent<T> implements OnInit {
@Input() apiPath: string;
@Input() itemType: Type<T>;
paginationData: PaginationData = {};
items: T[] = [];
columns: { title: string, prop: string }[] = [
{title: 'Név', prop: 'name'},
{title: 'Admin', prop: 'isAdmin'}
];
loading = false;
constructor(private api: ApiService) {
}
ngOnInit(): void {
this.getItems(10, 1).catch(e => console.error(e));
}
async handlePageChange(event: PageEvent): Promise<void> {
await this.getItems(event.pageSize, event.pageIndex + 1);
}
async getItems(limit: number, page: number): Promise<void> {
try {
this.loading = true;
const total = await this.api.requestItemCount(this.apiPath);
this.paginationData.page = page;
this.paginationData.limit = limit;
this.paginationData.total = total;
this.items = await this.api.requestPage<T>(this.apiPath, limit, page);
} finally {
this.loading = false;
}
}
}

View file

@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TableComponent } from './table/table.component';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatTableModule } from '@angular/material/table';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { ListComponent } from './list/list.component';
@NgModule({
declarations: [TableComponent, ListComponent],
exports: [
TableComponent,
ListComponent
],
imports: [
CommonModule,
MatProgressBarModule,
MatPaginatorModule,
MatTableModule,
MatButtonModule,
MatIconModule
]
})
export class SharedComponentsModule { }

View file

@ -0,0 +1,3 @@
table {
width: 100%;
}

View file

@ -0,0 +1,35 @@
<div>
<div *ngIf="showHeader">
<div>
<ng-content select="[tableHeader]"></ng-content>
</div>
</div>
<div>
<mat-progress-bar mode="indeterminate" *ngIf="loading"></mat-progress-bar>
<table mat-table [dataSource]="items">
<ng-container *ngFor="let col of columns" [matColumnDef]="col.prop">
<th mat-header-cell *matHeaderCellDef>{{col.title}}</th>
<td mat-cell *matCellDef="let item">{{ item[col.prop]}}</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef style="width: 2rem"></th>
<td mat-cell *matCellDef="let item">
<button mat-icon-button color="primary"><mat-icon>edit</mat-icon></button>
</td>
<tr mat-header-row *matHeaderRowDef="getPropNames()"></tr>
<tr mat-row *matRowDef="let row; columns: getPropNames()"></tr>
</ng-container>
</table>
</div>
<mat-paginator
[length]="paginationData.total"
[pageSize]="paginationData.limit"
[pageSizeOptions]="[10, 25, 50, 100]"
(page)="pageChange.emit($event)"
showFirstLastButtons
></mat-paginator>
<div>
<ng-content select="[tableFooter]"></ng-content>
</div>
</div>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TableComponent } from './table.component';
describe('AppTableComponent', () => {
let component: TableComponent;
let fixture: ComponentFixture<TableComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TableComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,31 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { PaginationData } from '../../utility/pagination-data';
import { PageEvent } from '@angular/material/paginator';
@Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.css']
})
export class TableComponent<T> implements OnInit {
@Input() showHeader = false;
@Input() items: T[] = [];
@Input() loading = false;
@Input() columns: { title: string, prop: string }[] = [];
@Input() paginationData: PaginationData = {page: 1, limit: 10};
@Output() pageChange = new EventEmitter<PageEvent>();
constructor() {
}
ngOnInit(): void {
}
getPropNames(): string[] {
return this.columns.map(col => col.prop).concat('actions');
}
}

View file

@ -1 +1 @@
<p>user-list works!</p>
<app-list apiPath="/users" [itemType]="itemType"></app-list>

View file

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { User } from '../../model/user.model';
@Component({
selector: 'app-user-list',
@ -6,8 +7,10 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
itemType = User;
constructor() { }
constructor() {
}
ngOnInit(): void {
}

View file

@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { UserListComponent } from './user-list/user-list.component';
import { RouterModule, Routes } from '@angular/router';
import { RouteData } from '../app-routing.module';
import { SharedComponentsModule } from '../shared-components/shared-components.module';
const routes: Routes = [
{path: '', component: UserListComponent, data: {title: 'Felhasználók'} as RouteData}
@ -12,7 +13,8 @@ const routes: Routes = [
declarations: [UserListComponent],
imports: [
CommonModule,
RouterModule.forChild(routes)
RouterModule.forChild(routes),
SharedComponentsModule
]
})
export class UsersModule { }

View file

@ -0,0 +1,5 @@
export class PaginationData {
total?: number;
limit?: number;
page?: number;
}