1

I have a problem with ngFor while using Angular OnPush ChangeDetection.

This is my code:

app.state.ts

export class Node {
    title: string;
}

export class Tree {
    nodes: Array<Node>
}

home.component.ts

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class HomeComponent implements OnInit {
    tree$: Observable<Tree>;

    ngOnInit() {
        this.tree$ = this.store.select(state => state.tree);
    }
}

home.component.html

Number of nodes: {{ (tree$ | async).nodes.length }}
<div *ngFor="let node of (tree$ | async).nodes" class="node-list">
    {{ node.title }}
</div>

By default, the nodes array is empty. When I dispatch an action to add a new node into the nodes array. The number of nodes in the template changes, but the node list is still empty.

I don't understand why. Do you have any idea? Thanks!

UPDATE By adding *ngIf in a container that contains node list, it is working.

<div *ngIf="tree$">
    <div *ngFor="let node of (tree$ | async).nodes" class="node-list">
        {{ node.title }}
    </div>
</div>

But I still do not understand why. Could you explain this behavior, please?

Trong Lam Phan
  • 2,292
  • 3
  • 24
  • 51
  • When you add a new node into the nodes, do you use the `push` method on the `nodes` array or you create a new array? I mean do you mutate the array or creates a new Array on adding new node? Also can you please post your reducer code which adds a new node in the `nodes` array? – user2216584 Jul 08 '19 at 03:25
  • Yes, I created a completely new array @user2216584. What makes me confused is why the number of nodes changes if there is a problem in my code – Trong Lam Phan Jul 08 '19 at 04:45
  • Add the code for your reducer where you update the state and where you dispatch the action. – Adrian Brand Jul 08 '19 at 04:48

2 Answers2

1

(tree$ | async).nodes is going to cause an exception if the tree is not initialised.

Try

<ng-container *ngIf="tree$ | async as tree">
  Number of nodes: {{ tree.nodes.length }}
  <div *ngFor="let node of tree.nodes" class="node-list">
    {{ node.title }}
  </div>
</ng-container>

This will not crash for when the tree is undefined before the first action is dispatched.

Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
-1

ChangeDetectionStrategy.OnPush is designed to improve performance of a component by ignore checking changes internally to component and its directives. This will only just check change for some transferring decorators like @Input. So in this case even if we declare tree$ as async, the component can not detect that is an update to update into DOM. The number of nodes in the template changes because length is an immutable built-in property for any Array in Javascript which is assigned to a different memory location everytime its value changes. In contrast, (tree$ | async).nodes is referred to the same memory location even if its property is changed.

There are several options you can check to fix it:

Lam Nguyen
  • 186
  • 1
  • 8
  • 2
    tree$ | async is watching an observable and is perfect for ChangeDetectionStrategy.OnPush. When observables emit change detection is triggered. – Adrian Brand Jul 08 '19 at 04:46