How to update defaultColDef in agGrid - javascript

I am having a custom column filter with a button toggle.By default, the column filter is set to false. When I click on the button the column filter is toggled by setting the floatingFilter: true. While the floatingFilter becomes true during the button click it doesn't show the filter.
Whereas if we make the floatingFilter to be true by default at that time it shows the filter after that if we toggle the button to show/hide the floatingFilter it works expected.
May i know how to update the defaultColDef dynamically in ag-grid to make the floatingFilter to be true during button click.
defaultColDef:
this.defaultColumnDefs = {
suppressMenu: true,
suppressMovable: true,
sortable: true,
resizable: true,
floatingFilter: this.hasFloatingFilter
};
toggleFilter:
toggleFloatingFilter() {
this.hasFloatingFilter = !this.hasFloatingFilter;
this.clearSelectedRows();
this.gridApi.setRowData(this.rowData);
this.defaultColumnDefs = {...this.defaultColumnDefs, floatingFilter: this.hasFloatingFilter};
if (!this.hasFloatingFilter) {
this.gridApi.setFilterModel(null);
this.loadData();
}
setTimeout(() => {
this.gridApi.refreshHeader();
}, 0);
}
GridHTML:
<app-data-grid
[columnDefs]="columnDefs"
[defaultColDef]="defaultColumnDefs"
[overlayNoRowsTemplate]="overlayNoRowsTemplate"
[frameworkComponents]="frameworkComponents"
[rowData]="rowData"
[hasMultipleRows]="rowSelection"
[hasRowAnimation]="hasRowAnimation"
[multiSortKey]="multiSortKey"
(rowDataChanged)="onRowDataChanged()"
(selectionChanged)="onSelectionChanged()"
(rowClicked)="gotoDetailView($event)"
(sortChanged)="onSortChanged($event)"
(columnResized)="onColumnResized()"
(gridReady)="OnGridReady($event)"
>
</app-data-grid>
AppDataGrid Component:
export class DataGridComponent {
gridApi;
gridColumnApi;
constructor() {}
#Input() columnDefs: DeviceColumns;
#Input() rowData: any[];
#Input() overlayNoRowsTemplate: any;
#Input() defaultColDef: any;
#Input() hasMultipleRows: boolean;
#Input() hasRowAnimation: boolean;
#Input() hasFloatingFilter: boolean;
#Input() frameworkComponents: any;
#Input() multiSortKey: string;
#Output() gridReady = new EventEmitter();
#Output() selectionChanged = new EventEmitter();
#Output() rowClicked = new EventEmitter();
#Output() rowDataChanged = new EventEmitter();
#Output() sortChanged = new EventEmitter();
#Output() columnResized = new EventEmitter();
onGridReady(params): void {
this.gridApi = params.api;
this.gridReady.emit(params);
this.gridApi.setGridAutoHeight(true);
}
onSelectionChanged(): void {
this.selectionChanged.emit(this.gridApi);
}
onRowClicked(params): void {
this.rowClicked.emit(params.data);
}
onRowDataChanged(): void {
this.rowDataChanged.emit();
}
onSortChanged(params): void {
this.sortChanged.emit(params.api.getSortModel());
}
onColumnResized() {
this.columnResized.emit(this.gridApi);
}
}
Ag-Grid HTML
<ag-grid-angular
class="ag-theme-balham"
[rowData]="rowData"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[overlayNoRowsTemplate]="overlayNoRowsTemplate"
[frameworkComponents]="frameworkComponents"
(selectionChanged)="onSelectionChanged()"
(rowDataChanged)="onRowDataChanged()"
(rowClicked)="onRowClicked($event)"
(sortChanged)="onSortChanged($event)"
[suppressRowClickSelection]="true"
[rowSelection]="hasMultipleRows"
[animateRows]="hasRowAnimation"
[multiSortKey]="multiSortKey"
(columnResized)="onColumnResized()"
(gridReady)="onGridReady($event)"
>
</ag-grid-angular>
Example: https://plnkr.co/edit/w2UDNd4u657tdr0Q?preview
Current behavior
Not showing the floating filter during button click (When the flaotingFilter is false by default and it is changed to true dynmically)
Expected behavior
It should show the floating filter when
ag-Grid version: 23.2.1

you need to do this with columnDefs instead of defaultColDef. plunkr link
showFilter() {
/*
this.defaultColDef = {...this.defaultColDef, floatingFilter: true};
setTimeout(() => {
this.gridApi.refreshHeader();
}, 0);*/
var columnDefs = this.gridApi.getColumnDefs();
columnDefs.forEach(function (colDef, index) {
colDef.floatingFilter = true;
});
this.gridApi.setColumnDefs(columnDefs);
}
Also AG grid merges defaultColDefs with colDefs while rendering the grid and then uses colDefs object in setupFloatingFilter method thus setting value in defaultColDefs is of no use.
Calling gridApi.setColumnDefs calls HeaderContainer.prototype.init thus rendering your filter component whereas calling refreshHeader interanlly calls gridPanel.setHeaderAndFloatingHeights and headerRootComp.refreshHeader but there is no call to init function which will render your filter component.

To update defaultColDef in agGrid you can use setDefaultColDef method in gridApi passing there the brand new colDef. And do not forget to refresh all headers.
this.gridApi.api.setDefaultColDef({
...this.defaultColDef,
floatingFilter: true
});
this.gridApi.api.refreshHeader();
Hope this will help

Related

Connected Objects in Typescript

I am exploring TS and Angular. I wanted to update my parent component array with and EventEmitter call. I did it so far but then noticed I don't need to cause my parent array is connected to my component variables (I guess so, that why I ask).
I generate my child component dynamically and initialize my child objects from the parent array. Is my template-task task$ Object so a reference to to the $task object from my parent array ?
Parent component:
<div *ngIf="task" class="elements">
<app-template-task (ping)="update($event)" [task$]="task" *ngFor="let task of $tasks"></app-template-task>
</div>
Child component HTML:
<div class="checkbox">
<img *ngIf="task$.done" (click)="changeStatus($event)" src="../../../assets/checked.png" alt="">
<img *ngIf="!task$.done" (click)="changeStatus($event)" src="../../../assets/unchecked.png" alt="">
</div>
<div class="text">
<input type="text" [(ngModel)]="task$.todoText" (blur)="changeText($event)" placeholder="Name des Todo punkts">
</div>
TS from parent:
public task: boolean;
public taskDone: boolean;
public $tasks: Todo[];
public $tasksdone: Todo[];
constructor() {
this.task = true;
this.taskDone = true;
this.$tasks = [
{
id: 0,
todoText: "Task 1",
done: false,
position: 1
},
{
id: 1,
todoText: "Task 2",
done: false,
position: 2
}
]
this.$tasksdone = []
}
TS from Child:
#Input() task$!: Todo; //! erlaubt es das theoretisch task auch null sein darf
#Output() ping: EventEmitter<any> = new EventEmitter<any>();
constructor() {
}
public changeStatus(event? : any): void{
this.task$.done = !this.task$.done
this.sendEvent("changed")
}
public changeText(event? : any): void{
console.log(this.task$.todoText)
this.sendEvent("textChanged")
}
private sendEvent(eventIdentifier: string): void{
const eventObject: Eventping = {
label: eventIdentifier,
object: this.task$
}
this.ping.emit(eventObject)
}
Yes, you're passing a reference up there: [task$]="task"
Having references in data bindings is good. Mutating objects in child components on the other hand is a bad thing to do.
So you should only use your data from an #Input, not change it.
The only place to change data is the parent component (where it's originally located).
I would recommend you making your child component a "dumb" one (means that it only shows data and tells parent what's going on, and the parent changes the data accordingly).
child.component.html :
<div class="checkbox">
<img *ngIf="task.done" (click)="changeStatus()" src="../../../assets/checked.png" alt="">
<img *ngIf="!task.done" (click)="changeStatus()" src="../../../assets/unchecked.png" alt="">
</div>
<div class="text">
<input type="text" [value]="task.todoText" (change)="changeText($event)" placeholder="Name des Todo punkts">
</div>
child.component.ts :
#Input() task: Todo;
// you can check docs on Partial here: https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype
#Output() ping: EventEmitter<any> = new EventEmitter<Partial<Todo>>();
constructor(){}
public changeStatus(): void {
this.ping.next({ done: !this.task$.done });
}
public changeText(newText: string): void {
this.ping.next({ todoText: newText });
}
Then your parent component should handle the updates and finally change your precious data :)
parent.component.html :
<div *ngIf="task" class="elements">
<app-template-task (ping)="update(idx, $event)" [task]="task" *ngFor="let task of $tasks; let idx = index"></app-template-task>
</div>
parent.component.ts :
public task = true;
public taskDone = true;
public tasks: Todo[] = [
{
id: 0,
todoText: "Task 1",
done: false,
position: 1
},
{
id: 1,
todoText: "Task 2",
done: false,
position: 2
}
];
public tasksDone: Todo[] = [];
constructor(){
}
update(taskIdx: number, changes: Partial<Todo>) {
// here we update reference by creating a new array with the .map method
// it's needed for the Angular Change Detection worked.
this.tasks = this.tasks.map((task, idx) => {
if (idx === taskIdx) return { ...task, ...changes };
return task;
});
// do anything else if needed
}
Basically that's it. I hope it'll help :)

Ag-grid button in cell that directs to new screen

I have added the "Edit" button in the grid of the table for every row. The data for the table is coming from the api with the following JSON response.
{
Id: 1783
total: 2
trasfer: true
Sizing: true
name: "runner"
}
I am trying to implement when user clicks the edit button a new screen appears where one can edit the values of that row. So far I have implemented a button rendered component and alert when the button is click. How can I implement router to a new screen along with editable data of that particular row.
Demo: https://ag-grid2.zoltanhalasz.net/
button-renderer.component.ts
#Component({
selector: 'app-button-renderer',
template: `
<button type="button" (click)="onClick($event)">{{label}}</button>
`
})
export class ButtonRendererComponent implements ICellRendererAngularComp {
afterGuiAttached?(params?: import("ag-grid-community").IAfterGuiAttachedParams): void {
return;
}
ngAfterViewInit(): void {
return;
}
public params;
label: string;
constructor(){}
agInit(params: ICellRendererParams): void {
this.params = params;
this.label = this.params.label || null;
}
refresh(params: any): boolean {
return false;
}
public onClick(event) {
this.params.data[this.params.colDef.field] = event.checked;
if (typeof this.params.context.componentParent.notifyCellUpdate === "function"){
this.params.context.componentParent.CellUpdate(this.params);
}
}
}
app.component.ts
columnDef = [ {
headerName: 'Button', field : 'changeSettings',
cellRenderer: 'buttonRenderer',
cellStyle: {'text-align': 'center'},
cellRendererParams: {
label: 'Open'
}
} ]
CellUpdate(params){
if (params.colDef.field === "changeSettings"){
alert("Notified Button Clicked");
}
}
Create a component and navigate to it with input data.
There are many ways for transforming data between components.
Read this article
In your sample demo you should define route with an id and navigate to it's component then call an api to fetch the data.
Also you can open a modal to show the data instead.
Example:
CellUpdate(params){
if (params.colDef.field === "changeSettings"){
const modalRef = this.modalService.open(YourEditComponent);
modalRef.componentInstance.data= params;
}
}

How to show in template property from array of objects

I just try to show the value of a property in the template. But at the moment nothing is shown.
So this is the component:
export class ServerStatusComponent implements OnInit {
snovieCollection: SnovietatusDto = {};
constructor(private snovierStatus: snovieStatusService) {}
ngOnInit(): void {
this.sensorStatus
.getSensorStatuses()
.pipe(
map((data) => {
console.log(data.cameraSensors);
})
)
.subscribe((status) => {
});
}
}
And this is the template:
<p>Camera sensoren</p>
<tr *ngFor="let camera of snovieStatusCollection.key|keyvalue">
test
<h3> {{camera | json}}</h3>
</tr>
So I just want to show in the template the value of key. And the console.log returns this:
0: {key: "T", latestTimestamp: "2021-03-12T10:09:00Z"}
So I don't get any errors. But also nothing is shown.
Two things:
You aren't returning anything from the map. So undefined would be emitted to the subscription. Use tap for side-effects instead.
You aren't assigning the response to this.sensorStatusCollection in the subscription.
export class ServerStatusComponent implements OnInit {
sensorStatusCollection: SensorStatusDto = {};
constructor(private sensorStatus: SensorStatusService) {}
ngOnInit(): void {
this.sensorStatus
.getSensorStatuses()
.pipe(
tap((data) => { // <-- `tap` here
console.log(data.cameraSensors);
})
)
.subscribe((status) => {
this.sensorStatusCollection = status; // <-- assign here
});
}
}
Update: Type
As pointed out by #TotallyNewb in the comments, the type of this.sensorStatusCollection needs to be an array of type SensorStatusDto
export class ServerStatusComponent implements OnInit {
sensorStatusCollection: SensorStatusDto[] = [];
...
}

FormControl validator always invalid

Following my previous question, I'm trying to create a custom validator that allow the users to type only specific values in an input of text.
app.component.ts:
export class AppComponent implements OnInit {
myForm: FormGroup;
allowedValuesArray = ['Foo', 'Boo'];
ngOnInit() {
this.myForm = new FormGroup({
'foo': new FormControl(null, [this.allowedValues.bind(this)])
});
}
allowedValues(control: FormControl): {[s: string]: boolean} {
if (this.allowedValuesArray.indexOf(control.value)) {
return {'notValidFoo': true};
}
return {'notValidFoo': false};
}
}
app.component.html:
<form [formGroup]="myForm">
Foo: <input type="text" formControlName="foo">
<span *ngIf="!myForm.get('foo').valid">Not valid foo</span>
</form>
The problem is that the foo FormControl is always false, (the myForm.get('foo').valid is always false).
What wrong with my implementation?
you just need to return null when validation is ok. and change that method like below
private allowedValues: ValidatorFn (control: FormControl) => {
if (this.allowedValuesArray.indexOf(control.value) !== -1) {
return {'notValidFoo': true};
}
return null;
}

Update ngrx selector inside ngOnChanges

I have a parent component (B) that is getting data from it's parent input (A)
(C) have is (B) child component.
Inside (B) I'm having a selector that gets data from the store.
export class BComponent implements OnChanges {
#Input() branchId;
ngOnChanges() {
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
directionChanged(event) {
this.selectedDirection = event;
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
}
}
directionChanged is the Output event that I get from (C)
The issue this that selectedDataByBranch subscription is not getting the new data update triggered inside selectedDataByBranch$
I have also tried this way
directionChanged(event) {
this.selectedDirection = event;
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection });
}
What i could suggest is. Turn your parameters into a Subject then merge with the store selection, in your directionChanged(event) method provide value to subject.
So your final code will be something like this:
export class BComponent implements OnChanges {
#Input() branchId;
criterias$= new Subject<{branchId:number,dir:number}>;
ngOnChanges() {
this.selectedDataByBranch$ = this.criterias$.pipe(mergeMap(criteria=> this.store.pipe(
select(selectBranchDirections, { branchId: criteria.branchId, dir: this.searchDirection})
)));
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
this.criterias$.next({branchId:this.branchId,dir:this.sortDirection}); // init first call
}
directionChanged(event) {
this.selectedDirection = event;
this.criterias$.next({ branchId: criteria.branchId, dir: this.searchDirection}});
);
}
}
This stackblitz tries to materialize what i say.

Categories

Resources