setColumnDefs does not work in Angular 5 - javascript

As what has been suggested elsewhere,
setColumnDefs is not working for some ag-grids
How to initialize ag-grid api in angular2 application
I have already initialized the gridOptions in my class constructor. But when I tried to setColumnDefs, it still gave me error:
TypeError: Cannot read property 'setColumnDefs' of undefined
What else am I missing here?
export class ConfigurationComponent implements OnInit {
constructor(
private configurationService: ConfigurationService,
)
{
this.gridOptions = {
enableSorting: false,
rowData: this.tableData,
columnDefs: this.tableColumns,
onGridReady: () => {
this.gridOptions.api.sizeColumnsToFit();
this.gridOptions.api.setColumnDefs(this.tableColumns);
alert(this.gridOptions.api);
}
}
}
tableData: string[] = [];
tableList: string[] = [];
tableName: string;
tableColumns: [{headerName: string, field: string}] = [{headerName: "", field: ""}];
tableRecord: {};
gridOptions: GridOptions;
ngOnInit() {
this.retrieveTableList();
}
retrieveTableList(){
/*blah blah*/
}
retrieveTableData(){
/*blah blah*/
this.configurationService.retrieveTableData(this.schemaFullname, this.tableName).subscribe(data => {
/* GETTING tableColumn HERE from the service*/
this.gridOptions.api.setColumnDefs(this.tableColumns);
}, error => {
console.error(error);
this.alertService.error("Get table data error", "No table data retrieved from data source for " + this.tableName);
})
}
}

As your comment says,
It works now after I added [gridOptions]="gridOptions" in html.
Do you know why it worked?
As in your code, you are defining gridOptions in your constructor. In your onGridReady function, nobody knows from where api property (and sizeColumnsToFit, etc methods) gets added.
this.gridOptions = {
enableSorting: false,
rowData: this.tableData,
columnDefs: this.tableColumns,
onGridReady: () => {
this.gridOptions.api.sizeColumnsToFit();
this.gridOptions.api.setColumnDefs(this.tableColumns);
alert(this.gridOptions.api);
}
}
When you add [gridOptions]="gridOptions" in your component, ag-grid component uses the gridOptions object and injects other apis for you. Hence, it works afterwards.

Related

Karma: TypeError: Cannot read properties of undefined (reading 'subscribe')

I am trying to test a method (refreshMemberList()) that contains a subscribe, but when I run the tests I get this error from Karma. Can anyone tell me what is going on? Thank you very much in advance, such a great community you are :D
Jasmine spec list failure:
MemberListComponent > refreshMemberList refreshes members list
TypeError: Cannot read properties of undefined (reading 'subscribe')
member-list.component.ts:
describe('MemberListComponent', () => {
let component: MemberListComponent;
let fixture: ComponentFixture<MemberListComponent>;
let mockMemberListService: jasmine.SpyObj<MemberListService>;
let modalService: NgbModal;
beforeEach(async () => {
mockMemberListService = jasmine.createSpyObj(['getMembers']);
mockMemberListService.getMembers.and.returnValue(memberMockObject.fakeMemberListJSON);
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
{
provide: MemberListService,
useValue: mockMemberListService,
}
],
declarations: [MemberListComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MemberListComponent);
component = fixture.componentInstance;
modalService = TestBed.get(NgbModal);
});
it('refreshMemberList refreshes members list', () => {
component.refreshMemberList(memberMockObject.dataMemberMap);
fixture.detectChanges();
expect(component.members).toEqual(memberMockObject.fakeMembers);
});
});
member-list.component.ts:
export class MemberListComponent implements OnInit {
public title: String = "Members";
public showMemberDeletedAlert: Boolean;
private _memberListService: MemberListService;
public members: Member[] = [];
private dataMemberMap: Map<string, string>;
public page: number = 0;
public pageSize: number = 10;
public collectionSize: number;
public isLoading: boolean = true;
constructor(
memberListService: MemberListService,
private _modalService: NgbModal
) {
this._memberListService = memberListService;
}
ngOnInit(): void {
if(history.state.type==="delete")
this.showDeletedAlert()
this.dataMemberMap = new Map([
["name", ""],
["role", ""],
["skill", ""],
["skillLevel", ""],
["language", ""],
["languageLevel", ""]
]);
this.retrieveMemberList();
}
retrieveMemberList(): void {
this.isLoading = true;
this._memberListService.getMembers(this.dataMemberMap!, this.page, this.pageSize).subscribe((data) => {
this.members = data.memberList;
this.collectionSize = data.totalPages * this.pageSize;
this.isLoading = false;
});
}
refreshMemberList(dataMemberMap: Map<string, string>): void {
this.dataMemberMap = dataMemberMap;
this.retrieveMemberList();
}
}
Why do you have a class property of type MemberListService? Just use the dependency injection as
constructor(private memberListService: MemberListService) {}
Then just use it as:
...
this.memberListService.getMembers(...);
Also, you have all this logic been triggered right from the beginning, in your ngOnInit lifecycle hook, which is fine; the first test you should have for this component should be this one:
it('should create', () => {
expect(component).toBeTruthy();
});
This test (which is the first test by default) verifies the successful creation of your component, so any logic within your ngOnInit will be tested, some of this logic is calling your getMembers service function sending 3 parameters, maybe some or all of those are undefined?
Also, I don't know what memberMockObject is in your test file, so for setting the return type, I would say use of from rxjs to create an observable:
mockMemberListService.getMembers
.and.returnValue(of(yourDesiredValueHere));
It is undefined, so can't read the subscribe property, since you want this (_memberListService) to be private, you can just use (memberListService) on the constructor as private and do not use _memberListService, it'd something like
constructor(
private memberListService: MemberListService,
private _modalService: NgbModal
) {}
and when you call getMembers you can just use this.memberListService.getMembers(...

How to replace all the columns dynamically in Data Table using Angular2+?

My requirement to replace the all the columns when ever the changes/event is happening outside of the Data Table.
data table is displaying for the first time with selected columns(from event). if i select second one it's not displaying but columns in dtOptions getting changed but it's not displaying. I think clearing the view the problem but i tried using destroy it's not working out for me. some one please help me to achieve this.
HTML Code:
<div id="data-table-grid-slide">
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="row-border hover"></table>
</div>
Angular Code for DataTable:
import {Component, ViewChild, OnInit, Input, AfterViewInit, OnDestroy, Output, EventEmitter} from '#angular/core';
import { DataTableDirective } from 'angular-datatables';
import { Subject } from 'rxjs';
import { ColumnObject } from '../data-tables-net/model/data-tables-model';
import { HttpClient } from '#angular/common/http';
import { DtServiceService} from '../data-tables-net/dt-service.service';
import { WindowRef} from '../services/WindowRef';
declare var $;
#Component({
selector: 'app-data-tables-net',
templateUrl: './data-tables-net.component.html',
styleUrls: ['./data-tables-net.component.css']
})
export class DataTablesNetComponent implements OnInit, AfterViewInit, OnDestroy {
#ViewChild('dataTable', {static: true}) table;
#ViewChild(DataTableDirective, {static: false}) dtElement: DataTableDirective;
dataTableColumn: Array<any> = [];
dtOptions: DataTables.Settings = {};
#Input() dataTableGrid: boolean;
#Input() tableShow: boolean;
#Output() tableShowChange = new EventEmitter<boolean>();
#Output() dataTableGridChange = new EventEmitter<boolean>();
dtTrigger: Subject<any> = new Subject();
// editor: any;
columnObject: ColumnObject = {
title: '',
data: ''
};
constructor(private http: HttpClient, private dtServiceService: DtServiceService, private winRef: WindowRef) { }
ngOnInit() {
this.dataTableGrid = true;
this.initDt();
}
ngAfterViewInit(): void {
// This method get called on pencil click of model in Data Model Visuvalizer
this.winRef.modelClick$.subscribe((modelObjAttributes) => {
this.dataTableGrid = true;
this.tableShow = false;
this.tableShowChange.emit(this.tableShow);
this.dataTableGridChange.emit(this.dataTableGrid);
console.log('modelObjAttributes', modelObjAttributes);
// tslint:disable-next-line: max-line-length
// this.dtOptions.columns = [{title: 'id', data: 'id'}, {title: 'name', data: 'name'}, {title: 'item code', data: 'item code'}, {title: 'addr', data: 'addr'}];
if (this.dtOptions.columns) {
// this.dtOptions.destroy = true;
// delete this.dtOptions.columns;
this.reRenderDataTable();
// console.log('columns', this.dtOptions.columns);
this.initDt();
this.dtOptions.columns = this.getModelDetails(modelObjAttributes);
// console.log(this.dtOptions.columns);
this.dtTrigger.next();
} else {
this.dtOptions.columns = this.getModelDetails(modelObjAttributes);
console.log(this.dtOptions.columns);
this.dtTrigger.next();
// this.dtOptions.destroy = true;
}
// delete this.dtOptions.columns;
});
}
initDt() {
this.dtOptions = {
// ajax: 'data/data.json',
// columns: [{title: 'Column1', data: 'column1'}],
paging: true,
searching: true,
ordering: true,
info: false,
responsive: true,
destroy: true
};
}
ngOnDestroy(): void {
// Do not forget to unsubscribe the event
this.dtTrigger.unsubscribe();
}
// This method used to get the details of model on clicking of pencil icon
getModelDetails(modelDetailsObj) {
return this.convertModelAttributesToDataTable(modelDetailsObj.options);
// this.getModelDetailsFromService(modelDetailsObj.id);
}
// This method is used to call the service to get the selected Models / Schema details from Database
getModelDetailsFromService(schemaId): void {
this.dtServiceService.getSelectedSchema(schemaId).subscribe(data => {
console.log(data);
},
error => {
console.log('Data is not getting');
});
}
// This method used to form the schema data for Data Table
convertModelAttributesToDataTable(attributesObject) {
this.dataTableColumn = [];
// delete this.dtOptions.columns;
for (const [index, obj] of attributesObject.entries()) {
if (obj) {
this.columnObject = { title: obj.text, data: obj.text};
console.log('columnObject', this.columnObject);
this.dataTableColumn.push(this.columnObject);
// console.log(this.dtOptions);
}
}
// this.dtTrigger.next();
return this.dataTableColumn;
}
// This method used re-render the data table with updated data's
reRenderDataTable(): void {
this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
// Destroy the table first
// dtInstance.destroy();
// Call the dtTrigger to rerender again
this.dtTrigger.next();
});
}
}
I have created stackblitz for my requirement. In this example variables called columnsDataObj and dataUrl will change dynamically. it should get reflect in the data table. Please let me if you need more details:
https://stackblitz.com/edit/angular-datatables-gitter-4tavmk?file=app/app.component.ts
You need to also "destroy" dtOptions and the previous table from the template, also you have to make sure your template notices when the change is done, this are the main changes:
First destroy your previous DT completely, i added a flag called dtRendered:
dtRendered = true;
<table *ngIf="dtRendered" datatable [dtOptions]="dtOptions" class="row-border hover">
Also at your update method you have to make sure everything is destroyed and initialized again:
updateData() {
// destroy you current configuration
this.dtRendered = false
this.dtOptions = {
data: this.jsonData1.data,
columns: this.columnsDataObj1
};
// make sure your template notices it
this.cdr.detectChanges();
// initialize them again
this.dtRendered = true
this.cdr.detectChanges();
}
The this.cdr.detectChanges() call is needed so the lifecycle-hook notices about the change.
Here's your example working as expected:
https://stackblitz.com/edit/how-to-replace-all-the-columns-dynamically-in-data-table?file=app/app.component.ts

Angular DataTable not sorting asynchronous data

On a datatable that I am using, I am having mixed results when attempting to sort them by using the toggles in the table headers. It appears that columns that get populated by the response in my initial GET call populate and sort as expected. However, the columns with data that comes from the GET calls inside the outer subscription don't sort as expected. Ideally all columns would be able to sort properly.
When wrapping the this.dtTrigger.next() in a setTimeout() of 5000ms, all columns sorted as expected. So the question here is, how would I wait until the innermost GET call finishes to call the dtTrigger.next()so that all of the columns will be sortable once all of the data is finished loading in.
import { Component, OnInit, Input, OnChanges } from '#angular/core';
import { Subject } from 'rxjs';
import { BuildersService } from '#/services/builders/builders.service';
import { CommunitiesService } from '#/services/communities/communities.service';
import { ContactsService } from '#/services/contacts/contacts.service';
import { DataTableDirective } from 'angular-datatables';
#Component({
selector: 'datatable-builders',
templateUrl: './builder-datatable.component.html',
styleUrls: ['./builder-datatable.component.scss']
})
export class BuilderDatatableComponent implements OnInit {
#Input() title: string = '';
#Input() showFooter: boolean = true;
#Input() rowsPerPage: number = 10;
#Input() viewRoute: string = '';
#Input() viewText: string = 'View';
rows = [];
dtOptions: DataTables.Settings = {};
dtTrigger: Subject<any> = new Subject();
constructor(
private buildersService: BuildersService,
private communitiesService: CommunitiesService,
private contactsService: ContactsService
) { }
dataTablesInit() {
this.dtOptions = {
pagingType: 'simple_numbers',
lengthChange: false,
info: this.showFooter,
paging: this.showFooter,
columnDefs: [
{
targets: [4],
orderable: false,
searchable: false
}
],
initComplete: () => {
let searchLabels = document.querySelectorAll('.dataTables_filter > label');
searchLabels.forEach((label) => {
label.setAttribute('aria-label', 'Search/Filter Table');
});
}
};
}
ngOnInit() {
this.dataTablesInit();
// Get the table data
this.buildersService.getBuilders().subscribe((result: any) => {
// Get all Rows
let rows = result.body.map(row => {
let communities = [],
communitiesColumn = [],
managersColumn = [];
// Get the builders info and set up output
row.CommunityIDs.forEach((id) => {
this.communitiesService.getCommunity(id).subscribe((result: any) => {
communities.push(result.body);
communitiesColumn.push( result.body.Name );
});
});
row.Contacts.Managers.forEach((id) => {
this.contactsService.getContact(id).subscribe((result: any) => {
managersColumn.push(result.body);
});
});
console.log(row);
// Set additional row data
row.Communities = communities;
row.CommunitiesColumn = communitiesColumn;
row.ManagersColumn = managersColumn;
return row;
});
this.rows = result.body;
this.dtTrigger.next();
console.log(this.rows)
});
}
}
This is the synchronicity of JavaScript in general, you can use the keyword Async in the top level GET and Await keyword in the down level GET
e.g :
// Get the table data
async this.buildersService.getBuilders().subscribe((result: any) => {
...
//
// Get the builders info and set up output
row.CommunityIDs.forEach((id) => {
await this.communitiesService.getCommunity(id).subscribe((result: any) => {
...
//
communities.push(result.body);
communitiesColumn.push( result.body.Name );
});
});
row.Contacts.Managers.forEach((id) => {
this.contactsService.getContact(id).subscribe((result: any) => {
managersColumn.push(result.body);
});
});
console.log(row);
// Set additional row data
row.Communities = communities;
row.CommunitiesColumn = communitiesColumn;
row.ManagersColumn = managersColumn;
return row;
});
this.rows = result.body;
this.dtTrigger.next();
console.log(this.rows)
});
}
}

Why canMakePayment returns null sometimes in following case

I'm using stripe-js.
When I call this.initializePaymentRequest() at following code,
If I call initializePaymentRequest() from first observable, canMakePayment() returns Object, and I'm able to see that google pay is supported in browser.
If I call initializePaymentRequest() from this.datas.subscribe, I'm getting null from canMakePayment() which is not true. I'm still same tab, and google pay is supported.
export class DatasComponent implements OnInit {
datas: any;
data: any;
data2s: any;
data2: any;
paymentRequest: any;
private isStripeAvailable: boolean;
constructor(
private db: AngularFirestore,
private paymentService: PaymentService
) {
// stripe js load status listener (true/false)
paymentService.stripeStatus.asObservable().subscribe(data2 => {
this.isStripeAvailable = !!data2;
if ((this.data || {}).val) {
// /******************** works here ****************
this.initializePaymentRequest();
}
});
this.slug1 = 'hello', this.slug2 = 'hi';
this.data2s = db
.collection('data2s', ref => ref
.where('slug', '==', this.slug1)
).valueChanges();
this.data2s.subscribe(data3 => {
if (data3.length) {
this.data2 = data[0];
this.datas = db
.collection('datas', ref => ref
.where('slug', '==', this.slug2)
)
.valueChanges();
this.datas.subscribe(data4 => {
if (data4.length) {
this.data = data4[0];
if (this.isStripeAvailable) {
// /*************** doesn't work here ********
this.initializePaymentRequest();
}
}
});
}
});
}
initializePaymentRequest = () => {
this.paymentRequest = this.paymentService.stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Sample Payment',
amount: 500,
},
requestPayerName: true,
requestPayerEmail: true,
requestPayerPhone: true,
});
this.paymentRequest.canMakePayment().then(data => {
// data is object if called from first, null if called from second
debugger;
});
}
}
Why would this happen?
Update
I can see that if I call initializePaymentRequest() within setTimeout, It is returning null too. Is there any way set timeout is breaking payment apis?

How to call a REST service for every row, of a json array, and replace one of the values

I have the following code in typescript which calls a sharepoint rest api and gets lists items, however sharepoint internally saves thes user profiles with an id, instead of the name, so you can see number 9 actually should be a person name.
What I want is to replace that number 9 with my name, I already know what service I need to call, however I dont know how do it for each row of the json returned and to replace the 9 with the name returned.
My code is as follow:
/// <reference path="../../../typings/jquery/jquery.d.ts" />
/// <reference path="../../../typings/jquery.dataTables/jquery.dataTables.d.ts" />
import {
BaseClientSideWebPart,
IPropertyPaneSettings,
IWebPartContext,
PropertyPaneTextField
} from '#microsoft/sp-client-preview';
//import styles from './Pnpcrudsample.module.scss';
import ModuleLoader from '#microsoft/sp-module-loader';
import * as strings from 'pnpcrudsampleStrings';
import { IPnpcrudsampleWebPartProps } from './IPnpcrudsampleWebPartProps';
//import * as pnp from 'sp-pnp-js';
import MockHttpClient from './MockHttpClient';
import { EnvironmentType } from '#microsoft/sp-client-base';
require('jquery');
require('datatables');
export interface ISPLists {
value: ISPList[];
}
export interface ISPList {
Title?: string;
Id: number;
}
export interface IListItems{
value: IListItem[];
}
//Title,h7vv,v7nw,mczsId,mczsStringId,BooleanColumn
export interface IListItem {
Title: string;
h7vv: string;
v7nw: string;
mczsId: string;
BooleanColumn: string;
}
export default class PnpcrudsampleWebPart extends BaseClientSideWebPart<IPnpcrudsampleWebPartProps> {
//private container: JQuery;
//Default constructor, here we have to load css
public constructor(context: IWebPartContext) {
super(context);
ModuleLoader.loadCss('//cdn.datatables.net/1.10.12/css/jquery.dataTables.min.css');
}
///Gets data from the mock, fake data
private _getMockListData(): Promise<IListItems> {
return MockHttpClient.get(this.context.pageContext.web.absoluteUrl)
.then((data: IListItem[]) => {
var listData: IListItems = { value: data };
return listData;
}) as Promise<IListItems>;
}
///Checks if the environment is local, then we will load data from mock, if not from the list
private _renderListAsync(): void {
// Local environment
if (this.context.environment.type === EnvironmentType.Local) {
this._getMockListData().then((response) => {
this._renderList(response.value);
});
}
else{
this._getListData()
.then((response) => {
this._renderList(response.value);
});
}
}
//Title,h7vv,v7nw,mczsId,mczsStringId,BooleanColumn
///Render list on the datatable
private _renderList(items: IListItem[]): void {
$('#example').DataTable({
data: items,
columns: [
{ "data": "Title" },
{ "data": "h7vv" },
{ "data": "v7nw" },
{ "data": "mczsId" },
{ "data": "BooleanColumn" }
]
});
}
///Get list data
private _getListData(): Promise<IListItems> {
return this.context.httpClient.get(this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Lista')/items?$select=Title,h7vv,v7nw,mczsId,mczsStringId,BooleanColumn`)
.then((response: Response) => {
return response.json();
});
}
/// Generar contenido HTML
public render(): void {
debugger;
ModuleLoader.loadCss('//cdn.datatables.net/1.10.12/css/jquery.dataTables.min.css');
if (this.renderedOnce === false) {
this.domElement.innerHTML = `<table id="example" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Title</th>
<th>NumberColumn</th>
<th>DateColumn</th>
<th>PersonColumn</th>
<th>BooleanColumn</th>
</tr>
</thead>
</table>`;
}
this._renderListAsync();
}
//Property pane fields
protected get propertyPaneSettings(): IPropertyPaneSettings {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
}
import { IListItem } from './PnpcrudSampleWebPart';
export default class MockHttpClient {
//Title,h7vv,v7nw,mczsId,mczsStringId,BooleanColumn
private static _items: IListItem[] =
[
{ Title: 'Mock List', h7vv: '1',v7nw :'01-01-2016',mczsId:'Luis Esteban Valencia',BooleanColumn:'Yes' },
{ Title: 'Mock List2', h7vv: '1',v7nw :'01-01-2016',mczsId:'Luis Esteban Valencia',BooleanColumn:'Yes' },
];
public static get(restUrl: string, options?: any): Promise<IListItem[]> {
return new Promise<IListItem[]>((resolve) => {
resolve(MockHttpClient._items);
});
}
}
The importan piece of the code above is: the _renderlist method which gets the data and BINDS it to a datatable (jquery plugin), I guess its there where I should plug the code to call the other service which is something like this:
https://mytenant.sharepoint.com/sites/devsitesc1/_api/web/getuserbyid(9)/title
No need of 2 REST calls here. The REST API call should look like...
https://mytenant.sharepoint.com/sites/devsitesc1/_api/web/lists/getByTitle('YourListTitle')/items?$select=Title,NumberColumn,...,PersonColumn/Title&$expand=PersonColumn
if you have any filter, you can add "$filter=..." also

Categories

Resources