| technology | Angular | ||||||
|---|---|---|---|---|---|---|---|
| domain | frontend | ||||||
| level | Senior/Architect | ||||||
| version | 20+ | ||||||
| tags |
|
||||||
| ai_role | Senior Angular Expert | ||||||
| last_updated | 2026-03-22 |
- Primary Goal: Deep-dive into expert and niche topics in Angular.
- Target Tooling: Cursor, Windsurf, Antigravity.
- Tech Stack Version: Angular 20
Note
Context: Fine-grained Reactivity
Accidentally creating a cyclic dependency in computed.
Error: Detected cycle in computations.
computed(() => {
const user = this.user();
untracked(() => this.logger.log(user)); // Logging doesn't create dependency
return user.name;
});Use untracked() for side effects or reads that shouldn't affect recalculation.
⚡ 57. V8 Hidden Classes Optimization
Note
Context: Micro-optimization
user = signal({});
// later
user.set({ name: 'A', age: 10 }); // Shape changeInitializing with an empty object and later adding fields changes the object "shape" (Hidden Class), breaking V8 JIT compiler optimization.
interface User { name: string | null; age: number | null; }
user = signal<User>({ name: null, age: null });Always initialize signals with the full object shape (even with null) to preserve property access monomorphism.
Note
Context: Reactivity Theory
count = signal(0);
doubleCount = signal(0);
constructor() {
effect(() => {
this.doubleCount.set(this.count() * 2);
});
}Using effect to derive or synchronize local state is an anti-pattern. Effects are asynchronous (microtask timing), which means the state can momentarily be inconsistent ("glitch") before the effect runs, leading to UI flicker or bugs.
count = signal(0);
doubleCount = computed(() => this.count() * 2);Use computed for derived state. Computed signals evaluate lazily and synchronously when read, ensuring true Glitch Freedom and eliminating unnecessary change detection cycles.
Note
Context: Application Lifecycle
@Injectable({ providedIn: 'root' })
export class GlobalService {
constructor() {
effect(() => {
// Listens to global events indefinitely
console.log('State changed', this.state());
});
}
}Effects created in root services live for the entire lifecycle of the application. If the service relies on dynamic instantiation or lazy loading and is later destroyed, the effect will continue to execute, causing memory leaks and unexpected behavior.
@Injectable({ providedIn: 'root' })
export class GlobalService implements OnDestroy {
private effectRef = effect(() => {
console.log('State changed', this.state());
}, { manualCleanup: true });
ngOnDestroy() {
this.effectRef.destroy();
}
}When creating effects in long-lived or dynamic services, explicitly configure them with { manualCleanup: true } and destroy the reference during the service's ngOnDestroy lifecycle hook. This ensures deterministic resource management.
Note
Context: Advanced DI
Passing an Injector instance manually into functions.
Bulky code.
runInInjectionContext(this.injector, () => {
// can use inject() here dynamically
const service = inject(MyService);
});Use this helper to execute functions requiring a DI context outside the constructor/initialization.