Fel
Fel

Reputation: 4828

Force change detection on all components in Angular 6 app

I'm developing a simple grid component for an Angular 6 application. Basically, it consists of a main component, app-grid, which is an HTML table. Inside of it there are a determinate number of rows, that are another component (app-row). Finally, inside of each row, there are some cells that hold objects, and are inside another component: app-cell.

Here's a diagram of the architecture:

Component architecture

In an Angular-like template notation, the code for building the above components looks like:

<app-grid>
    <app-row *ngFor="let row of grid.rows; let i=index">
        <tr [ngClass]="{ 'row-highlighted' : row.highlighted }">
            <td> #{{ i }} </td>
            <app-cell *ngFor="let cell of row.cells">
                <td [ngClass]="{ 'cell-highlighted' : cell.highlighted }">
                </td>
            </app-cell>
        </tr>
    </app-row>
</app-grid>

The internal data structure to build this is stored in the app-grid component, and it looks like this:

grid = {
    title: 'SAMPLE GRID',        

    rows: [
        { 
          row_id: 20, 
          cells: [
            { cell_id: 201, cell_data: { DATA IN HERE } },
            { cell_id: 202, cell_data: { DATA IN HERE } },
            { cell_id: 203, cell_data: { DATA IN HERE } }
          ]
        },
        { 
          row_id: 30, 
          cells: [
            { cell_id: 301, cell_data: { DATA IN HERE } },
            { cell_id: 302, cell_data: { DATA IN HERE } },
            { cell_id: 303, cell_data: { DATA IN HERE } }
          ]
        }
    ]
};

To improve performance, both app-row and app-cell components are coded using changeDetection: ChangeDetectionStrategy.OnPush, so I trigger the change detection manually.

I'd like to be able to highlight an specific cell (for example, Cell [3, 2] in the picture above) and the row that contains it. To do it, I simply add an special class to the <tr> of the app-row and the <td> of the wanted app-cell, as you can see in the code sample above.

The problem I'm having is that the <tr> style is immediately applied so the row is highlighted, but the cell doesn't apply the style until I interact with it (for example, if I hover it). For doing it, I've created a method in the app-grid component with the following pseudo-code:

highlightRowAndCell(row_id, cell_id) {

    Search 'grid.rows' for a row with 'row_id' identifier;

    If found
        Set row.highlighted to true;
        Search 'row.cells' in the found row for a cell with 'cell_id' identifier;

        If found     
            Set cell.highlighted to true;

        // FORCE CHANGE DETECTION
        this.appRef.tick();

        this.changeDetectorRef.detectChanges();
}

Neither appRef.tick() nor changeDetectorRef.detectChanges() update the cell style, but the row's is immediately changed. If I hover the cell, then the style is applied.

How could I tell Angular to update the view of the affected components (or all of them, it doesn't matter)?

Thanks,

Upvotes: 2

Views: 4162

Answers (4)

Fel
Fel

Reputation: 4828

The app-row component has the <tr> element of the rows and the <td> of the cells, as stated above, so it's ideal to set the highlighted visual style to both row and cell. As I told before, I use a method (highlightRowAndCell) in the app-grid component to apply the visual style to the appropriate row and cell. The problem is that when I set row.highlighted = true and cell.highlighted = true, Angular doesn't detect these changes because they are properties of objects inside an array. So, to force it detecting the changes, I've added the following @Input to the app-row component:

app-grid.component.html template:

<app-row [...]
  [highlighted]="row.highlighted">
</app-row>

app-row.component.ts:

export class AppRowComponent implements OnInit, OnDestroy {
  [...]

  @Input() highlighted: boolean;

  [...]
}

Now, running this.changeDetectorRef.detectChanges(); in the highlightRowAndCell method inside the app-grid component makes Angular refresh the affected app-row and the styles are applied immediately.

Upvotes: 0

Chellappan வ
Chellappan வ

Reputation: 27441

All primitive types are passed by value. Objects, arrays, and functions are also passed by value

So in order to trigger a change detection in our component, we need to change the object reference.

highlightRowAndCell(row_id, cell_id) {

    Search 'grid.rows' for a row with 'row_id' identifier;

    If found
        //Set row.highlighted to true;
        this.row={
        highlighted=true;
         }
        Search 'row.cells' in the found row for a cell with 'cell_id' identifier;

        If found     
           // Set cell.highlighted to true;
           this.cell={highlighted=true}
}

Ref:https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4

Upvotes: 0

Cobus Kruger
Cobus Kruger

Reputation: 8605

Did you have a performance problem that prompted you to use ChangeDetectionStrategy.OnPush? I think the much more common use case for that is for input controls, where you have a very specific event where you expect to trigger the change detection.

In your case, there isn't really such an event and I would rather forego the complexity altogether by using ChangeDetectionStrategy.Default. It works really well for most use cases.

Also, if you are going to trigger change detection for all components, then you're not getting any benefit from the push model anyway - in fact, your performance will very likely be worse.

Upvotes: 0

Vivek Kumar
Vivek Kumar

Reputation: 5040

As far I know change Detection is for playing with data and data change. So this will not be a better approach for this purpose.

better to you [ngClass]="{someCondition: 'someClass'}" or [ngStyle] and bind it to some variable.

Upvotes: 1

Related Questions