Angular: Cannot read property 'dataRows' of undefined - html

Can anyone help me with this issue.
Seems to be caused by the fact that the datatable is being build before the data is returned.
The data does load but and the table works as required but its annoying me that it still shows in the console.
Any help would be great, code below.
userlist.component.ts
export class UserslistComponent implements OnInit {
public dataTable: DataTable;
public dataTableData: any;
constructor( private usersService: UsersService) {}
ngOnInit() {
this.usersService.getUsers().subscribe(data => { this.buildDtOptions(data); });
}
buildDtOptions(tableData: any): void {
this.dataTable = {
headerRow: [ 'Firstname', 'Lastname', 'Email', 'Permission', 'Account Status' ],
footerRow: [ 'Firstname', 'Lastname', 'Email', 'Permission', 'Account Status' ],
dataRows: tableData
};
}
ngAfterViewInit() {
$('#datatable').DataTable({
'pagingType': 'full_numbers',
'lengthMenu': [
[10, 25, 50, -1],
[10, 25, 50, 'All']
],
responsive: true,
language: {
search: '_INPUT_',
searchPlaceholder: 'Search records',
},
});
const table = $('#datatable').DataTable();
}
users.service.html
export class UsersService {
localUrl = './assets/data/users.json';
usersUrl = 'https://my.api.mockaroo.com/users.json?key=58f30fc0';
constructor(private http: HttpClient) { }
getUsers() {
return this.http.get(this.localUrl)
.catch(this.errorHandler);
}
errorHandler(error: HttpErrorResponse) {
return Observable.throw(error.message || 'Server Error');
}

Related

Ag Grid Why cell renderer component updates after running 'onRowHeightChanged()'

I use Ag Grid Angular to display the user list.
One of the cells is an email with a custom component. When the value is invalid it shows an error message. To display it properly I should expend the row. When I expend the row, an error despaired.
It looks like the cell renderer component updates every time after calling the onRowHeightChanged() method.
How to fix this issue?
<div class="app-users-list__action-bar">
<button
class="btn btn-primary ml-1"
(click)="changeGridMode()"
>{{gridModeBtnTitle}}</button>
</div>
<ng-container *ngIf="users$ | async"></ng-container>
<div
class="app-users-list__table"
infiniteScroll
[infiniteScrollContainer]="'.ag-body-viewport'"
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="0"
(scrolled)="onScroll()"
[scrollWindow]="false">
<ag-grid-angular
class="ag-theme-bootstrap user-list"
[suppressRowClickSelection]="true"
[gridOptions]="gridOptions"
[frameworkComponents]="frameworkComponents"
>
</ag-grid-angular>
</div>
<ngx-loading [show]="isLoading$ | async"></ngx-loading>
</section>
import { Component, OnInit } from '#angular/core';
import { NgModel } from '#angular/forms';
import { select, Store } from '#ngrx/store';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import * as fromShared from '../../../shared/models';
import * as userList from '../../actions/user-list.action';
import * as fromUserListSelectors from '../../selectors/user-list.selectors';
import * as gridComponents from '../../../shared/grid-components';
import { Organization } from '../../../organizations';
import { DEFAULT_INTERACTION_LIST_FILTER, SORT_DIRECTION } from '../../../shared/constants/filter.constant';
import { GridApi, GridOptions, GridReadyEvent, RowNode } from 'ag-grid-community';
import { Evaluator } from '../../../evaluators';
#Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss'],
})
export class UserListComponent implements OnInit {
public users$: Observable<fromShared.User[]>;
public isLoading$: Observable<boolean>;
public isEditMode = false;
public frameworkComponents = {...gridComponents};
public gridModeBtnTitle = this.setButtonTitle(this.isEditMode);
public columnDefs = [
{
headerName: 'First Name',
field: 'firstName',
minWidth: 200,
unSortIcon: true,
suppressMovable: true,
resizable: true,
autoHeight: true,
cellRenderer: 'GridTextCellComponent',
cellRendererParams: (params) => {
return {
value: params.data.firstName,
name: 'firstName',
aqaId: 'user-firstName',
};
}
},
{
headerName: 'Last Name',
field: 'lastName',
minWidth: 200,
unSortIcon: true,
suppressMovable: true,
autoHeight: true,
cellRenderer: 'GridTextCellComponent',
cellRendererParams: (params) => {
return {
value: params.data.lastName,
name: 'lastName',
aqaId: 'user-lastName',
};
}
},
{
headerName: 'Email',
field: 'email',
minWidth: 200,
unSortIcon: true,
suppressMovable: true,
autoHeight: true,
cellRenderer: 'GridEmailCellComponent',
cellRendererParams: (params) => {
return {
value: params.data.email,
name: 'email',
aqaId: 'user-email',
};
}
},
{
headerName: 'Sso Email Id',
field: 'SsoEmail',
minWidth: 300,
unSortIcon: true,
suppressMovable: true,
autoHeight: true,
cellRenderer: 'GridEmailCellComponent',
cellRendererParams: (params) => {
return {
editable: true,
editMode: this.isEditMode,
value: params.data.ssoEmail,
name: 'ssoEmail',
placeholder: 'Enter a SSO email',
aqaId: 'user-ssoEmail',
callback: this.updateHandler.bind(this, params.data, 'ssoEmail'),
statusChange: this.statusFieldChange.bind(this, params.node)
};
}
},
{
headerName: 'Org.Admin',
field: 'orgAdmin',
maxWidth: 200,
unSortIcon: false,
suppressMovable: true,
autoHeight: true,
cellRenderer: 'GridTextCellComponent',
cellRendererParams: (params) => {
return {
value: this.setUserRoleStatus(params.data.roles, fromShared.ERole.ORG_ADMIN),
name: 'orgAdmin',
aqaId: 'user-orgAdmin',
};
}
},
{
headerName: 'Eval.Admin',
field: 'evalAdmin',
maxWidth: 200,
unSortIcon: false,
suppressMovable: true,
autoHeight: true,
cellRenderer: 'GridTextCellComponent',
cellRendererParams: (params) => {
return {
value: this.setUserRoleStatus(params.data.roles, fromShared.ERole.EVALUATION_ADMIN),
name: 'evalAdmin',
aqaId: 'user-evalAdmin',
};
}
},
{
headerName: 'Evaluator',
field: 'evaluator',
maxWidth: 200,
unSortIcon: false,
suppressMovable: true,
autoHeight: true,
cellRenderer: 'GridTextCellComponent',
cellRendererParams: (params) => {
return {
value: this.setUserRoleStatus(params.data.roles, fromShared.ERole.CAN_VOTE),
name: 'evaluator',
aqaId: 'user-evaluator',
};
}
}
];
public gridOptions = <GridOptions>{
context: {componentParent: this},
columnDefs: this.columnDefs,
rowData: [],
enableColResize: true,
enableSorting: true,
headerHeight: 40,
rowHeight: 40,
animateRows: true,
suppressLoadingOverlay: false,
onSortChanged: () => {
let sortModel = this.gridOptions.api.getSortModel();
if (sortModel.length > 0) {
this.usersFilter.direction = SORT_DIRECTION[sortModel[0].sort.toUpperCase()];
this.usersFilter.orderBy = sortModel[0].colId;
} else {
this.usersFilter.orderBy = null;
this.usersFilter.direction = DEFAULT_INTERACTION_LIST_FILTER.direction;
}
this.usersFilter.page = 0;
this.gridOptions.api.setRowData([]);
this.store.dispatch(new userList.LoadUsersByFilterAction(this.usersFilter));
},
onRowDataChanged: (params) => {
},
getRowNodeId: (data: Evaluator) => {
return data.id;
},
noRowsOverlayComponent: 'NoRowsOverlayComponent',
noRowsOverlayComponentParams: {
noRowsMessageFunc: () => {
this.isLoading$.pipe(
map(loading => loading ? '' : 'You have no Users.')
);
}
},
rowClassRules: {
'text-transparent': (params) => params.data.unused,
},
defaultColDef: {
headerName: '',
field: '',
minWidth: 100,
unSortIcon: true,
suppressMovable: true,
resizable: true,
autoHeight: true,
},
onGridReady(event: GridReadyEvent) {
event.api.sizeColumnsToFit();
},
};
private usersFilter: fromShared.UserFilterList = {
limit: 20,
page: 0,
orderBy: 'firstName',
direction: SORT_DIRECTION.ASC
};
constructor(private store: Store<Organization>) {}
public ngOnInit(): void {
this.store.dispatch(new userList.LoadUsersByFilterAction(this.usersFilter));
this.isLoading$ = this.store.pipe(select(fromUserListSelectors.selectLoading));
this.users$ = this.store.pipe(select(fromUserListSelectors.selectUsers))
.pipe(
map(users => {
const api: GridApi = this.gridOptions.api;
if (api) {
api.setRowData(users);
}
return users;
})
);
}
public onScroll() {
this.usersFilter.page++;
this.store.dispatch(new userList.LoadUsersByFilterAction(this.usersFilter));
}
public changeGridMode(): void {
this.isEditMode = !this.isEditMode;
this.gridModeBtnTitle = this.setButtonTitle(this.isEditMode);
this.gridOptions.enableSorting = !this.isEditMode;
this.gridOptions.api.refreshCells({force: true});
}
private updateHandler(item: fromShared.User, field: string, newValue: any, controller: NgModel) {
if (controller && controller.invalid) {
return;
}
item[field] = newValue;
this.store.dispatch(new userList.UpdateUserAction(item));
}
private statusFieldChange(node: RowNode, status: string, isTouched: boolean) {
if (!isTouched) {
return;
}
if (status === 'INVALID') {
if (node.rowHeight < 60) {
this.updateRowSize(node, 60);
}
} else {
setTimeout(() => this.updateRowSize(node, null));
}
}
private updateRowSize(node: RowNode, height: number) {
node.setRowHeight(height);
this.gridOptions.api.onRowHeightChanged();
}
private setButtonTitle(isEditMode: boolean): string {
return isEditMode ? 'Done' : 'Edit';
}
private setUserRoleStatus(roles: fromShared.ERole[], type: fromShared.ERole): boolean {
return roles.includes(type);
}
}

Angular 6 - Get current route and its data

How to get the current route you're in and its data, children and parent?
If this is the route structure:
const routes: Routes = [
{path: 'home', component: HomeComponent, data: {title: 'Home'}},
{
path: 'about',
component: AboutComponent,
data: {title: 'About'},
children: [
{
path: 'company',
component: 'CompanyComponent',
data: {title: 'Company'}
},
{
path: 'mission',
component: 'MissionComponent',
data: {title: 'Mission'}
},
...
]
},
...
]
If I am currently in CompanyComponent, how do I get my current route w/c is Company, get its parent w/c is about, its data and its siblings such as mission, etc.?
#Component({...})
export class CompanyComponent implements OnInit {
constructor(
private router: Router,
private route: ActivatedRoute
) {}
ngOnInit() {
// Parent: about
this.route.parent.url.subscribe(url => console.log(url[0].path));
// Current Path: company
this.route.url.subscribe(url => console.log(url[0].path));
// Data: { title: 'Company' }
this.route.data.subscribe(data => console.log(data));
// Siblings
console.log(this.router.config);
}
}
constructor(
private router: Router,
private route: ActivatedRoute,
) {
}
ngOnInit() {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
map(() => {
return this.getHeaderClasses();
}),
)
.subscribe((headerClasses: string | null) => {
this.headerClasses = headerClasses;
});
this.headerClasses = this.getHeaderClasses();
}
getHeaderClasses(): string | null {
let child = this.route.firstChild;
while (child) {
if (child.firstChild) {
child = child.firstChild;
} else if (child.snapshot.data && child.snapshot.data['headerClasses']) {
return child.snapshot.data['headerClasses'];
} else {
return null;
}
}
return null;
}
routing
{
path: 'list',
component: DialogListComponent,
data: {
headerClasses: 'col-lg-8',
},
},
You can access the route's data property from the snapshot like this:
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
#Component({
templateUrl: './app/home/welcome.component.html'
})
export class WelcomeComponent implements OnInit {
public pageTitle: string;
constructor( private route: ActivatedRoute) {
}
ngOnInit(): void {
this.pageTitle = this.route.snapshot.data['title'];
}
}
#Component({...})
#UntilDestroy()
export class CompanyComponent implements OnInit {
constructor(private router: Router) {}
ngOnInit() {
this.router.events
.pipe(
untilDestroyed(this),
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
map((event: NavigationEnd) => event.url)
)
.subscribe(url=> {
console.log(url);
});
}
}

Set nested JSON Response as rowdata to ag-grid in Angular4

I am new to angular and doing a sample project in which I want to show some JSON data in a grid.
I'm using ag-grid for the same.
I have the following Json response that I'm getting from a rest API :-
[
{
"id": 64,
"name": "Utopia",
"language": "English",
"genres": [
"Drama",
"Science-Fiction",
"Thriller"
],
"status": "Ended",
"image": {
"medium": "http://static.tvmaze.com/uploads/images/medium_portrait/0/474.jpg",
"original": "http://static.tvmaze.com/uploads/images/original_untouched/0/474.jpg"
}
},
{
"id": 65,
"name": "Bones",
"language": "English",
"genres": [
"Drama",
"Crime",
"Medical"
],
"status": "Ended",
"image": {
"medium": "http://static.tvmaze.com/uploads/images/medium_portrait/80/201202.jpg",
"original": "http://static.tvmaze.com/uploads/images/original_untouched/80/201202.jpg"
}
}
]
I was able to successfully bind the data for the simple keys like id, name, language etc. but when it comes to binding the nested object I'm not able to do it.
If you look at the above json response, The 'image' field is an object. How can I get the value of 'medium' or 'original' key from it and just show the image in my row ?
Some help is appreciated, as this is the point I'm getting stuck at.
Below is my component code :-
shows.component.ts
#Component({
selector: 'app-shows',
templateUrl: './shows.component.html',
styleUrls: ['./shows.component.css']
})
export class ShowsComponent implements OnInit {
public gridOptions: GridOptions;
public tvShowsColumnDefs = new ShowColumn;
public showMetaData: any;
constructor(private _contentService: ContentService, private _router: Router,
private _route: ActivatedRoute) {
// GridOptions Initialized
this.gridOptions = <GridOptions>{};
this.gridOptions.columnDefs = this.tvShowsColumnDefs.columnDefs;
}
ngOnInit() {
// Prepare Grid Row Data
this.prepareRowData();
}
prepareRowData() {
// API Call for getting TV-Shows
this._contentService.getAllShows()
.subscribe(response => {
const shows = response;
console.log('TVShows-API Response ', shows);
// Setting Grid RowData using api response
this.gridOptions.api.setRowData(shows);
});
}
show.columnDef.ts
export class ShowColumn {
public columnDefs = [
{ field: 'id', headerName: '', width: 50 },
{ field: 'image', headerName: '', width: 50, cellRendererFramework: null},
{ field: 'name', headerName: '', width: 250},
{ field: 'language', headerName: 'Language', width: 100},
{ field: 'genres', headerName: 'Genres', width: 250},
{ field: 'status', headerName: 'Status', width: 145 }
];
constructor() { }
}
The nested properties are accessible by the dot notation (.), e.g.:
{ field: 'image.medium', headerName: '', width: 50}
For the nested arrays, a value-getter will most likely do the job:
function genreValueGetter(params) {
const arr = params.data.genres as Array<string>;
return arr.join(', ');
}
{ headerName: 'Genres', valueGetter: genreValueGetter, width: 250},
First let me build classes:
export class myShow {
image: myImage;
id: number;
...
constructor(obj: any) {
this.document = new myImage(obj.image);
this.id = obj.id;
...
}
}
export class myImage {
medium: string;
original: string;
constructor(obj?: any) {
if(obj){
this.medium = obj.medium;
this.original = obj.original;
}
}
}
Then you can use .map operator
allShows: myShow[] = [];
prepareRowData(){
this._contentService.getAllShows().map((shows: myShow[])=> {
return shows.map((show: myShow)=>{
return new myShow(show);
})
}).subscribe((allShows)=> {
this.allShows = allShows;
});
}

Angular 4 DataTables. Search placeholder

I have DataTables loaded into my Angular app and was wondering how I can put some text in the search box as a placholder. Also I want the word "Search:" to appear to the left of the box, which I thought was the default but I guess not. Here's what I have plus a screenshot.
component.ts
dtOptions: any = {
lengthChange: false,
dom: 'Bfrtip',
buttons: [
'colvis'
],
aoColumnDefs: [
{ aTargets: [7, 8], bVisible: false}
]
};
dtTrigger: Subject<any> = new Subject();
component.html
<table datatable class="bordered" [dtOptions]="dtOptions" [dtTrigger]="dtTrigger">
HTML
<table datatable [dtOptions]="dtOptions" class="row-border hover"></table>
In .ts file
export class WithOptionsComponent implements OnInit {
dtOptions: DataTables.Settings = {};
ngOnInit(): void {
this.dtOptions = {
searchPlaceholder: 'search here',
};
}
}
export class WithOptionsComponent implements OnInit {
dtOptions: DataTables.Settings = {};
ngOnInit(): void {
this.dtOptions = {
language: {
searchPlaceholder: 'Search user',
}
};
}
}
You can add language property to set the wording.
component.ts
dtOptions: any = {
lengthChange: false,
dom: 'Bfrtip',
buttons: [
'colvis'
],
aoColumnDefs: [
{ aTargets: [7, 8], bVisible: false}
],
language: {
url: '/assets/styles/en.json',
searchPlaceholder: "Search here...",
search: "Search:",
}
};

How to pass data received from service to angular datatable

I have just started working on Angular 4 and I am trying to render some data which I receive from angular service in json format, into angular-datatable, but whichever option i try its not working for me.
The table is coming, the columns are coming, however the data inside the columns are not displaying.
Any help would be great,
Thanks in advance..!!!!
Please find my code below:
component.html
<table datatable [dtOptions]="dtOptions" class="row-border hover"></table>
component.ts
import { Component, OnInit } from '#angular/core';
import { FleetDataService } from '../../services/fleet-data.service';
import { Subject } from 'rxjs/Subject';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
private fleetData: any;
dtOptions: DataTables.Settings = {};
dtTrigger: Subject<any> = new Subject();
constructor(private getFleetData:FleetDataService) { }
ngOnInit() {
this.getFleetData.getFleetData().subscribe(
fleetData => {
this.fleetData = fleetData;
console.log(this.fleetData);
this.dtTrigger.next();
},
err => {
console.log(err);
}
);
this.dtOptions = {
pagingType: 'full_numbers',
columns: [{
title: 'First Name',
data: this.fleetData
}, {
title: 'Last Name',
data: this.fleetData
}, {
title: 'Score',
data: this.fleetData
}]
};
}
}
component.service
import { Injectable } from '#angular/core';
import { HttpModule, Http, Response, Headers, RequestOptions } from
'#angular/http';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class FleetDataService {
constructor(private http: Http) { }
getFleetData() {
return this.http.get("../../assets/data/test.json")
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server
Error'));
}
}
test.json
[{
"FirstName": "Jill",
"LastName": "Smith",
"Score": "disqualified"
}, {
"FirstName": "Eve",
"LastName": "Jackson",
"Score": "94"
}, {
"FirstName": "John",
"LastName": "Doe",
"Score": "80"
}, {
"FirstName": "Adam",
"LastName": "Johnson",
"Score": "67"
}]
You set your dtOptions outside the subscribe.
If you do this the fleetData stays empty so dtOptions is never set correctly, because an Observable is asynchronous. I propose this code:
export class DashboardComponent implements OnInit {
dtOptions: DataTables.Settings = {};
dtTrigger: Subject<any> = new Subject();
constructor(private getFleetData:FleetDataService) { }
ngOnInit() {
this.getFleetData.getFleetData().subscribe(
fleetData => {
console.log(fleetData);
this.buildDtOptions(fleetData)
this.dtTrigger.next();
},
err => {
console.log(err);
});
}
private buildDtOptions(fleetData: any): void {
this.dtOptions = {
pagingType: 'full_numbers',
columns: [
{title: 'First Name', data: fleetData},
{title: 'Last Name', data: fleetData},
{title: 'Score', data: fleetData}
]
};
}
}
For this error: ERROR TypeError: Cannot read property 'aDataSort' of undefined. You can do a spinner (ngIf / else) in the view and when data are loaded you display the datatable