This is a migrated thread and some comments may be shown as answers.

TableBodyComponent.html - Changed after checked

2 Answers 419 Views
Grid
This is a migrated thread and some comments may be shown as answers.
Ron
Top achievements
Rank 1
Iron
Iron
Ron asked on 30 May 2019, 06:06 PM

I have a general question related to the TableBodyComponent.html. It has an *ngIf that's kicking off a changed after checked error and I'm trying to figure out how the content of the if statement relates to my data.

The particular *ngIf is below the console error in the attached image (in case the image doesn't send properly it's at TableBodyComponent.html: 125). I'm not 100% sure what the content of the if statement conveys and I'm wondering if you can offer any insights as to what's getting hooked on my side that I may need to manually postpone to prevent this error.

2 Answers, 1 is accepted

Sort by
0
Dimiter Topalov
Telerik team
answered on 03 Jun 2019, 12:05 PM
Hi Ron,

There are multiple conditions related to whether a detail row needs to be displayed, whether a master-grid row is expanded, etc., for example related to the conditional display of the detail template:

https://www.telerik.com/kendo-angular-ui/components/grid/advanced-features/detail-template/#toc-conditional-display

To gain a better understanding of the inner logic and mechanics of the various Grid functionalities and rendering logic, you can obtain the whole Grid source code, and inspect it as described in the following section of our documentation:

https://www.telerik.com/kendo-angular-ui/components/installation/source-code/

For example, this is the whole code for the TableBodyComponent:

import { Component, Input, SimpleChange, NgZone, Renderer2, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { GroupDescriptor } from '@progress/kendo-data-query';
import { ColumnBase } from '../columns/column-base';
import { DetailTemplateDirective } from './details/detail-template.directive';
import { DetailsService } from './details/details.service';
import { GroupsService } from '../grouping/groups.service';
import { GroupItem, Item, GroupFooterItem } from '../data/data.iterators';
import { ChangeNotificationService } from '../data/change-notification.service';
import { isChanged, isPresent } from '../utils';
import { NoRecordsTemplateDirective } from './no-records-template.directive';
import { EditService } from '../editing/edit.service';
import { LocalizationService } from '@progress/kendo-angular-l10n';
import { RowClassFn } from './common/row-class';
import { SelectableSettings } from '../selection/selectable-settings';
import { columnsToRender, columnsSpan } from "../columns/column-common";
import { closest, closestInScope, hasClasses, isFocusable, matchesClasses, matchesNodeName } from './common/dom-queries';
import { DomEventsService } from '../common/dom-events.service';
import { SelectionService } from "../selection/selection.service";
import { ColumnInfoService } from "../common/column-info.service";
import { FilterableSettings, hasFilterRow } from '../filtering/filterable';
import { CellKeydownEvent } from '../navigation/cell-keydown-event';
import { Subscription } from 'rxjs/Subscription';
import { NavigationService } from '../navigation/navigation.service';
import { Keys } from '../common/keys';
 
const NON_DATA_CELL_CLASSES = 'k-hierarchy-cell k-detail-cell k-group-cell';
const NON_DATA_ROW_CLASSES = 'k-grouping-row k-group-footer k-detail-row k-grid-norecords';
const IGNORE_TARGET_CLASSSES = 'k-icon';
const IGNORE_CONTAINER_CLASSES = 'k-widget k-grid-ignore-click';
 
const columnCellIndex = (cell, cells) => {
    let cellIndex = 0;
    for (let idx = 0; idx < cells.length; idx++) {
        if (cells[idx] === cell) {
            return cellIndex;
        }
 
        if (!hasClasses(cells[idx], 'k-hierarchy-cell k-group-cell')) {
            cellIndex++;
        }
    }
};
 
 
/**
 * @hidden
 */
@Component({
    selector: '[kendoGridTableBody]', // tslint:disable-line:component-selector
    template: `
    <ng-template [ngIf]="editService.hasNewItem">
        <tr class="k-grid-add-row k-grid-edit-row"
            kendoGridLogicalRow
                [logicalRowIndex]="addRowLogicalIndex()"
                [logicalSlaveRow]="lockedColumnsCount > 0"
                [logicalCellsCount]="columns.length"
                [logicalSlaveCellsCount]="unlockedColumnsCount">
            <ng-template [ngIf]="!skipGroupDecoration">
                <td [class.k-group-cell]="true" *ngFor="let g of groups" role="presentation"></td>
            </ng-template>
            <td [class.k-hierarchy-cell]="true"
                *ngIf="detailTemplate?.templateRef"
                kendoGridLogicalCell
                    [logicalRowIndex]="addRowLogicalIndex()"
                    [logicalColIndex]="0"
                    aria-selected="false"
                >
            </td>
            <td *ngFor="let column of columns; let columnIndex = index"
                kendoGridCell
                    [rowIndex]="-1"
                    [columnIndex]="lockedColumnsCount + columnIndex"
                    [isNew]="true"
                    [column]="column"
                    [dataItem]="newDataItem"
                [ngClass]="column.cssClass"
                [ngStyle]="column.style"
                [attr.colspan]="column.colspan"
                kendoGridLogicalCell
                    [logicalRowIndex]="addRowLogicalIndex()"
                    [logicalColIndex]="logicalColIndex(columnIndex)"
                    [colSpan]="column.colspan"
                role="gridcell">
            </td>
        </tr>
    </ng-template>
    <tr *ngIf="data?.length === 0 || data == null" class="k-grid-norecords">
        <td [attr.colspan]="colSpan">
            <ng-template
                [ngIf]="noRecordsTemplate?.templateRef"
                [templateContext]="{
                    templateRef: noRecordsTemplate?.templateRef
                 }">
            </ng-template>
            <ng-container *ngIf="!noRecordsTemplate?.templateRef">
                {{noRecordsText}}
            </ng-container>
        </td>
    </tr>
    <ng-template ngFor
        [ngForOf]="data"
        [ngForTrackBy]="trackByFn"
        let-item
        let-rowIndex="index">
        <tr *ngIf="isGroup(item) && isParentGroupExpanded(item) && showGroupHeader(item)"
            kendoGridGroupHeader
                [columns]="columns"
                [groups]="groups"
                [item]="item"
                [hasDetails]="detailTemplate?.templateRef"
                [skipGroupDecoration]="skipGroupDecoration"
                [rowIndex]="rowIndex + 1"
                [totalColumnsCount]="totalColumnsCount"
            kendoGridLogicalRow
                [logicalRowIndex]="logicalRowIndex(rowIndex)"
                [logicalSlaveRow]="lockedColumnsCount > 0"
                [logicalCellsCount]="columns.length"
                [logicalSlaveCellsCount]="unlockedColumnsCount">
        </tr>
        <tr
            *ngIf="isDataItem(item) && isInExpandedGroup(item)"
            kendoGridLogicalRow
                [dataRowIndex]="item.index"
                [dataItem]="item.data"
                [logicalRowIndex]="logicalRowIndex(rowIndex)"
                [logicalSlaveRow]="lockedColumnsCount > 0"
                [logicalCellsCount]="columns.length"
                [logicalSlaveCellsCount]="unlockedColumnsCount"
            [ngClass]="rowClass({ dataItem: item.data, index: item.index })"
            [class.k-alt]="isOdd(item)"
            [class.k-master-row]="detailTemplate?.templateRef"
            [class.k-grid-edit-row]="editService.hasEdited(item.index)"
            [attr.data-kendo-grid-item-index]="item.index"
            [class.k-state-selected]="isSelectable() && isRowSelected(item)">
            <ng-template [ngIf]="!skipGroupDecoration">
                <td [class.k-group-cell]="true" *ngFor="let g of groups" role="presentation"></td>
            </ng-template>
            <td [class.k-hierarchy-cell]="true"
                *ngIf="detailTemplate?.templateRef"
                kendoGridLogicalCell
                    [logicalRowIndex]="logicalRowIndex(rowIndex)"
                    [logicalColIndex]="0"
                    [dataRowIndex]="item.index"
                    [dataItem]="item.data"
                    [detailExpandCell]="true"
                    aria-selected="false"
                >
                <a class="k-icon"
                    *ngIf="detailTemplate.showIf(item.data, item.index)"
                    [ngClass]="detailButtonStyles(item.index)"
                    href="#" tabindex="-1" (click)="toggleRow(item.index, item.data)"></a>
            </td>
            <td
                kendoGridCell
                    [rowIndex]="item.index"
                    [columnIndex]="lockedColumnsCount + columnIndex"
                    [column]="column"
                    [dataItem]="item.data"
                kendoGridLogicalCell
                    [logicalRowIndex]="logicalRowIndex(rowIndex)"
                    [logicalColIndex]="logicalColIndex(columnIndex)"
                    [dataRowIndex]="item.index"
                    [dataItem]="item.data"
                    [colIndex]="columnIndex"
                    [colSpan]="column.colspan"
                    role="gridcell" aria-selected="false"
                [ngClass]="column.cssClass"
                [class.k-grid-edit-cell]="editService.isEditedColumn(item.index, column)"
                [ngStyle]="column.style"
                [attr.colspan]="column.colspan"
                *ngFor="let column of columns; let columnIndex = index">
            </td>
        </tr>
        <tr *ngIf="isDataItem(item) && isInExpandedGroup(item) && detailTemplate?.templateRef &&
            detailTemplate.showIf(item.data, item.index) && isExpanded(item.index)"
            [class.k-detail-row]="true"
            [class.k-alt]="isOdd(item)"
            kendoGridLogicalRow
                [dataRowIndex]="item.index"
                [dataItem]="item.data"
                [logicalRowIndex]="logicalRowIndex(rowIndex) + 1"
                [logicalSlaveRow]="false"
                [logicalCellsCount]="1"
            >
            <td [class.k-group-cell]="true" *ngFor="let g of groups"></td>
            <td [class.k-hierarchy-cell]="true"></td>
            <td [class.k-detail-cell]="true"
                [attr.colspan]="columnsSpan"
                kendoGridLogicalCell
                    [logicalRowIndex]="logicalRowIndex(rowIndex) + 1"
                    [logicalColIndex]="0"
                    [dataRowIndex]="item.index"
                    [dataItem]="item.data"
                    [colIndex]="0"
                    [colSpan]="totalColumnsCount - 1"
                    role="gridcell" aria-selected="false"
                >
                <ng-template
                    [templateContext]="{
                        templateRef: detailTemplate?.templateRef,
                        dataItem: item.data,
                        rowIndex: item.index,
                        $implicit: item.data
                        }">
                </ng-template>
            </td>
        </tr>
        <tr *ngIf="isFooter(item) && (isInExpandedGroup(item) || (showGroupFooters && isParentGroupExpanded(item)))
            && !item.data.hideFooter"
            [class.k-group-footer]="true"
            kendoGridLogicalRow
                [logicalRowIndex]="logicalRowIndex(rowIndex)"
                [logicalSlaveRow]="lockedColumnsCount > 0"
                [logicalCellsCount]="columns.length"
                [logicalSlaveCellsCount]="unlockedColumnsCount">
            <ng-template [ngIf]="!skipGroupDecoration">
                <td [class.k-group-cell]="true" *ngFor="let g of groups"></td>
            </ng-template>
            <td [class.k-hierarchy-cell]="true"
                *ngIf="detailTemplate?.templateRef"
                kendoGridLogicalCell
                    [logicalRowIndex]="logicalRowIndex(rowIndex)"
                    [logicalColIndex]="0"
                    aria-selected="false"
                >
            </td>
            <td kendoGridLogicalCell
                    [logicalRowIndex]="logicalRowIndex(rowIndex)"
                    [logicalColIndex]="logicalColIndex(columnIndex)"
                [attr.data-skip]="skipGroupDecoration"
                *ngFor="let column of footerColumns; let columnIndex = index">
                <ng-template
                    [templateContext]="{
                        templateRef: column.groupFooterTemplateRef,
                        group: item.data,
                        field: column.field,
                        column: column,
                        $implicit: item.data?.aggregates
                        }">
                </ng-template>
           </td>
        </tr>
    </ng-template>
    `
})
export class TableBodyComponent implements OnInit, OnDestroy {
    @Input() public columns: Array<ColumnBase> = [];
    @Input() public groups: Array<GroupDescriptor> = [];
    @Input() public detailTemplate: DetailTemplateDirective;
    @Input() public noRecordsTemplate: NoRecordsTemplateDirective;
    @Input() public data: Array<GroupItem | Item | GroupFooterItem>;
    @Input() public skip: number = 0;
    @Input() public selectable: SelectableSettings | boolean;
    @Input() public filterable: FilterableSettings;
    @Input() public noRecordsText: string = this.localization.get('noRecords');
 
    @Input() public skipGroupDecoration: boolean = false;
    @Input() public showGroupFooters: boolean = false;
    @Input() public lockedColumnsCount: number = 0;
    @Input() public totalColumnsCount: number = 0;
 
    private clickSubscription: Function;
    private cellKeydownSubscription: Subscription;
    private clickTimeout: any;
 
    @Input() public rowClass: RowClassFn = () => null;
 
    constructor(
        public detailsService: DetailsService,
        public groupsService: GroupsService,
        private changeNotification: ChangeNotificationService,
        public editService: EditService,
        private localization: LocalizationService,
        private ngZone: NgZone,
        private renderer: Renderer2,
        private element: ElementRef,
        private domEvents: DomEventsService,
        public selectionService: SelectionService,
        private columnInfoService: ColumnInfoService,
        private navigationService: NavigationService
    ) {
        this.cellKeydownSubscription = this.navigationService.cellKeydown.subscribe((args) => this.cellKeydownHandler(args));
    }
 
    public get newDataItem(): any {
        return this.editService.newDataItem;
    }
 
    // Number of unlocked columns in the next table, if any
    public get unlockedColumnsCount(): number {
        return this.totalColumnsCount - this.lockedColumnsCount - this.columns.length;
    }
 
    public toggleRow(index: number, dataItem: any): boolean {
        this.detailsService.toggleRow(index, dataItem);
        return false;
    }
 
    public trackByFn = (index: number, item: any): any => {
        if (item.type === 'data' && this.editService.hasEdited(item.index)) {
            return item.data;
        }
        return index;
    }
 
    public isExpanded(index: number): boolean {
        return this.detailsService.isExpanded(index);
    }
 
    public detailButtonStyles(index: number): any {
        const expanded = this.isExpanded(index);
        return expanded ? 'k-minus' : 'k-plus';
    }
 
    public isGroup(item: Item | GroupItem): boolean {
        return item.type === 'group';
    }
 
    public isDataItem(item: Item | GroupItem): boolean {
        return !this.isGroup(item) && !this.isFooter(item);
    }
 
    public isFooter(item: Item | GroupItem | GroupFooterItem): boolean {
        return item.type === 'footer';
    }
 
    public isInExpandedGroup(item: Item): boolean {
        return this.groupsService.isInExpandedGroup(item.groupIndex, false);
    }
 
    public isParentGroupExpanded(item: any): boolean {
        return this.groupsService.isInExpandedGroup(item.index || item.groupIndex);
    }
 
    public isOdd(item: any): boolean {
        return item.index % 2 !== 0;
    }
 
    public isSelectable(): boolean {
        return this.selectable && (<SelectableSettings>this.selectable).enabled !== false;
    }
 
    public isRowSelected(item: any): boolean {
        return this.selectionService.isSelected(item.index);
    }
 
    public ngOnChanges(changes: { [propertyName: string]: SimpleChange }): void {
        if (isChanged("columns", changes, false)) {
            this.changeNotification.notify();
        }
    }
 
    public logicalRowIndex(rowIndex: number): number {
        let pos = this.skip + rowIndex;
        if (this.hasDetailTemplate) {
            pos *= 2;
        }
 
        const absoluteRowIndex = 1 + pos;
        const addRowOffset = this.editService.hasNewItem ? 1 : 0;
        const filterRowOffset = hasFilterRow(this.filterable) ? 1 : 0;
        const headerRowCount = this.columnInfoService.totalLevels + filterRowOffset + addRowOffset;
 
        return absoluteRowIndex + headerRowCount;
    }
 
    public addRowLogicalIndex(): number {
        return this.columnInfoService.totalLevels + 1;
    }
 
    public logicalColIndex(colIndex: number): number {
        return this.lockedColumnsCount + colIndex + (this.hasDetailTemplate ? 1 : 0);
    }
 
    public ngOnInit(): void {
        this.ngZone.runOutsideAngular(() => {
            const clickHandler = this.clickHandler.bind(this);
            const mousedownSubscription = this.renderer.listen(this.element.nativeElement, 'mousedown', clickHandler);
            const clickSubscription = this.renderer.listen(this.element.nativeElement, 'click', clickHandler);
            const contextmenuSubscription = this.renderer.listen(this.element.nativeElement, 'contextmenu', clickHandler);
 
            this.clickSubscription = () => {
                mousedownSubscription();
                clickSubscription();
                contextmenuSubscription();
            };
        });
 
        let originalNoRecordText = this.localization.get('noRecords');
 
        this.localization.changes.subscribe(() => {
            if (this.noRecordsText === originalNoRecordText) {
                this.noRecordsText = this.localization.get('noRecords');
                originalNoRecordText = this.noRecordsText;
            }
        });
    }
 
    public ngOnDestroy(): void {
        if (this.clickSubscription) {
            this.clickSubscription();
        }
 
        this.cellKeydownSubscription.unsubscribe();
 
        clearTimeout(this.clickTimeout);
    }
 
    public get columnsSpan(): number {
        return columnsSpan(this.columns);
    }
 
    public get colSpan(): number {
        return this.columnsSpan + this.groups.length + (this.hasDetailTemplate ? 1 : 0);
    }
 
    public get footerColumns(): ColumnBase[] {
        return columnsToRender(this.columns);
    }
 
    public showGroupHeader(item: any): boolean {
        return !item.data.skipHeader;
    }
 
    private get hasDetailTemplate(): boolean {
        return isPresent(this.detailTemplate);
    }
 
    private clickHandler(eventArg: any): void {
        const target = eventArg.target;
        const cell = closest(target, matchesNodeName('td'));
        const row = closest(cell, matchesNodeName('tr'));
        const body = closest(row, matchesNodeName('tbody'));
 
        if (cell && !hasClasses(cell, NON_DATA_CELL_CLASSES) &&
            !hasClasses(row, NON_DATA_ROW_CLASSES) &&
            body === this.element.nativeElement) {
 
            this.editService.preventCellClose();
 
            const focusable = target !== cell && isFocusable(target, false);
            if (!focusable && !matchesNodeName('label')(target) && !hasClasses(target, IGNORE_TARGET_CLASSSES) &&
                !closestInScope(target, matchesClasses(IGNORE_CONTAINER_CLASSES), cell)) {
                const args = this.cellClickArgs(cell, row, eventArg);
 
                if (eventArg.type === 'mousedown') {
                    this.domEvents.cellMousedown.emit(args);
                } else {
                    if (args.isEditedColumn || !this.editService.closeCell(eventArg)) {
                        if (eventArg.type === 'click') {
                            this.clickTimeout = setTimeout(() => {
                                this.emitCellClick(args);
                            }, 0);
                        } else {
                            this.emitCellClick(args);
                        }
 
                    }
                }
            }
        }
    }
 
    private emitCellClick(args: any): void {
        this.domEvents.cellClick.emit(Object.assign(args, {
            isEdited: args.isEditedRow || args.isEditedColumn
        }));
    }
 
    private cellKeydownHandler(args: CellKeydownEvent): void {
        const body = closest(args.rowElement, matchesNodeName('tbody'));
        if (body !== this.element.nativeElement) {
            return;
        }
 
        if (args.keyCode === Keys.enter) {
            const clickArgs = this.cellClickArgs(args.cellElement, args.rowElement, args.originalEvent);
            this.domEvents.cellClick.emit(clickArgs);
        }
    }
 
    private cellClickArgs(cell: any, row: any, eventArg: any): any {
        const index = columnCellIndex(cell, row.cells);
        const column = (this.columns as any).toArray()[index];
        const columnIndex = this.lockedColumnsCount + index;
        let rowIndex = row.getAttribute('data-kendo-grid-item-index');
        rowIndex = rowIndex ? parseInt(rowIndex, 10) : -1;
 
        const dataItem = rowIndex === -1 ? this.editService.newDataItem : (this.data as any).at(rowIndex - this.skip);
        const isEditedColumn = this.editService.isEditedColumn(rowIndex, column);
        const isEditedRow = this.editService.isEdited(rowIndex);
 
        return {
            column: column,
            columnIndex: columnIndex,
            dataItem: dataItem,
            isEditedColumn: isEditedColumn,
            isEditedRow: isEditedRow,
            originalEvent: eventArg,
            rowIndex: rowIndex,
            type: eventArg.type
        };
    }
}

I am afraid the provided information is not enough for us to determine what exactly is triggering the described error, but if you send us an isolated runnable project (or a Stackblitz example, similar to our online demos) where the described error can be observed, we will investigate it further, and try to determine what is causing and suggest a solution. Thank you in advance.

Regards,
Dimiter Topalov
Progress Telerik
Get quickly onboarded and successful with your Telerik and Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Ron
Top achievements
Rank 1
Iron
Iron
answered on 03 Jun 2019, 03:02 PM

Dimiter,

That's exactly the information I needed actually and in one of the cases it was a loop I was performing to open detail rows which was occurring while the loading checks were still happening for some of the other forms. This just helped me narrow my field of inquiry enough to get under it.

Tags
Grid
Asked by
Ron
Top achievements
Rank 1
Iron
Iron
Answers by
Dimiter Topalov
Telerik team
Ron
Top achievements
Rank 1
Iron
Iron
Share this question
or