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
|
@ -1,7 +1,7 @@
|
||||||
import {Injectable} from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {environment} from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import {LoginService} from './auth/login.service';
|
import { LoginService } from './auth/login.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -18,6 +18,18 @@ export class ApiService {
|
||||||
}).toPromise();
|
}).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> {
|
async logout(): Promise<void> {
|
||||||
await this.request('post', '/users/logout', '');
|
await this.request('post', '/users/logout', '');
|
||||||
this.loginService.token = null;
|
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 { Component, OnInit } from '@angular/core';
|
||||||
|
import { User } from '../../model/user.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-list',
|
selector: 'app-user-list',
|
||||||
|
@ -6,8 +7,10 @@ import { Component, OnInit } from '@angular/core';
|
||||||
styleUrls: ['./user-list.component.css']
|
styleUrls: ['./user-list.component.css']
|
||||||
})
|
})
|
||||||
export class UserListComponent implements OnInit {
|
export class UserListComponent implements OnInit {
|
||||||
|
itemType = User;
|
||||||
|
|
||||||
constructor() { }
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
||||||
import { UserListComponent } from './user-list/user-list.component';
|
import { UserListComponent } from './user-list/user-list.component';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { RouteData } from '../app-routing.module';
|
import { RouteData } from '../app-routing.module';
|
||||||
|
import { SharedComponentsModule } from '../shared-components/shared-components.module';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: '', component: UserListComponent, data: {title: 'Felhasználók'} as RouteData}
|
{path: '', component: UserListComponent, data: {title: 'Felhasználók'} as RouteData}
|
||||||
|
@ -12,7 +13,8 @@ const routes: Routes = [
|
||||||
declarations: [UserListComponent],
|
declarations: [UserListComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
RouterModule.forChild(routes)
|
RouterModule.forChild(routes),
|
||||||
|
SharedComponentsModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class UsersModule { }
|
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