23
loading...
This website collects cookies to deliver better user experience
Using Angular Material from a feature module
Custom Directives (including @HostListener and @HostBinding)
@ViewChild vs @ViewChildren and subscribing to changes on the latter
Validate while typing
Custom Events
<div class="mat-elevation-z8">
<table mat-table matSort [dataSource]="dataSource">
<tr mat-header-row *matHeaderRowDef="displayOrder"></tr>
<tr mat-row *matRowDef="let row; columns: displayOrder" (click)="onTouched(row)"></tr>
<ng-container matColumnDef="year">
<th mat-header-cell *matHeaderCellDef mat-sort-header="year"> Year </th>
<td mat-cell *matCellDef="let element"> {{element.year}} </td>
</ng-container>
<ng-container matColumnDef="model">
<th mat-header-cell *matHeaderCellDef> Model </th>
<td mat-cell *matCellDef="let element"> {{element.model}} </td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef> Price </th>
<td mat-cell *matCellDef="let element"> {{element.price}} </td>
</ng-container>
<ng-container matColumnDef="mileage">
<th mat-header-cell *matHeaderCellDef> Mileage </th>
<td mat-cell *matCellDef="let element">
<mat-form-field>
<input matInput class="bordered editable" type="text" min="0" max="1000000" value="{{element.mileage}}" id="{{element.carid}}"
(keyup)="__checkNumber($event)" (inputChanged)="onEdited($event)" >
<mat-hint><strong>Mileage</strong></mat-hint>
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="color">
<th mat-header-cell *matHeaderCellDef> Color </th>
<td mat-cell *matCellDef="let element"> {{element.color}} </td>
</ng-container>
<ng-container matColumnDef="transmission">
<th mat-header-cell *matHeaderCellDef> Transmission </th>
<td mat-cell *matCellDef="let element"> {{element.transmission}} </td>
</ng-container>
</table>
<!-- options should always be Fibonacci :) -->
<mat-paginator [length]="150" [pageSize]="5" [pageSizeOptions]="[5, 8, 13]" showFirstLastButtons (page)="onPage($event)"></mat-paginator>
<div align="right">
<button mat-button color="primary" (click)="onSave()">Save</button>
</div>
</div>
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MatTableModule,
MatPaginatorModule,
MatInputModule,
MatSortModule,
MatButtonModule
} from '@angular/material';
const PLATFORM_IMPORTS: Array<any> = [BrowserAnimationsModule];
const MATERIAL_IMPORTS: Array<any> = [MatTableModule, MatPaginatorModule, MatInputModule, MatSortModule, MatButtonModule];
@NgModule({
imports: [PLATFORM_IMPORTS, MATERIAL_IMPORTS],
exports: MATERIAL_IMPORTS,
declarations: []
})
export class MaterialModule { }
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MaterialModule } from '../material.module';
import { TableEditComponent } from '../table-edit/table-edit/table-edit.component';
import { InputSelectorDirective } from './directives/input-selector.directive';
export const TABLE_COMPONENTS: Array<any> = [TableEditComponent, InputSelectorDirective];
@NgModule({
imports: [MaterialModule, CommonModule],
exports: TABLE_COMPONENTS,
declarations: TABLE_COMPONENTS
})
export class TableEditModule { }
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
// feature module
import { TableEditModule } from './features/table-edit/table-edit.module';
// app-level components
import { AppComponent } from './app.component';
const APP_DECLARATIONS: Array<any> = [AppComponent];
@NgModule({
declarations: APP_DECLARATIONS,
imports: [
BrowserModule, HttpClientModule, TableEditModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
export interface ICarData
{
carid: number;
year: number,
model: string,
price: number
mileage: number;
color: string;
transmission: TransmissionEnum;
}
export interface ICarDataModel
{
header: Array<string>;
data: Array<ICarData>;
}
export interface IEditedData
{
id: number;
value: number;
}
protected __onModelLoaded(result: ICarDataModel): void
{
// this handler could be used to check the integrity of returned data
this.header = result.header.slice();
// assign a copy of the returned model to the bound data
this.data = result.data.map( (car: ICarData): ICarData => {return JSON.parse(JSON.stringify(car))} );
}
@Directive({
selector: '.editable'
})
export class InputSelectorDirective implements OnInit
<input matInput class="bordered editable" type="text" min="0" max="1000000" value="{{element.mileage}}" id="{{element.carid}}"
(keyup)="__checkNumber($event)" (inputChanged)="onEdited($event)" >
@ViewChildren(InputSelectorDirective)
protected _inputs: QueryList<InputSelectorDirective>; // reference to QueryList returned by Angular
protected _inputsArr: Array<InputSelectorDirective>; // Array of Directive references
protected _edited: Record<string, number>;
protected _touches: Record<string, number>;
@ViewChild(MatPaginator)
protected _paginator: MatPaginator;
@ViewChild(MatSort)
protected _sort: MatSort;
// (Material) Datasource for the table display
public dataSource: MatTableDataSource<ICarData>;
public ngAfterViewInit(): void
{
// subscribe to changes in the query list
this._inputs.changes.subscribe( () => this.__onInputsChanged() );
}
protected __onInputsChanged(): void
{
// input query list changed (which happens on profile selection)
this._inputsArr = this._inputs.toArray();
// set default border color on everything
if (this._inputsArr && this._inputsArr.length > 0) {
this._inputsArr.forEach( (input: InputSelectorDirective): void => {input.borderColor = '#cccccc'});
}
}
@Output('inputChanged')
protected _changed: EventEmitter<IEditedData>;
@HostBinding('style.border-color')
public borderColor: string = '#cccccc';
@HostListener('keyup', ['$event']) onKeyUp(evt: KeyboardEvent): boolean
{
// test for singleton leading negative sign as first character
const v: string = this._input.value;
const n: number = v.length;
// for now, allow a blank field as it is possible that the entire number could be deleted by backspace before
// entering a new number
if (n == 0) {
return true;
}
// physical quantities may not be negative and a decimal is currently not allowed
if ( (n == 1 && v == "-") || (evt.key == ".") )
{
this.hasError = true;
this._input.value = this._currentValue.toString();
return true;
}
// check for most recent keystroke being an enter, which is currently the only way to indicate an edit
const code: string = evt.code.toLowerCase();
if (code == 'enter' || code == 'return')
{
if (!isNaN(+v) && isFinite(+v))
{
this.hasError = false;
this._currentValue = +v;
// set 'edited' border color and emit the changed event
this.borderColor = '#66CD00';
this._changed.emit({id: this._currentID, value: +v});
}
else
{
this.hasError = true;
this._input.value = this._currentValue.toString();
}
return true;
}
this.hasError = !Validation.checkNumber(evt);
if (this.hasError)
{
console.log( "error: ", this._currentValue );
// indicate an error by replacing the bad input with the 'current' or last-known good value
// this may be altered in a future release
this._input.value = this._currentValue.toString();
}
return true;
}