2

While using ChangeDetectionStrategy.OnPush observables$ that are updated within ngAfterViewInit on children components do not have their values reflected correctly within their respective html templates.

@Component({
  selector: 'app-example',
  template: `exampleOne$ that does update - {{exampleOne$ | async}}
             exampleTwo$ that doesn't update - {{exampleTwo$ | async}}
             <button (click)="update()">Update</button>`,
  styleUrls: ['./example.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExampleComponent implements OnChanges, AfterViewInit {
  readonly exampleOne$ = new BehaviorSubject(false);
  readonly exampleTwo$ = new BehaviorSubject(false);
  @Input() someValue = '';

  ngOnChanges(): void {
    // Reflected correctly within the template
    this.exampleOne$.next(true);
  }

  ngAfterViewInit(): void {
    // Reflected incorrectly within the template
    this.exampleTwo$.next(true);
  }

  update(): void { 
    // Reflected correctly within the template
    this.exampleTwo$.next(true);
   }
}

Exception - If the component is included at the root of the application within app.component.html the values are reflected correctly.

If used in any other context such a child component within a parent component then the exampleTwo$ observable will remain as false until some other event triggers a change detection cycle.

Why does updating an observable$ inside ngAfterViewInit not stay within Angular's zone, I'm aware of most of the limiting factors when using OnPush regarding when and when it doesn't trigger a change detection cycle, but I can't find any decent information regarding it's lack of compatibility with ngAfterViewInit

Excerpt from - https://blog.angular-university.io/onpush-change-detection-how-it-works/

An OnPush change detector gets triggered in a couple of other situations other than changes in component Input() references, it also gets triggered for example:

if an observable linked to the template via the async pipe emits a new value

Munerz
  • 1,052
  • 1
  • 13
  • 26

1 Answers1

2

I'm surprised you didn't get an "Expression has changed after it was checked" error.

The problem is due to changing component variables after the view has been checked for changes. AfterViewInit is called after the DOM updates. So those changes won't be reflected unless another update occurs. The only reason why it appears to be working is because multiple updates of the root component must have occurred allowing the value to be reflected on the second.

So why doesn't using the async pipe prevent the problem? All the async pipe does is subscribe to an observable and call markForCheck() after every emission. Since the component was already checked you'll have to wait until something else initiates another cycle for it to get updated.

So either move variable updates from AfterViewInit to OnInit or call detectChanges() at the end of the method.

Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
  • so if we subscribe to Ngrx state inside ngOnint , will ngOnint reRun if state changes ? @Daniel Gimenez – sravan ganji Oct 27 '22 at 17:40
  • 1
    @sravanganji - **ngOnInit** won't rerun, but the subscription will stay active until you unsubscribe from it - potentially well after the lifetime of the component itself if you're not careful. – Daniel Gimenez Oct 27 '22 at 21:44