| |

Angular 63 ๐Ÿ…ฐ๏ธ Change Detection โ€” Signals and Manual Control

The previous chapter covered the two strategies โ€” the Default (Eager) and the OnPush. This chapter covers the tools that give the developer direct control over the change detection: the ChangeDetectorRef‘s methods, the signal-driven change detection, the effect‘s scheduling, the markForCheck‘s mechanics, the detectChanges‘s and the ApplicationRef.tick()‘s manual invocations, the NgZone‘s and the runOutsideAngular‘s, the ChangeDetectionScheduler, the zoneless’s model, and the patterns that make the manual control safe. It builds on Angular 62 and prepares the ground for the component’s lifecycle’s material.

Key point: The ChangeDetectorRef has the four methods: the markForCheck() (the mark’s and the ancestors’s), the detectChanges() (the immediate’s for the component and its children), the detach() (the remove’s from the tree), and the reattach() (the add’s back). The markForCheck() schedules the check for the next cycle; the detectChanges() runs the check synchronously for the component and its children. The signals trigger the change detection automatically when the signal read in the template updates, and the effect() schedules the side effect after the change detection. The ApplicationRef.tick() runs the change detection for the whole application, and the NgZone.run() and the runOutsideAngular() control the zone’s context. The zoneless’s is the provideZonelessChangeDetection(), and the scheduler is the ChangeDetectionScheduler.


The ChangeDetectorRef

The ChangeDetectorRef is the injectable’s, and the injectable’s is the component’s. The four methods are the control’s, and the control’s is the manual’s.

import { Component, ChangeDetectorRef, inject, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-user',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<p>{{ user.name }}</p>`,
})
export class UserComponent {
  private readonly cdr = inject(ChangeDetectorRef);
  user = { name: 'Alice' };
}

The inject(ChangeDetectorRef) is the injectable’s, and the injectable’s is the component’s. The cdr is the reference’s, and the reference’s is the four methods’s. The two are the pair, and the pair is the manual’s.

Why the ChangeDetectorRef matters. The ChangeDetectorRef is the component’s, and the component’s is the change detection’s. The cdr is the handle’s, and the handle’s is the manual’s. The two are the pair, and the pair is the control’s.

Why the ChangeDetectorRef should be the inject()‘s. The ChangeDetectorRef should be the inject()‘s, and the inject()‘s is the modern’s. The constructor’s is the legacy’s, and the legacy’s is the constructor’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectorRef matters for the OnPush’s. The ChangeDetectorRef matters for the OnPush’s, and the OnPush’s is the manual’s. The markForCheck() is the trigger’s, and the trigger’s is the OnPush’s. The two are the pair, and the pair is the control’s.

Why the ChangeDetectorRef should be the sparing. The ChangeDetectorRef should be the sparing, and the sparing’s is the specific’s. The signals’s is the modern’s, and the modern’s is the replacement’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectorRef matters for the zoneless’s. The ChangeDetectorRef matters for the zoneless’s, and the zoneless’s is the manual’s. The markForCheck() is the trigger’s, and the trigger’s is the zoneless’s. The two are the pair, and the pair is the control’s.

Why the ChangeDetectorRef matters for the editor’s. The ChangeDetectorRef matters for the editor’s, and the editor’s is the language service’s. The markForCheck() is the hint’s, and the hint’s is the editor’s. The two are the pair, and the pair is the productivity’s.

Why the ChangeDetectorRef should be the documented. The ChangeDetectorRef should be the documented, and the documented’s is the why’s. The comment’s is the why’s, and the why’s is the clarity’s. The two are the pair, and the pair is the discipline’s.

Why the ChangeDetectorRef is the escape’s. The ChangeDetectorRef is the escape’s, and the escape’s is the manual’s. The signals’s is the modern’s, and the modern’s is the automatic’s. The two are the pair, and the pair is the design’s.


The markForCheck()

The markForCheck() is the mark’s and the ancestors’s, and the mark’s is the next cycle’s.

updateFromOutside(): void {
  this.user.name = 'Bob';
  this.cdr.markForCheck();  // the mark's and the ancestors's
}

The markForCheck() is the mark’s and the ancestors’s, and the ancestors’s is the root’s. The markForCheck() schedules the check for the next cycle, and the next cycle’s is the tick’s. The two are the pair, and the pair is the manual’s.

Why the markForCheck() matters. The markForCheck() is the OnPush’s trigger’s, and the trigger’s is the manual’s. The markForCheck() is the mark’s and the ancestors’s, and the ancestors’s is the chain’s. The two are the pair, and the pair is the control’s.

Why the markForCheck()‘s ancestor’s matters. The markForCheck()‘s ancestor’s is the chain’s, and the chain’s is the root’s. The markForCheck() marks the component and all the ancestors’s, and the ancestors’s is the path’s. The two are the pair, and the pair is the design’s.

Why the markForCheck()‘s scheduling matters. The markForCheck()‘s scheduling is the next cycle’s, and the next cycle’s is the async’s. The markForCheck() does not run the check immediately, and the immediately’s is the detectChanges()‘s. The two are the pair, and the pair is the distinction’s.

Why the markForCheck()‘s idempotence matters. The markForCheck()‘s idempotence is the multiple’s, and the multiple’s is the once’s. The markForCheck() can be called the multiple’s, and the multiple’s is the once’s effect’s. The two are the pair, and the pair is the design’s.

Why the markForCheck()‘s dirty’s flag matters. The markForCheck()‘s dirty’s flag is the internal’s, and the internal’s is the scheduler’s. The markForCheck() sets the flag, and the flag’s is the check’s. The two are the pair, and the pair is the design’s.

Why the markForCheck() should be the last’s. The markForCheck() should be the last’s, and the last’s is the mutation’s. The markForCheck() after the mutation, and the mutation’s is the state’s. The two are the pair, and the pair is the order’s.

Why the markForCheck() matters for the async’s. The markForCheck() matters for the async’s, and the async’s is the subscription’s. The markForCheck() in the setTimeout, and the setTimeout‘s is the trigger’s. The two are the pair, and the pair is the pattern’s.

Why the markForCheck() should be the documented. The markForCheck() should be the documented, and the documented’s is the why’s. The comment’s is the why’s, and the why’s is the clarity’s. The two are the pair, and the pair is the discipline’s.


The detectChanges()

The detectChanges() is the immediate’s for the component and its children, and the immediate’s is the synchronous’s.

updateImmediately(): void {
  this.user.name = 'Bob';
  this.cdr.detectChanges();  // the immediate's
}

The detectChanges() is the immediate’s, and the immediate’s is the synchronous’s. The detectChanges() runs the check for the component and its children, and the children’s is the subtree’s. The two are the pair, and the pair is the manual’s.

Why the detectChanges() matters. The detectChanges() is the immediate’s, and the immediate’s is the synchronous’s. The markForCheck() is the scheduled’s, and the scheduled’s is the next’s. The two are the different, and the different is the distinction’s.

Why the detectChanges()‘s scope matters. The detectChanges()‘s scope is the component and its children, and the children’s is the subtree’s. The detectChanges() does not check the ancestors’s, and the ancestors’s is the markForCheck()‘s. The two are the pair, and the pair is the distinction’s.

Why the detectChanges()‘s synchrony matters. The detectChanges()‘s synchrony is the immediate’s, and the immediate’s is the synchronous’s. The detectChanges() runs the check synchronously, and the synchronously’s is the tick‘s. The two are the pair, and the pair is the design’s.

Why the detectChanges() should be the sparing. The detectChanges() should be the sparing, and the sparing’s is the rare’s. The detectChanges() is the synchronous’s, and the synchronous’s is the performance’s. The two are the pair, and the pair is the discipline’s.

Why the detectChanges() matters for the test’s. The detectChanges() matters for the test’s, and the test’s is the fixture’s. The fixture.detectChanges() is the test’s, and the test’s is the manual’s. The two are the pair, and the pair is the pattern’s.

Why the detectChanges()‘s nested’s matters. The detectChanges()‘s nested’s is the child’s, and the child’s is the subtree’s. The detectChanges() on the parent does the children’s, and the children’s is the descendant’s. The two are the pair, and the pair is the design’s.

Why the detectChanges() matters for the OnPush‘s. The detectChanges() matters for the OnPush‘s, and the OnPush‘s is the manual’s. The detectChanges() runs the check regardless of the strategy, and the strategy’s is the markForCheck‘s. The two are the pair, and the pair is the distinction’s.

Why the detectChanges() should be the markForCheck()‘s first. The detectChanges() should be the markForCheck()‘s first, and the first’s is the best’s. The detectChanges() alone does not mark the ancestors’s, and the ancestors’s is the markForCheck‘s. The two are the pair, and the pair is the design’s.


The signals and the change detection

The signals trigger the change detection automatically when the signal read in the template updates, and the trigger’s is the signal’s.

@Component({
  selector: 'app-counter',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<p>{{ count() }}</p><button (click)="inc()">+</button>`,
})
export class CounterComponent {
  count = signal(0);

  inc(): void {
    this.count.update((v) => v + 1);  // the trigger's
  }
}

The count = signal(0) is the signal’s, and the signal’s is the state’s. The this.count.update((v) => v + 1) is the trigger’s, and the trigger’s is the change detection’s. The two are the pair, and the pair is the signal’s.

Why the signals matter. The signals are the modern’s, and the modern’s is the automatic’s. The signals trigger the change detection, and the change detection’s is the component’s. The two are the pair, and the pair is the design’s.

Why the signals’s read’s matters. The signals’s read’s is the template’s, and the template’s is the dependency’s. The signal read in the template is the trigger’s, and the trigger’s is the component’s. The two are the pair, and the pair is the design’s.

Why the signals’s consumer’s matters. The signals’s consumer’s is the component’s, and the component’s is the mark’s. The signal notifies the consumers’s, and the consumers’s is the mark’s. The two are the pair, and the pair is the design’s.

Why the signals’s scheduling matters. The signals’s scheduling is the scheduler’s, and the scheduler’s is the microtask’s. The signal’s update schedules the check, and the check’s is the microtask’s. The two are the pair, and the pair is the design’s.

Why the signals’s untracked matters. The signals’s untracked is the no-track’s, and the no-track’s is the escape’s. The untracked(() => this.other()) is the no-track’s, and the no-track’s is the read’s. The two are the pair, and the pair is the design’s.

Why the signals’s computed matters. The signals’s computed is the derived’s, and the derived’s is the memoized’s. The computed(() => this.count() * 2) is the derived’s, and the derived’s is the cached’s. The two are the pair, and the pair is the design’s.

Why the signals’s effect matters. The signals’s effect is the side effect’s, and the side effect’s is the after’s. The effect(() => console.log(this.count())) is the side effect’s, and the side effect’s is the after’s. The two are the pair, and the pair is the design’s.

Why the signals’s linkedSignal matters. The signals’s linkedSignal is the reset’s, and the reset’s is the source’s. The linkedSignal(() => this.options()[0]) is the reset’s, and the reset’s is the source’s. The two are the pair, and the pair is the design’s.


The effect‘s scheduling

The effect() schedules the side effect after the change detection, and the after’s is the effect’s.

effect(() => {
  console.log('the count is', this.count());
});

The effect(() => { ... }) is the side effect’s, and the side effect’s is the after’s. The effect runs after the change detection, and the after’s is the DOM’s. The two are the pair, and the pair is the design’s.

Why the effect‘s scheduling matters. The effect‘s scheduling is the after’s, and the after’s is the change detection’s. The effect runs after the change detection, and the after’s is the DOM’s updated’s. The two are the pair, and the pair is the design’s.

Why the effect‘s timing matters. The effect‘s timing is the microtask’s, and the microtask’s is the after’s. The effect is the microtask’s, and the microtask’s is the change detection’s. The two are the pair, and the pair is the design’s.

Why the effect‘s scheduling’s allowSignalWrites matters. The effect‘s allowSignalWrites is the write’s, and the write’s is the signal’s. The allowSignalWrites: true is the write’s, and the write’s is the signal’s. The two are the pair, and the pair is the design’s.

Why the effect‘s allowSignalWrites should be the careful. The effect‘s allowSignalWrites should be the careful, and the careful’s is the loop’s. The allowSignalWrites: true can create the loop’s, and the loop’s is the bug’s. The two are the pair, and the pair is the risk’s.

Why the effect‘s cleanup matters. The effect‘s cleanup is the onCleanup‘s, and the onCleanup‘s is the destroy’s. The effect((onCleanup) => { ... }) is the cleanup’s, and the cleanup’s is the resource’s. The two are the pair, and the pair is the design’s.

Why the effect‘s injector matters. The effect‘s injector is the context’s, and the context’s is the injection’s. The effect‘s injector is the EnvironmentInjector‘s, and the EnvironmentInjector‘s is the context’s. The two are the pair, and the pair is the design’s.

Why the effect‘s component’s cleanup matters. The effect‘s component’s cleanup is the destroy’s, and the destroy’s is the component’s. The effect‘s in the component is the cleanup’s, and the cleanup’s is the automatic’s. The two are the pair, and the pair is the design’s.

Why the effect‘s untracked matters. The effect‘s untracked is the no-track’s, and the no-track’s is the escape’s. The untracked(() => this.other()) is the no-track’s, and the no-track’s is the read’s. The two are the pair, and the pair is the design’s.


The ApplicationRef.tick()

The ApplicationRef.tick() runs the change detection for the whole application, and the whole’s is the manual’s.

import { ApplicationRef, inject } from '@angular/core';

const appRef = inject(ApplicationRef);
appRef.tick();  // the whole's

The appRef.tick() is the whole’s, and the whole’s is the manual’s. The tick() runs the change detection for the whole application, and the whole’s is the root’s. The two are the pair, and the pair is the manual’s.

Why the tick() matters. The tick() is the whole’s, and the whole’s is the manual’s. The tick() is the setTimeout‘s or the requestAnimationFrame‘s, and the setTimeout‘s is the manual’s. The two are the pair, and the pair is the design’s.

Why the tick() should be the sparing. The tick() should be the sparing, and the sparing’s is the rare’s. The tick() is the whole’s, and the whole’s is the expensive’s. The two are the pair, and the pair is the discipline’s.

Why the tick()‘s synchrony matters. The tick()‘s synchrony is the immediate’s, and the immediate’s is the synchronous’s. The tick() runs the change detection synchronously, and the synchronously’s is the detectChanges‘s. The two are the pair, and the pair is the design’s.

Why the tick() matters for the runOutsideAngular‘s. The tick() matters for the runOutsideAngular‘s, and the runOutsideAngular‘s is the zone’s. The tick() is the manual’s, and the manual’s is the runOutsideAngular‘s. The two are the pair, and the pair is the pattern’s.

Why the tick()‘s error handling matters. The tick()‘s error handling is the try/catch‘s, and the try/catch‘s is the guard’s. The tick() in the try/catch, and the try/catch‘s is the error’s. The two are the pair, and the pair is the design’s.

Why the tick()‘s ApplicationRef matters. The tick()‘s ApplicationRef is the inject’s, and the inject’s is the modern’s. The inject(ApplicationRef) is the modern’s, and the modern’s is the concise’s. The two are the pair, and the pair is the design’s.

Why the tick() matters for the zoneless’s. The tick() matters for the zoneless’s, and the zoneless’s is the manual’s. The tick() is the trigger’s, and the trigger’s is the zoneless’s. The two are the pair, and the pair is the design’s.

Why the tick() should be the last’s. The tick() should be the last’s, and the last’s is the mutation’s. The tick() after the mutation, and the mutation’s is the state’s. The two are the pair, and the pair is the order’s.


The NgZone and the runOutsideAngular

The NgZone is the zone’s, and the runOutsideAngular is the outside’s.

import { NgZone, inject } from '@angular/core';

const zone = inject(NgZone);

zone.runOutsideAngular(() => {
  setInterval(() => {
    // the no-change-detection's
  }, 1000);
});

The zone.runOutsideAngular(() => { ... }) is the outside’s, and the outside’s is the no-change-detection’s. The setInterval in the runOutsideAngular, and the runOutsideAngular‘s is the performance’s. The two are the pair, and the pair is the design’s.

Why the runOutsideAngular matters. The runOutsideAngular is the outside’s, and the outside’s is the no-change-detection’s. The runOutsideAngular prevents the change detection’s, and the change detection’s is the expensive’s. The two are the pair, and the pair is the performance’s.

Why the runOutsideAngular‘s setInterval matters. The runOutsideAngular‘s setInterval is the frequent’s, and the frequent’s is the performance’s. The setInterval outside the zone, and the zone’s is the change detection’s. The two are the pair, and the pair is the design’s.

Why the runOutsideAngular‘s the re-entry matters. The runOutsideAngular‘s the re-entry is the NgZone.run‘s, and the NgZone.run‘s is the inside’s. The zone.run(() => { ... }) is the inside’s, and the inside’s is the change detection’s. The two are the pair, and the pair is the design’s.

Why the runOutsideAngular‘s the event matters. The runOutsideAngular‘s the event is the listener’s, and the listener’s is the outside’s. The addEventListener in the runOutsideAngular, and the runOutsideAngular‘s is the no-change-detection’s. The two are the pair, and the pair is the design’s.

Why the runOutsideAngular‘s the library matters. The runOutsideAngular‘s the library is the third-party’s, and the third-party’s is the chart’s. The runOutsideAngular for the chart’s animation, and the animation’s is the no-change-detection’s. The two are the pair, and the pair is the performance’s.

Why the NgZone‘s isStable matters. The NgZone‘s isStable is the stable’s, and the stable’s is the Observable’s. The zone.onStable is the stable’s, and the stable’s is the after’s. The two are the pair, and the pair is the design’s.

Why the NgZone‘s onMicrotaskEmpty matters. The NgZone‘s onMicrotaskEmpty is the microtask’s, and the microtask’s is the after’s. The zone.onMicrotaskEmpty is the microtask’s, and the microtask’s is the change detection’s. The two are the pair, and the pair is the design’s.

Why the NgZone‘s the zoneless’s matters. The NgZone‘s the zoneless’s is the no-zone’s, and the no-zone’s is the provideZonelessChangeDetection‘s. The provideZonelessChangeDetection() is the no-zone’s, and the no-zone’s is the modern’s. The two are the pair, and the pair is the design’s.


The ChangeDetectionScheduler

The ChangeDetectionScheduler is the scheduler’s, and the scheduler’s is the internal’s.

// The internal's, the not the public's.
// The ChangeDetectionScheduler is the Angular's internal's.

The ChangeDetectionScheduler is the scheduler’s, and the scheduler’s is the internal’s. The ChangeDetectionScheduler schedules the change detection’s, and the change detection’s is the microtask’s. The two are the pair, and the pair is the internal’s.

Why the ChangeDetectionScheduler matters. The ChangeDetectionScheduler is the scheduler’s, and the scheduler’s is the internal’s. The ChangeDetectionScheduler is the notification’s, and the notification’s is the component’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectionScheduler‘s the microtask matters. The ChangeDetectionScheduler‘s the microtask is the after’s, and the after’s is the sync’s. The ChangeDetectionScheduler schedules the microtask’s, and the microtask’s is the after’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectionScheduler‘s the notify matters. The ChangeDetectionScheduler‘s the notify is the method’s, and the method’s is the notification’s. The notify() is the method’s, and the method’s is the scheduler’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectionScheduler‘s the public’s matters. The ChangeDetectionScheduler‘s the public’s is the markForCheck‘s, and the markForCheck‘s is the signal’s. The markForCheck() and the signal’s are the public’s, and the public’s is the API’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectionScheduler should be the internal’s. The ChangeDetectionScheduler should be the internal’s, and the internal’s is the not-the-public’s. The ChangeDetectionScheduler is the Angular’s, and the Angular’s is the internal’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectionScheduler matters for the zoneless’s. The ChangeDetectionScheduler matters for the zoneless’s, and the zoneless’s is the scheduler’s. The ChangeDetectionScheduler is the zoneless’s, and the zoneless’s is the trigger’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectionScheduler‘s the NotificationSource matters. The ChangeDetectionScheduler‘s the NotificationSource is the source’s, and the source’s is the enum’s. The NotificationSource.MarkForCheck is the source’s, and the source’s is the debug’s. The two are the pair, and the pair is the design’s.

Why the ChangeDetectionScheduler matters for the debug’s. The ChangeDetectionScheduler matters for the debug’s, and the debug’s is the source’s. The NotificationSource is the debug’s, and the debug’s is the profiling’s. The two are the pair, and the pair is the performance’s.


The patterns

The patterns are the common’s, and the common’s is the practice’s.

The pattern 1: the signal’s state. The signal’s state is the modern’s, and the modern’s is the automatic’s.

readonly count = signal(0);

The readonly count = signal(0) is the signal’s, and the signal’s is the automatic’s. The two are the pair, and the pair is the pattern’s.

Why the pattern 1 matters. The pattern 1 is the signal’s, and the signal’s is the modern’s. The signal’s is the automatic’s, and the automatic’s is the design’s. The two are the pair, and the pair is the pattern’s.

The pattern 2: the markForCheck‘s. The markForCheck‘s is the manual’s, and the manual’s is the specific’s.

this.cdr.markForCheck();

The this.cdr.markForCheck() is the manual’s, and the manual’s is the specific’s. The two are the pair, and the pair is the pattern’s.

Why the pattern 2 matters. The pattern 2 is the markForCheck‘s, and the markForCheck‘s is the escape’s. The markForCheck‘s is the OnPush’s, and the OnPush’s is the trigger’s. The two are the pair, and the pair is the design’s.

The pattern 3: the effect‘s. The effect‘s is the side effect’s, and the side effect’s is the after’s.

effect(() => { ... });

The effect(() => { ... }) is the side effect’s, and the side effect’s is the after’s. The two are the pair, and the pair is the pattern’s.

Why the pattern 3 matters. The pattern 3 is the effect‘s, and the effect‘s is the after’s. The effect‘s is the after’s, and the after’s is the DOM’s. The two are the pair, and the pair is the design’s.

The pattern 4: the runOutsideAngular‘s. The runOutsideAngular‘s is the performance’s, and the performance’s is the no-change-detection’s.

zone.runOutsideAngular(() => { ... });

The zone.runOutsideAngular(() => { ... }) is the performance’s, and the performance’s is the no-change-detection’s. The two are the pair, and the pair is the pattern’s.

Why the pattern 4 matters. The pattern 4 is the runOutsideAngular‘s, and the runOutsideAngular‘s is the performance’s. The runOutsideAngular‘s is the frequent’s, and the frequent’s is the optimization’s. The two are the pair, and the pair is the design’s.

The pattern 5: the zoneless’s. The zoneless’s is the modern’s, and the modern’s is the no-zone’s.

provideZonelessChangeDetection()

The provideZonelessChangeDetection() is the modern’s, and the modern’s is the no-zone’s. The two are the pair, and the pair is the pattern’s.

Why the pattern 5 matters. The pattern 5 is the zoneless’s, and the zoneless’s is the modern’s. The zoneless’s is the signals’s, and the signals’s is the requirement’s. The two are the pair, and the pair is the design’s.

Why the patterns matter. The patterns are the vocabulary’s, and the vocabulary’s is the fluency’s. The five are the common’s, and the common’s is the practice’s. The two are the pair, and the pair is the skill’s.


Complete Example Session

// ============================================
// PART 1: THE CHANGE DETECTOR REF
// ============================================

import { Component, ChangeDetectorRef, inject, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-user',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<p>{{ user.name }}</p>`,
})
export class UserComponent {
  private readonly cdr = inject(ChangeDetectorRef);
  user = { name: 'Alice' };
}

// ============================================
// PART 2: THE MARK FOR CHECK
// ============================================

updateFromOutside(): void {
  this.user.name = 'Bob';
  this.cdr.markForCheck();  // the scheduled's
}

// ============================================
// PART 3: THE DETECT CHANGES
// ============================================

updateImmediately(): void {
  this.user.name = 'Bob';
  this.cdr.detectChanges();  // the immediate's
}

// ============================================
// PART 4: THE SIGNALS
// ============================================

@Component({
  selector: 'app-counter',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<p>{{ count() }}</p><button (click)="inc()">+</button>`,
})
export class CounterComponent {
  count = signal(0);

  inc(): void {
    this.count.update((v) => v + 1);  // the trigger's
  }
}

// ============================================
// PART 5: THE EFFECT
// ============================================

effect(() => {
  console.log('the count is', this.count());
});

// ============================================
// PART 6: THE APPLICATION REF TICK
// ============================================

import { ApplicationRef, inject } from '@angular/core';

const appRef = inject(ApplicationRef);
appRef.tick();  // the whole's

// ============================================
// PART 7: THE RUN OUTSIDE ANGULAR
// ============================================

import { NgZone, inject } from '@angular/core';

const zone = inject(NgZone);

zone.runOutsideAngular(() => {
  setInterval(() => {
    // the no-change-detection's
  }, 1000);
});

// ============================================
// PART 8: THE DETACH AND THE REATTACH
// ============================================

this.cdr.detach();  // the remove's
this.cdr.reattach();  // the add's back

// ============================================
// PART 9: THE ZONELESS
// ============================================

export const appConfig: ApplicationConfig = {
  providers: [
    provideZonelessChangeDetection(),
  ],
};

// ============================================
// PART 10: WHAT NOT TO DO
// ============================================

// Don't use the detectChanges for the every's
this.cdr.detectChanges();  // the expensive's                // โš ๏ธ

// Don't forget the markForCheck for the async's
setTimeout(() => { this.value = 'x'; });  // the no-trigger's // โš ๏ธ

// Don't use the runOutsideAngular for the state's
zone.runOutsideAngular(() => { this.count++; });  // the no-trigger's // โš ๏ธ

// Don't forget the effect's cleanup
effect(() => { setInterval(() => {}, 1000); });  // the leak's   // โš ๏ธ

// Don't use the tick for the every's
appRef.tick();  // the expensive's                              // โš ๏ธ

// Don't forget the zoneless's triggers
this.plainProperty = 'x';  // the no-trigger's in the zoneless's // โš ๏ธ

The ten parts cover the ChangeDetectorRef, the markForCheck, the detectChanges, the signals, the effect, the ApplicationRef.tick, the runOutsideAngular, the detach/reattach, the zoneless, and the anti-patterns.


Quick Reference

The ChangeDetectorRef‘s Methods

MethodPurpose
The markForCheck()The mark’s and the ancestors’s
The detectChanges()The immediate’s for the component and the children
The detach()The remove’s from the tree
The reattach()The add’s back

The Triggers

TriggerMechanism
The signal’s updateThe automatic’s
The markForCheck()The manual’s
The detectChanges()The synchronous’s
The eventThe bound’s
The ApplicationRef.tick()The whole’s

The Signals’s APIs

APIPurpose
The signal()The state’s
The computed()The derived’s
The effect()The side effect’s
The untracked()The no-track’s
The linkedSignal()The reset’s

The NgZone‘s Methods

MethodPurpose
The run()The inside’s
The runOutsideAngular()The outside’s
The onStableThe stable’s
The onMicrotaskEmptyThe microtask’s

The Zoneless’s Triggers

TriggerAPI
The signal’s updateThe signal.set()
The markForCheckThe ChangeDetectorRef
The input’s setThe ComponentRef.setInput()
The bound’s listenerThe (click)

The Patterns

PatternPurpose
The signal’s stateThe automatic’s
The markForCheck‘sThe manual’s
The effect‘sThe side effect’s
The runOutsideAngular‘sThe performance’s
The zoneless’sThe modern’s

Best Practices

โœ… Do This:

// Use the signals for the state
readonly count = signal(0);                                    // โœ…
// Use the markForCheck for the async's
this.cdr.markForCheck();                                       // โœ…
// Use the effect for the side effect's
effect(() => console.log(this.count()));                       // โœ…
// Use the runOutsideAngular for the frequent's
zone.runOutsideAngular(() => setInterval(...));                 // โœ…
// Use the zoneless for the new's
provideZonelessChangeDetection();                               // โœ…
// Use the effect's cleanup
effect((onCleanup) => { const t = setInterval(...); onCleanup(() => clearInterval(t)); }); // โœ…
// Use the detach for the static's
this.cdr.detach();  // the static's                              // โœ…

โŒ Don’t Do This:

// Don't use the detectChanges for the every's
this.cdr.detectChanges();  // the expensive's                   // โš ๏ธ
// Don't forget the markForCheck for the async's
setTimeout(() => { this.value = 'x'; });  // the no-trigger's  // โš ๏ธ
// Don't use the runOutsideAngular for the state's
zone.runOutsideAngular(() => { this.count++; });  // the no-trigger's // โš ๏ธ
// Don't forget the effect's cleanup
effect(() => { setInterval(() => {}, 1000); });  // the leak's    // โš ๏ธ
// Don't use the tick for the every's
appRef.tick();  // the expensive's                               // โš ๏ธ
// Don't forget the zoneless's triggers
this.plainProperty = 'x';  // the no-trigger's in the zoneless's  // โš ๏ธ

Common Pitfalls

PitfallProblemSolution
The detectChanges‘s overuseThe expensive’sThe signals’s
The missing markForCheckThe stale’sThe markForCheck()
The runOutsideAngular‘s state’sThe no-trigger’sThe run()
The missing effect‘s cleanupThe leak’sThe onCleanup
The tick()‘s overuseThe expensive’sThe signals’s
The zoneless’s no-trigger’sThe stale’sThe signal’s
The detach‘s forgottenThe no-update’sThe reattach()
The allowSignalWrites‘s loopThe bug’sThe careful’s

Real-World Examples

1. The ChangeDetectorRef

private readonly cdr = inject(ChangeDetectorRef);

2. The markForCheck

this.cdr.markForCheck();

3. The detectChanges

this.cdr.detectChanges();

4. The signals

readonly count = signal(0);

5. The effect

effect(() => console.log(this.count()));

6. The ApplicationRef.tick

appRef.tick();

7. The runOutsideAngular

zone.runOutsideAngular(() => setInterval(...));

8. The detach

this.cdr.detach();

9. The zoneless

provideZonelessChangeDetection();

10. The effect‘s cleanup

effect((onCleanup) => { const t = setInterval(...); onCleanup(() => clearInterval(t)); });

Visual: The ChangeDetectorRef‘s Methods

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  markForCheck()                              โ”‚
โ”‚    The mark's and the ancestors's.           โ”‚
โ”‚    The scheduled's for the next cycle's.     โ”‚
โ”‚                                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  detectChanges()                             โ”‚
โ”‚    The immediate's for the component and the โ”‚
โ”‚    children's. The synchronous's.            โ”‚
โ”‚                                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  detach()                                    โ”‚
โ”‚    The remove's from the tree's.             โ”‚
โ”‚    The no-check's until the reattach's.      โ”‚
โ”‚                                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  reattach()                                  โ”‚
โ”‚    The add's back to the tree's.             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Signals and the Change Detection

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  count = signal(0)                           โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ”‚  The template reads count()             โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The component is the consumer's.            โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ”‚  count.update((v) => v + 1)             โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The signal notifies the consumers's.        โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The component is marked dirty's.            โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The change detection's runs.                โ”‚
โ”‚                                              โ”‚
โ”‚  The signal's is the automatic's.            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The effect‘s Scheduling

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  effect(() => console.log(this.count()))     โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ”‚  The count's changes                    โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The change detection's runs.                โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The DOM's is updated.                       โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The effect's runs.                          โ”‚
โ”‚                                              โ”‚
โ”‚  The effect's is the after's.                โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The runOutsideAngular‘s

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  zone.runOutsideAngular(() => {              โ”‚
โ”‚    setInterval(() => {                       โ”‚
โ”‚      // the no-change-detection's            โ”‚
โ”‚    }, 1000);                                 โ”‚
โ”‚  });                                         โ”‚
โ”‚                                              โ”‚
โ”‚  The setInterval's outside the zone.         โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ–ผ                                         โ”‚
โ”‚  The change detection's is the skipped's.    โ”‚
โ”‚                                              โ”‚
โ”‚  THE RE-ENTRY                                โ”‚
โ”‚    zone.run(() => { this.count++; });        โ”‚
โ”‚    The change detection's is the triggered's.โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Zoneless’s Triggers

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE ZONELESS APPLICATION DETECTS:           โ”‚
โ”‚                                              โ”‚
โ”‚  โœ… signal.set() or signal.update()          โ”‚
โ”‚  โœ… ChangeDetectorRef.markForCheck()         โ”‚
โ”‚  โœ… ComponentRef.setInput()                  โ”‚
โ”‚  โœ… Bound host or template listeners         โ”‚
โ”‚                                              โ”‚
โ”‚  โŒ Plain property mutation                  โ”‚
โ”‚  โŒ setInterval without signal               โ”‚
โ”‚  โŒ setTimeout without markForCheck          โ”‚
โ”‚                                              โ”‚
โ”‚  The valid triggers are the Angular APIs.    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Patterns’s Decision

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Is the state a signal?                      โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ”œโ”€โ”€ Yes โ”€โ”€โ–บ The automatic's.              โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ””โ”€โ”€ No                                    โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ”œโ”€โ”€ The async's?                     โ”‚
โ”‚         โ”‚      โ””โ”€โ”€ The markForCheck's.       โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ”œโ”€โ”€ The immediate's?                 โ”‚
โ”‚         โ”‚      โ””โ”€โ”€ The detectChanges's.      โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ””โ”€โ”€ The frequent's?                  โ”‚
โ”‚                โ””โ”€โ”€ The runOutsideAngular's.  โ”‚
โ”‚                                              โ”‚
โ”‚  The signals's is the default's.             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
The ChangeDetectorRefThe manual’s
The markForCheck()The scheduled’s
The detectChanges()The immediate’s
The detach()The remove’s
The reattach()The add’s
The signalsThe automatic’s
The effectThe side effect’s after’s
The ApplicationRef.tick()The whole’s
The runOutsideAngularThe outside’s
The zonelessThe modern’s

Key takeaways:

  • The ChangeDetectorRef has the four methods โ€” the markForCheck() (the scheduled’s), the detectChanges() (the immediate’s), the detach() (the remove’s), and the reattach() (the add’s)
  • The markForCheck() marks the component and the ancestors’s โ€” it schedules the check for the next cycle, and it is the OnPush’s trigger’s
  • The detectChanges() runs the check synchronously โ€” it is the immediate’s for the component and its children, and it does not mark the ancestors’s
  • The signals trigger the change detection automatically โ€” the signal read in the template is the trigger’s, and the update marks the component dirty’s
  • The effect() schedules the side effect after the change detection โ€” the timing is the microtask’s, and the effect runs after the DOM’s update
  • The ApplicationRef.tick() runs the whole’s change detection โ€” it is the manual’s, and it should be the sparing’s
  • The runOutsideAngular prevents the change detection’s โ€” it is the frequent’s and the third-party’s, and the run() is the re-entry’s
  • The detach() and the reattach() control the component’s presence’s โ€” the detach() removes the component from the tree, and the reattach() adds it back
  • The zoneless’s relies on the Angular’s APIs โ€” the signals, the markForCheck(), the setInput(), and the bound listeners’s are the triggers, and the plain property mutations do not trigger
  • The signals are the modern’s, and the manual’s is the escape’s โ€” the signals’s is the automatic’s, and the ChangeDetectorRef is the specific’s

Remember: The change detection’s manual control is the ChangeDetectorRef‘s, and the signals are the automatic’s. The markForCheck() is the scheduled’s, and the detectChanges() is the immediate’s. The signals trigger the change detection, and the effect schedules the side effect’s. The runOutsideAngular is the performance’s, and the zoneless is the modern’s. The manual’s is the escape’s, and the automatic’s is the design’s.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!