Angular material MatTableDataSource Serverside pagination not working - datatable

I am new to angular, I am trying to apply material table with pagination and sorting. So, Initially I try with pagination. I stuck with this error. Do anyone help me out with pagination.
the error is:
Property 'subscribe' does not exist on type '() =>
Observable'.
servertable.component.ts
import { Component, OnInit, AfterContentInit, ViewChild } from '#angular/core';
import { MatPaginator, MatTableDataSource } from '#angular/material';
import { UserService } from './user.service';
import { IUser } from './model/IUser';
#Component({
selector: 'app-servertable',
templateUrl: './servertable.component.html',
styleUrls: ['./servertable.component.scss']
})
export class ServertableComponent implements OnInit, AfterContentInit, AfterViewInit {
dataSource: any;
users: IUser[];
displayedColumns = ['name', 'email', 'phone', 'company'];
#ViewChild(MatPaginator) paginator: MatPaginator;
constructor(private service: UserService) { }
ngOnInit() {
this.service.getUser.subscribe(results => this.users = results);
console.dir(this.dataSource);
}
ngAfterContentInit() {
this.dataSource = new MatTableDataSource(this.users);
this.dataSource.paginator = this.paginator;
}
}
user.service.ts
import { Injectable } from '#angular/core';
import { HttpClient} from '#angular/common/http';
import { Observable } from 'rxjs/observable';
import { IUser } from './model/IUser';
#Injectable()
export class UserService {
private serviceUrl = 'https://jsonplaceholder.typicode.com/users';
constructor(private http: HttpClient) { }
getUser(): Observable<IUser[]> {
return this.http.get<IUser[]>(this.serviceUrl);
}
}
servertable.component.html
<div>
<mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.name}} </mat-cell>
</ng-container>
<ng-container matColumnDef="email">
<mat-header-cell *matHeaderCellDef> E-Mail </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.email}} </mat-cell>
</ng-container>
<ng-container matColumnDef="phone">
<mat-header-cell *matHeaderCellDef> Phone </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.phone}} </mat-cell>
</ng-container>
<ng-container matColumnDef="company">
<mat-header-cell *matHeaderCellDef> Company </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.company.name}} </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 [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>
</div>

You are not calling your service method: this.service.getUser.subscribe(results => this.users = results);. You've forgotten to add parentheses on getUser.
Change it to: this.service.getUser().subscribe(results => this.users = results);
There is one more problem: this.users will not yet be available in ngAfterContentInit because the getUser() call is asynchronous. Move the assignment of the table to your subscribe() call. The whole ngOnInit() will look like this:
ngOnInit() {
this.service.getUser().subscribe(results => {
this.users = results;
this.dataSource = new MatTableDataSource(this.users);
this.dataSource.paginator = this.paginator;
});
}

Related

Custom Control not updating parent form validation

I've been working on adding a custom control to a form, I would like to do this because I know we have multiple components that may make up a form.
So for example you may have something like in app.components.ts:
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { FormGroup, FormBuilder, FormControl, Validators, AbstractControl } from '#angular/forms';
import { Model } from './cool-array/cool-array.component';
#Component({
selector: 'app-root',
styleUrls: ['./app.component.css'],
template:`
<!--The content below is only a placeholder and can be replaced.-->
<div class="col-md-6">
<form [formGroup]="form" >
<div class="form-group">
<label >Name</label>
<input formControlName="name" type="text" class="form-control" >
</div>
<app-cool-array formControlName="items"></app-cool-array>
</form>
</div>
<div class="col-md-6">
<div class="row">
IsValid Form: <strong> {{form.valid}}</strong>
Total Errors: <strong>{{form.errors? form.errors.length:0}}</strong>
</div>
<div class="row">
<table class="table table-stripped">
<thead>
<tr>
<th>Error</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let error of form.errors">
</tr>
</tbody>
</table>
</div>
</div>
`
})
export class AppComponent implements OnInit, AfterViewInit {
/**
*
*/
constructor(private formBuilder:FormBuilder) {
}
form:FormGroup;
model:MyModel;
ngOnInit(): void {
this.model = new MyModel();
this.form = this.formBuilder.group({ });
this.form.addControl('name', new FormControl('', Validators.required));
this.form.addControl('items', new FormControl([],[(control)=>MyValidator.MustHaveOne(control) ]))
}
ngAfterViewInit(): void {
console.log(this.form.errors)
}
}
export class MyValidator{
static MustHaveOne(control:AbstractControl){
if(control.value.length === 0) return {'length':'Items Must Have at least 1 item'};
return null;
}
}
export class MyModel{
name:string='';
items:Model[]=[];
}
You also may want to add the child component:
import { Component, OnInit } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'app-cool-array',
styleUrls: ['./cool-array.component.css'],
providers:[
{
provide:NG_VALUE_ACCESSOR,
multi:true,
useExisting:CoolArrayComponent
}
],
template:`
<button (click)="onAdd()" class="btn btn-primary" >Add</button>
<button (click)="onRemove()" class="btn">Remove</button>
<table class="table table-striped table-responsive">
<thead>
<tr>
<th>Something Required</th>
<th>Something Not Requred</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of items" (click)="onSelectedItem(item)" [ngClass]="{'selected-item':item === selectedItem}">
<td><input class="form-control" required [(ngModel)]="item.somethingRequired"/></td>
<td><input class="form-control" [(ngModel)]="item.somethingNotRequired"/></td>
</tr>
</tbody>
</table>
`
})
export class CoolArrayComponent implements OnInit, ControlValueAccessor {
onChange:any = ()=>{};
onTouched: any = () => { };
writeValue(obj: any): void {
this.items = obj;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
disabled:boolean=false;
items:Model[]=[];
selectedItem:Model;
constructor() { }
ngOnInit() {
}
onAdd():void{
this.items.push(new Model());
}
onRemove():void{
let index = this.items.indexOf(this.selectedItem);
if(index>-1)
this.items.splice(index, 1);
}
onSelectedItem(event){
this.selectedItem = event;
}
}
export class Model{
somethingRequired:string;
somethingNotRequired:string;
get isValid():boolean{
return this.somethingRequired !==undefined && this.somethingRequired.length>0;
}
}
When the child component becomes invalid this should set the form to be invalid.
I have tried adding a CustomValidator, however, it is never fired when the values change in the underlying array.
Can someone please explain why this is?
Okay, so I had to do a few things here to get this to work I will not post the entire code just the relevant parts.
I had to do is add a new custom Validator
static ArrayMustBeValid(control:AbstractControl){
if(control.value){
if(control.value.length>0){
let items:Model[] = control.value;
let invalidIndex = items.findIndex(x=> !x.isValid);
if(invalidIndex === -1) return null;
return {'array': 'array items are not valid'};
}
}
return {'array':'array cannot be null'};
}
Then I had to add an update event on the input this should fire on keyup
onUpdate(){
this.onChange(this.items);
}
Had to add the validator to FormControl in app.component.ts
this.form.addControl('items', new FormControl([],[(control)=>MyValidator.MustHaveOne(control), (control)=>MyValidator.ArrayMustBeValid(control) ]))

Angular6 filter

I'm trying to build a filter pipe in Angular 6, but it's not working properly.
.html
<form>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" name="searchString" placeholder="Type to search..." [(ngModel)]="searchString">
</div>
</div>
</form>
<table class="table">
<tr *ngFor="let user of users | filter2 : searchString">
{{user.name}}
</tr>
</table>
pipe
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter2'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (!items) return [];
if (!searchText) return items;
searchText = searchText.toLowerCase();
return items.filter(it => {
return it.toLowerCase().includes(searchText);
});
}
}
I get this error when I write inside input:
ERROR TypeError: it.toLowerCase is not a function
What am I doing wrong? Thank you for your time!
users
You can try it.name.toString().toLowerCase().includes(searchText);
Component
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
users:any[];
constructor(){
this.users = [
{name:"John"},
{name:"Tom"},
{name:"Carlton"},
{name:"Judy"},
{name:"Ada"}
];
}
}
Html
<form>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" name="searchString" placeholder="Type to search..." [(ngModel)]="searchString">
</div>
</div>
</form>
<table class="table">
<tr *ngFor="let user of users | filter2 : searchString">
{{user.name}}
</tr>
</table>
Pipe
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter2'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (!items) return [];
if (!searchText) return items;
searchText = searchText.toLowerCase();
return items.filter(it => {
return it.name.toLowerCase().includes(searchText);
});
}
}

Angular Typescript subscribed value is empty but data is still supplied to the template

I'm very new to Angular and Typescript and have ran into a, to me, seemingly strange issue.
I'm trying to build a mat-table and using the value of my class to create a MatTableDataSource object. I'm getting the values from a get request to my back-end but any time I try to use the recovered values in my component the values are empty, but when it comes time to load the template they are suddenly there.
Here's my component:
import { Component, OnInit, ViewChild } from '#angular/core';
import { first } from 'rxjs/operators';
import { MatTableDataSource } from '#angular/material/table';
import { MatPaginator, MatSort } from '#angular/material';
import { Invoice } from '../_models';
import { InvoiceService } from '../_services';
#Component({
selector: 'app-invoices',
templateUrl: './invoices.component.html',
styleUrls: ['./invoices.component.css']
})
export class InvoicesComponent implements OnInit {
displayedColumns=['rating', 'amount', 'debtor', 'serial', 'dateout', 'expiration', 'daysleft', 'fid']
invoices: Invoice[] = [];
dataSource= new MatTableDataSource<Invoice>(this.invoices);
#ViewChild(MatPaginator) paginator: MatPaginator;
#ViewChild(MatSort) sort: MatSort;
constructor(
private invoiceService: InvoiceService
) { }
ngOnInit() {
this.loadInvoices();
console.log(this.invoices);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
private loadInvoices(){
this.invoiceService.getUserInvoices().pipe(first()).subscribe(invoices => {
this.invoices=invoices;
});
console.log(this.invoices);
}
applyFilter(filterValue: string) {
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
this.dataSource.filter = filterValue;
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
}
And here is the template:
<h3> All Uploaded invoices:</h3>
<mat-form-field>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
<div class="mat-elevation-z8">
<mat-table #table [dataSource]="invoices" matSort class="mat-elevation-z8">
<ng-container matColumnDef="rating">
<mat-header-cell *matHeaderCellDef mat-sort-header> Rating </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.rating}} </mat-cell>
</ng-container>
<ng-container matColumnDef="amount">
<mat-header-cell *matHeaderCellDef mat-sort-header> Amount </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.amount}} </mat-cell>
</ng-container>
<ng-container matColumnDef="debtor">
<mat-header-cell *matHeaderCellDef> Debtor </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.debtor}} </mat-cell>
</ng-container>
<ng-container matColumnDef="serial">
<mat-header-cell *matHeaderCellDef> Serial </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.serial}} </mat-cell>
</ng-container>
<ng-container matColumnDef="dateout">
<mat-header-cell *matHeaderCellDef> Dateout </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.dateout}} </mat-cell>
</ng-container>
<ng-container matColumnDef="expiration">
<mat-header-cell *matHeaderCellDef> Expiration </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.expiration}} </mat-cell>
</ng-container>
<ng-container matColumnDef="daysleft">
<mat-header-cell *matHeaderCellDef mat-sort-header > Days left </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.daysleft}} </mat-cell>
</ng-container>
<ng-container matColumnDef="fid">
<mat-header-cell *matHeaderCellDef> Fid </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.fid}} </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 [pageSizeOptions]="[3, 100, 200]" showFirstLastButtons></mat-paginator>
</div>
<p><a [routerLink]="['/login']">Logout</a></p>
The important part is [dataSource]="invoices", as that get populated with the data collected from my get request that is initiated by the loadInvoices method. If i change it to [dataSource]="dataSource" it comes out as an empty array. Similarly both my console.logs output an empty array. Is there a way to use the data from my request to create a MatTableDataSource object and pass it to the template, and if so, how might I do that?
New component after #DiabolicWords solved it:
import { Component, OnInit, ViewChild } from '#angular/core';
import { first } from 'rxjs/operators';
import { MatTableDataSource } from '#angular/material/table';
import { MatPaginator, MatSort } from '#angular/material';
import { Invoice } from '../_models';
import { InvoiceService } from '../_services';
#Component({
selector: 'app-invoices',
templateUrl: './invoices.component.html',
styleUrls: ['./invoices.component.css']
})
export class InvoicesComponent implements OnInit {
displayedColumns=['rating', 'amount', 'debtor', 'serial', 'dateout', 'expiration', 'daysleft', 'fid']
dataSource: MatTableDataSource<Invoice>;
#ViewChild(MatPaginator) paginator: MatPaginator;
#ViewChild(MatSort) sort: MatSort;
constructor(
private invoiceService: InvoiceService
) { }
ngOnInit() {
this.loadInvoices();
}
private loadInvoices(){
this.invoiceService.getUserInvoices().pipe(first()).subscribe(invoices => {
this.dataSource = new MatTableDataSource<Invoice>(invoices);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}
applyFilter(filterValue: string) {
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
this.dataSource.filter = filterValue;
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
}
The problem comes with the Subscription to an Observable. Your console.log()-calls are processed much earlier than the values return. That's why you always see empty objects although, a few millisecs later, they are filled with data.
try it this way:
ngOnInit() {
this.loadInvoices();
}
private loadInvoices(){
this.invoiceService.getUserInvoices().pipe(first()).subscribe(invoices => {
this.invoices=invoices;
console.log(this.invoices);
this.dataSource = new MatTableDataSource<Invoice>(this.invoices);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}

Complete Angular Material 2 DataTable Example: Combining Multiple Columns Of Nested Objects To One With Sorting and Filtering

This is a problem faced during the development of a web application. It had an angular material 2 data table which was rendering data with nested objects and needed to concat data of different columns of nested object in one column. Additionally sorting and filtering on all columns.
Suppose that data is made of the following objects:
Incident, Patient, Doctor that are related. Each Incident is related with a patient and a doctor with a one to many relationship.
So we have the following implementation of objects in typescript:
Incident.ts
import {Patient} from '../patients/patient';
import {Doctor} from 'app/doctors/doctor';
export class Incident {
private _id: number;
private _protocolNo: number;
private _date: any;
private _symperasma: string;
private _patient: Patient;
get id(): number {
return this._id;
}
set id(value: number) {
this._id = value;
}
get protocolNo(): number {
return this._protocolNo;
}
set protocolNo(value: number) {
this._protocolNo = value;
}
get date(): any {
return this._date;
}
set date(value: any) {
this._date = value;
}
get symperasma(): string {
return this._symperasma;
}
set symperasma(value: string) {
this._symperasma = value;
}
get patient(): Patient {
return this._patient;
}
set patient(value: Patient) {
this._patient = value;
}
}
Patient.ts
import {Incident} from '../incidents/incident';
export class Patient {
_id: number;
_firstName: string;
_lastName: string;
_incidents: Incident[];
get incidents(): Incident[] {
return this._incidents;
}
set incidents(value: Incident[]) {
this._incidents = value;
}
get id(): number {
return this._id;
}
set id(value: number) {
this._id = value;
}
get firstName(): string {
return this._firstName;
}
set firstName(value: string) {
this._firstName = value;
}
get lastName(): string {
return this._lastName;
}
set lastName(value: string) {
this._lastName = value;
}
}
Now lets inspect the DataTables Code. The data is received by an http get request and assigned to a MatTableDataSource in function call getIncidents().
IncidentsListComponent.ts
export class IncidentsListComponent implements OnInit, OnDestroy {
displayedColumns = ['protocolNo', 'date', 'patient.lastName patient.firstName', 'actions'];
incidentsDataSource: MatTableDataSource<Incident> = new MatTableDataSource<Incident>();
#ViewChild(MatPaginator) paginator: MatPaginator;
#ViewChild(MatSort) sort: MatSort;
constructor(private incidentsService: IncidentsService) {
}
async ngOnInit() {
await this.getIncidents();
this.incidentsDataSource.paginator = this.paginator;
this.incidentsDataSource.sortingDataAccessor = (item, property) => {
switch (property) {
case 'patient.lastName patient.firstName':
return item.patient.lastName + ' ' + item.patient.firstName;
case 'protocolNo':
return item.protocolNo;
case 'date':
return item.date;
}
};
this.incidentsDataSource.sort = this.sort;
this.incidentsDataSource.filterPredicate = (data, filter) => JSON.stringify(data).includes(filter);
}
applyFilter(filterValue: string) {
this.incidentsDataSource.filter = filterValue;
}
getIncidents() {
this.incidentsService.getIncidents().subscribe((results) => {
this.incidentsDataSource.data = results;
}
// )
);
}
}
In displayedColumns array are declared the displayed columns of DataTable where we see the concatenation of two columns in one for patient object data.
For Sorting and Filtering this.incidentsDataSource.sortingDataAccessor and this.incidentsDataSource.filterPredicate are implemented. In sortingDataAccessor there is concatenation of columns again. In filterPredicate JSON to string conversion and filtering.
applyFilter function is called in template on (keyup) event of input search field as you will see below in the template file. Also at the input field related to patient object we use again concatenation in matcolumnDef
incidents-list.component.html
<!--Filtering input fields-->
<div class="example-header">
<mat-form-field>
<input matInput #filter (keyup)="applyFilter($event.target.value)" placeholder="Αναζήτηση">
</mat-form-field>
</div>
<div class="example-container mat-elevation-z8">
<mat-table #table [dataSource]="incidentsDataSource" matSort>
<ng-container matColumnDef="protocolNo">
<mat-header-cell *matHeaderCellDef mat-sort-header> Αρ. Πρωτοκόλλου</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.protocolNo}}</mat-cell>
</ng-container>
<ng-container matColumnDef="date">
<mat-header-cell *matHeaderCellDef mat-sort-header> Ημερομηνία</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.date | date:'dd/MM/yyyy' }}</mat-cell>
</ng-container>
<ng-container matColumnDef="patient.lastName patient.firstName">
<mat-header-cell *matHeaderCellDef mat-sort-header>Ασθενής</mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.patient.lastName}} {{row.patient.firstName}}</mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef> Ενέργειες</mat-header-cell>
<mat-cell *matCellDef="let row" style="white-space: nowrap">
<button mat-icon-button (click)="openDialog(row)">
<span matTooltip="Λεπτομέρειες!">
<mat-icon style="color: grey">assignment</mat-icon>
</span>
</button>
<a [routerLink]="['./update', row.id]">
<span matTooltip="Επεξεργασία!">
<mat-icon>mode_edit</mat-icon>
</span>
</a>
<a [routerLink]="['./delete', row.id]">
<span matTooltip="Διαγραφή!">
<mat-icon>delete</mat-icon>
</span>
</a>
<button mat-icon-button (click)="renderPDF(row.id)">
<span matTooltip="Λεπτομέρειες!">
<mat-icon style="color: grey">picture_as_pdf</mat-icon>
</span>
</button>
</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 #paginator
[length]="incidentsDataSource?.filteredData.length"
[pageIndex]="0"
[pageSize]="25"
[pageSizeOptions]="[25, 50, 100, 250, 500]">
</mat-paginator>
</div>

Datatable not refreshing on save

I want my table to refresh on save click, it's working for normal table with *ngFor, but I'm using Smartadmin angular template. I think the solution may be related to table.ajax.reload() , but how do i execute this in angular way.
save-tax.component.ts
import { Component, OnInit, Input ,Output, EventEmitter } from '#angular/core';
// Forms related packages
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { FormValidation } from 'app/shared/validators/formValidation'; //custom form validation
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { FlashMessagesService } from 'angular2-flash-messages';
#Component({
selector: 'save-tax',
templateUrl: './save-tax.component.html'
})
export class SaveTaxComponent implements OnInit {
#Output() reloadTableData = new EventEmitter();
saveTaxForm: FormGroup;
constructor(private _fb: FormBuilder,
private _config: ConfigurationService,
private _flash: FlashMessagesService) { }
ngOnInit() {
this.saveTaxForm_builder();
}
saveTaxForm_builder() {
this.saveTaxForm = this._fb.group({
tax_title: [null, [
Validators.required
]],
tax_rate: [null, [
Validators.required,
Validators.pattern(FormValidation.patterns().price),
]],
});
}
tTitle = "input"; tRate = "input";
validInput(val) {
var classSuccess = "input state-success";
val == 'tax_title' ? this.tTitle = classSuccess : null;
val == 'tax_rate' ? this.tRate = classSuccess : null;
}
invalidInput(val) {
var classError = "input state-error";
val == 'tax_title' ? this.tTitle = classError : null;
val == 'tax_rate' ? this.tRate = classError : null;
}
classReset() {
this.tTitle = "input";
this.tRate = "input";
}
save_tax() {
if (this.saveTaxForm.value) {
this._config.createTax(this.saveTaxForm.value).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-success', timeout: 1000 });
this.saveTaxForm.reset();
this.classReset();
this.reloadTableData.emit(); // Emitting an event
} else {
this.saveTaxForm.reset();
this.classReset();
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-danger', timeout: 3500 });
}
},
error => {
this.saveTaxForm.reset();
this.classReset();
this._flash.show("Please contact customer support. " + error.status + ": Internal server error.", { cssClass: 'alert alert-danger', timeout: 5000 });
});
} else {
this._flash.show('Something went wrong! Please try again..', { cssClass: 'alert alert-warning', timeout: 3000 });
}
}
}
datatable.component.ts
import {Component, Input, ElementRef, AfterContentInit, OnInit} from '#angular/core';
declare var $: any;
#Component({
selector: 'sa-datatable',
template: `
<table class="dataTable responsive {{tableClass}}" width="{{width}}">
<ng-content></ng-content>
</table>
`,
styles: [
require('smartadmin-plugins/datatables/datatables.min.css')
]
})
export class DatatableComponent implements OnInit {
#Input() public options:any;
#Input() public filter:any;
#Input() public detailsFormat:any;
#Input() public paginationLength: boolean;
#Input() public columnsHide: boolean;
#Input() public tableClass: string;
#Input() public width: string = '100%';
constructor(private el: ElementRef) {
}
ngOnInit() {
Promise.all([
System.import('script-loader!smartadmin-plugins/datatables/datatables.min.js'),
]).then(()=>{
this.render()
})
}
render() {
let element = $(this.el.nativeElement.children[0]);
let options = this.options || {}
let toolbar = '';
if (options.buttons)
toolbar += 'B';
if (this.paginationLength)
toolbar += 'l';
if (this.columnsHide)
toolbar += 'C';
if (typeof options.ajax === 'string') {
let url = options.ajax;
options.ajax = {
url: url,
complete: function (xhr) {
options.ajax.reload();
}
}
}
options = $.extend(options, {
"dom": "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-6 col-xs-12 hidden-xs text-right'" + toolbar + ">r>" +
"t" +
"<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
oLanguage: {
"sSearch": "<span class='input-group-addon'><i class='glyphicon glyphicon-search'></i></span> ",
"sLengthMenu": "_MENU_"
},
"autoWidth": false,
retrieve: true,
responsive: true,
initComplete: (settings, json)=> {
element.parent().find('.input-sm', ).removeClass("input-sm").addClass('input-md');
}
});
const _dataTable = element.DataTable(options);
if (this.filter) {
// Apply the filter
element.on('keyup change', 'thead th input[type=text]', function () {
_dataTable
.column($(this).parent().index() + ':visible')
.search(this.value)
.draw();
});
}
//custom functions
element.on('click', 'delete', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( $(this).hasClass('delete') ) {
row.remove().draw(false);
console.log(row);
}
else {
//$(table).$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
console.log($(this).attr("class"))
});
//end custom functions
if (!toolbar) {
element.parent().find(".dt-toolbar").append('<div class="text-right"><img src="assets/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;"></div>');
}
if(this.detailsFormat){
let format = this.detailsFormat
element.on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( row.child.isShown() ) {
row.child.hide();
tr.removeClass('shown');
}
else {
row.child( format(row.data()) ).show();
tr.addClass('shown');
}
})
}
}
}
tax-list.component.html
<!-- NEW COL START -->
<article class="col-sm-12 col-md-12 col-lg-12">
<!-- Widget ID (each widget will need unique ID)-->
<div sa-widget [editbutton]="false" [custombutton]="false">
<header>
<span class="widget-icon"> <i class="fa fa-percent"></i> </span>
<h2>Tax Rule List</h2>
</header>
<!-- widget div-->
<div>
<!-- widget content -->
<div class="widget-body no-padding">
<sa-datatable [options]="tableData" paginationLength="true" tableClass="table table-striped table-bordered table-hover" width="100%">
<thead>
<tr>
<th data-hide="phone"> ID </th>
<th data-hide="phone,tablet">Tax Title</th>
<th data-class="expand">Tax Rate</th>
<th data-hide="phone,tablet">Status</th>
<th data-hide="phone,tablet"> Action </th>
</tr>
</thead>
</sa-datatable>
</div>
<!-- end widget content -->
</div>
<!-- end widget div -->
</div>
<!-- end widget -->
</article>
<!-- END COL -->
tax-list.component.ts
import { FlashMessagesService } from 'angular2-flash-messages';
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { Component, OnInit } from '#angular/core';
declare var $: any;
#Component({
selector: 'tax-list',
templateUrl: './tax-list.component.html'
})
export class TaxListComponent implements OnInit {
tableData: any;
constructor(private _config: ConfigurationService, private _flash: FlashMessagesService) { }
ngOnInit() {
this.fetchTableData();
this.buttonEvents();
}
fetchTableData() {
this.tableData = {
ajax: (data, callback, settings) => {
this._config.getTaxRules().subscribe(data => {
if (data.success) {
callback({
aaData: data.data
});
} else {
alert(data.msg);
}
},
error => {
alert('Internal server error..check database connection.');
});
},
serverSIde:true,
columns: [
{
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{ data: 'tax_title' },
{ data: 'tax_rate' },
{ data: 'status' },
{
render: function (data, type, row) {
return `<button type="button" class="btn btn-warning btn-xs edit" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Edit</button>
<button type="button" class="btn btn-danger btn-xs delete" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Delete</button>`;
}
}
],
buttons: [
'copy', 'pdf', 'print'
]
};
}
buttonEvents(){
document.querySelector('body').addEventListener('click', (event) => {
let target = <Element>event.target; // Cast EventTarget into an Element
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('edit')) {
this.tax_edit(target.getAttribute('data-element-id'));
}
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('delete')) {
this.tax_delete(target.getAttribute('data-element-id'));
}
});
}
tax_edit(tax_id) {
}
tax_delete(tax_id) {
this._config.deleteTaxById(tax_id).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-info fade in', timeout: 3000 });
this.fetchTableData();
} else {
this._flash.show(data.msg, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
}
},
error => {
this._flash.show(error, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
});
}
reloadTable(){
this.ngOnInit();
}
}
You can add a refresh button in your widget using <div class='widget-toolbar'>...</div> and using (click) event binding, attach a method with it. I named it as onRefresh() ...
<div sa-widget [editbutton]="false" [colorbutton]="false">
<header>
<span class="widget-icon">
<i class="fa fa-chart"></i>
</span>
<h2>Sample Datatable</h2>
<div class="widget-toolbar" role="menu">
<a class="glyphicon glyphicon-refresh" (click)="onRefresh('#studentTable table')"></a>
</div>
</header>
<div>
<div class="widget-body no-padding">
<sa-datatable id="studentTable" [options]="datatableOptions" tableClass="table table-striped table-bordered table-hover table-responsive">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Rank</th>
<th>Options</th>
</tr>
</thead>
</sa-datatable>
</div>
</div>
</div>
Focus at the method which I have given in the click event binding, I passed the id of the <sa-datatable> with table, that is #studentTable table that mentions the table tag according to the datatable implementation of the smartadmin.
Now in the component, add a method 'onRefresh()' which should be like
onRefresh(id: any) {
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
In this method, that #studentTable table will come in this id which is a parameter in the method. Using jQuery you can do the table.ajax.reload().
But you need to declare the jQuery at the top.
declare var $ : any;
app.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
declare var $: any;
#Component({
selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
})
export class OrderComponent implements OnInit, OnDestroy {
datatableOptions:{
...
}
constructor(){
...
}
ngOnInit(){
...
}
onRefresh(){
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
}

Resources