Suggest an editImprove this articleRefine the answer for “component lifecycle methods in Angular”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Angular component lifecycle hooks** are predefined methods Angular calls at specific points during a component's creation, updates, and removal from the DOM. ```typescript export class MyComponent implements OnInit, OnDestroy { @Input() id: number; ngOnInit() { /* runs after @Input binds - safe to fetch data */ } ngOnDestroy() { /* cleanup before DOM removal */ } } ``` **Key:** use `ngOnInit` for data fetching (not the constructor), `ngOnDestroy` for unsubscribing.Shown above the full answer for quick recall.Answer (EN)Image**Angular component lifecycle hooks** are predefined methods Angular calls at specific points during a component's creation, updates, and removal from the DOM. ## Theory ### TL;DR - Hooks fire in a fixed order: `ngOnChanges` → `ngOnInit` → ... → `ngOnDestroy` - Think of it like a construction checklist: foundation laid (`ngOnInit`), wiring checked (`ngAfterViewInit`), final teardown (`ngOnDestroy`) - Use `ngOnInit` for data fetching and `ngOnDestroy` for cleanup (subscriptions, timers) - `ngOnChanges` fires before `ngOnInit` and on every `@Input` change after that - `ngDoCheck` runs on every change detection cycle — use it as a last resort ### Quick example ```typescript import { Component, OnInit, OnDestroy, Input } from '@angular/core'; @Component({ selector: 'app-user', template: `<p>{{ user?.name }}</p>` }) export class UserComponent implements OnInit, OnDestroy { @Input() user: { name: string }; ngOnInit() { // Runs once after @Input properties are bound console.log('Component ready, user:', this.user); } ngOnDestroy() { // Runs before the component leaves the DOM console.log('Cleaning up'); } } // On create: "Component ready, user: {name: 'Alice'}" // On destroy: "Cleaning up" ``` In `ngOnInit`, `@Input` values are already set. In the constructor, they are not. ### Hook execution order Hooks fire in this sequence on component initialization: | Hook | When triggered | Typical use | |---|---|---| | `ngOnChanges` | Input property binds or changes | React to parent updates, log input diffs | | `ngOnInit` | After first `ngOnChanges`, bindings complete | Fetch data, init services | | `ngDoCheck` | Every change detection cycle | Custom equality checks | | `ngAfterContentInit` | Projected content (`ng-content`) ready | Query projected components | | `ngAfterContentChecked` | Content view checked | Post-content mutation logic | | `ngAfterViewInit` | Component + children views rendered | Access `ViewChild`, start animations | | `ngAfterViewChecked` | View checked | Fine-tune after full render | | `ngOnDestroy` | Before DOM removal | Cleanup subscriptions, timers | After the first full pass, Angular cycles through `ngDoCheck`, `ngAfterContentChecked`, and `ngAfterViewChecked` on every subsequent change detection run. ### Key difference: ngOnInit vs constructor The constructor runs during dependency injection, before Angular binds any inputs. `ngOnInit` runs after the first change detection pass, when `@Input` values are already set. Putting a data fetch in the constructor means your service call runs with no context from the parent yet. That is the most common mistake you will see in Angular code reviews. ### When to use each hook - **Data fetch or service call:** `ngOnInit` - **React to parent `@Input` changes:** `ngOnChanges` with a `SimpleChanges` check - **Access `ViewChild` or start DOM animations:** `ngAfterViewInit` - **Query projected `ng-content` children:** `ngAfterContentInit` - **Cleanup (unsubscribe, cancel timers, close WebSocket):** `ngOnDestroy` - **Custom detection when OnPush misses a mutation:** `ngDoCheck` (last resort only) `ngOnInit` and `ngOnDestroy` cover 90% of real-world cases. The content and view hooks only matter when you work with `ng-content` or `ViewChild`. ### How Angular runs hooks internally Angular's Ivy renderer (default since Angular 9) creates a component instance, binds `@Input` via property setters (which triggers `ngOnChanges`), then runs change detection through Zone.js-patched async tasks. Hooks fire as the renderer traverses the component tree top-down. Post-render phases like `ngAfterViewInit` are queued as microtasks after the view is fully built. Ivy pre-generates lifecycle calls at compile time, which cuts runtime overhead compared to the older ViewEngine. For a parent-child tree: parent `ngOnInit` fires first, then the child goes through its full cycle. Parent `ngAfterViewInit` fires only after all children have completed their view initialization. ### Common mistakes **Fetching data in the constructor:** ```typescript // Wrong - @Input values are not available yet constructor(private svc: DataService) { this.svc.getData(this.userId); // this.userId is undefined here } // Right ngOnInit() { this.svc.getData(this.userId); // @Input() userId is bound } ``` **Accessing ViewChild before ngAfterViewInit:** ```typescript // Wrong - view is not rendered yet ngOnInit() { console.log(this.myElement.nativeElement); // null } // Right ngAfterViewInit() { console.log(this.myElement.nativeElement); // works } ``` **Skipping ngOnDestroy unsubscribe:** ```typescript // Wrong - subscription lives forever, memory leak on every route change ngOnInit() { interval(1000).subscribe(v => this.data = v); } // Right - takeUntil pattern private destroy$ = new Subject<void>(); ngOnInit() { interval(1000).pipe(takeUntil(this.destroy$)).subscribe(v => this.data = v); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } ``` **ngOnChanges without checking which input changed:** ```typescript // Runs full reload on every trivial input change ngOnChanges() { this.loadData(); } // Check first ngOnChanges(changes: SimpleChanges) { if (changes['userId'] && !changes['userId'].firstChange) { this.loadData(); } } ``` ### Real-world usage - Angular Material dialogs: `ngAfterViewInit` starts animations once modal content is in the DOM - NGRX/NGXS stores: `ngOnDestroy` cleans up selectors and effects to prevent stale subscriptions - PrimeNG tables: `ngAfterViewChecked` handles virtual scroll resize after each render cycle - Route-based components: `ngOnInit` loads lazy data after route resolvers finish - OnPush components with mutable arrays: `ngDoCheck` + `markForCheck()` when reference equality fails ### Follow-up questions **Q:** What is the difference between the constructor and `ngOnInit`? **A:** Constructor runs during DI, before Angular binds any `@Input` properties. `ngOnInit` runs after the first change detection pass, so inputs are available. Use the constructor only for injecting dependencies. **Q:** When does `ngOnChanges` fire relative to `ngOnInit`? **A:** `ngOnChanges` fires first, before `ngOnInit`, whenever an `@Input` property is set or updated. If a component has no `@Input`, `ngOnChanges` never fires at all. **Q:** Why is `ngDoCheck` a last resort? **A:** It runs on every change detection cycle, even when nothing changed. That can mean hundreds of calls per second. Prefer `OnPush` with immutable data or Angular signals (v16+) before reaching for `ngDoCheck`. **Q:** How does hook order work in a parent-child component tree? **A:** Parent `ngOnInit` fires, then the child goes through its full lifecycle. Parent `ngAfterViewInit` fires only after all children have completed their own view initialization. **Q:** OnPush component with a mutable `@Input` array - why does the UI stop updating and how do you fix it? **A:** `OnPush` skips change detection if the input reference has not changed. Mutating an array with `.push()` or `.splice()` keeps the same reference, so Angular skips the component entirely. Fix with `ngDoCheck` + `cdRef.markForCheck()`, or better: replace the array with a new reference on every update. ## Examples ### Basic: tracking input changes with ngOnChanges ```typescript @Component({ selector: 'app-counter', template: '<button (click)="increment()">Count: {{ count }}</button>' }) export class CounterComponent implements OnChanges { @Input() max = 10; count = 0; ngOnChanges(changes: SimpleChanges) { if (changes['max']) { // Only react to the specific input that changed console.log('Max changed to', changes['max'].currentValue); if (this.count > changes['max'].currentValue) { this.count = 0; // reset if over new limit } } } increment() { this.count++; } } // Parent changes @Input max from 10 to 5 → "Max changed to 5" // If count was 7, it resets to 0 ``` `SimpleChanges` gives you both `currentValue` and `previousValue`. Without it you are reacting blindly to every change detection cycle. ### Intermediate: data loading with cleanup This pattern covers the most common production scenario: fetch on init, cancel on destroy. It comes directly from Angular's Tour of Heroes architecture. ```typescript @Component({ template: ` <div *ngIf="hero$ | async as hero"> {{ hero.name }} </div> ` }) export class HeroDetailComponent implements OnInit, OnDestroy { hero$: Observable<Hero>; private destroy$ = new Subject<void>(); constructor( private heroService: HeroService, private route: ActivatedRoute ) {} ngOnInit() { const id = this.route.snapshot.paramMap.get('id'); this.hero$ = this.heroService.getHero(id).pipe( takeUntil(this.destroy$) ); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); // prevents memory leak on route change } } // Hero loads on component init // Subscription cancels cleanly when user navigates away ``` ### Advanced: ngDoCheck with OnPush for mutable array detection ```typescript @Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: `<ul><li *ngFor="let item of items">{{ item }}</li></ul>` }) export class ItemListComponent implements DoCheck { @Input() items: string[] = []; private prevLength = 0; constructor(private cdRef: ChangeDetectorRef) {} ngDoCheck() { // OnPush misses array.push() because the reference stays the same if (this.items.length !== this.prevLength) { this.prevLength = this.items.length; this.cdRef.markForCheck(); // force re-render for this subtree } } } // Without ngDoCheck: UI freezes after array.push() // With it: rerenders on length change, but ngDoCheck runs every cycle (perf cost) // Better alternative: replace the array with a new reference on every update ``` In production I prefer the immutable update approach over `ngDoCheck`. The `markForCheck` path works, but every extra comparison you add to `ngDoCheck` eventually becomes a performance conversation during code review.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.