Add custom material table and list components
Automatically querying the specified endpoint for the list data
This commit is contained in:
parent
e4ff4f5acf
commit
fc995858e6
14 changed files with 228 additions and 7 deletions
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<app-table [items]="items" (pageChange)="handlePageChange($event)" [paginationData]="paginationData" [columns]="columns"
|
||||
[loading]="loading">
|
||||
|
||||
</app-table>
|
|
@ -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();
|
||||
});
|
||||
});
|
48
frontend/src/app/shared-components/list/list.component.ts
Normal file
48
frontend/src/app/shared-components/list/list.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 { }
|
|
@ -0,0 +1,3 @@
|
|||
table {
|
||||
width: 100%;
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
31
frontend/src/app/shared-components/table/table.component.ts
Normal file
31
frontend/src/app/shared-components/table/table.component.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1 +1 @@
|
|||
<p>user-list works!</p>
|
||||
<app-list apiPath="/users" [itemType]="itemType"></app-list>
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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 { }
|
||||
|
|
5
frontend/src/app/utility/pagination-data.ts
Normal file
5
frontend/src/app/utility/pagination-data.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export class PaginationData {
|
||||
total?: number;
|
||||
limit?: number;
|
||||
page?: number;
|
||||
}
|
Loading…
Reference in a new issue