Suggest an editImprove this articleRefine the answer for “ViewChild and ContentChild in Angular”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`@ViewChild` and `@ContentChild`** are Angular decorators for querying elements inside a template. `@ViewChild` reads from the component's own template; `@ContentChild` reads content projected by a parent via `<ng-content>`. ```typescript @ViewChild('input') input!: ElementRef; // own template @ContentChild('title') title!: ElementRef; // projected from parent ``` **Key:** ViewChild resolves in `ngAfterViewInit`; ContentChild in `ngAfterContentInit`.Shown above the full answer for quick recall.Answer (EN)Image**`@ViewChild` and `@ContentChild`** are Angular decorators that give you direct access to child elements in a template. Where the element lives determines which one you need. ## Theory ### TL;DR - `@ViewChild` queries your component's own template; `@ContentChild` queries content a parent passes in via `<ng-content>` - Both return a single match; use `@ViewChildren` / `@ContentChildren` for multiple elements - `@ViewChild` resolves after `ngAfterViewInit`; `@ContentChild` after `ngAfterContentInit` - Decision rule: if you wrote the element in your own template, use ViewChild. If a parent passed it in, use ContentChild - Angular 17+ has signal-based alternatives: `viewChild()` and `contentChild()` ### Quick example ```typescript // Parent passes content into the card: // <app-card> // <h2 #title>My Title</h2> <-- projected content // </app-card> @Component({ selector: 'app-card', template: `<ng-content></ng-content>` }) export class CardComponent implements AfterContentInit { @ContentChild('title') titleRef!: ElementRef; ngAfterContentInit() { console.log(this.titleRef.nativeElement.textContent); // "My Title" } } ``` The `h2` lives in the parent's template, not inside `CardComponent`. That is why `@ContentChild` works here and `@ViewChild` would return `undefined`. ### Key difference `@ViewChild` looks inside the component's own template file. `@ContentChild` looks at what a parent projected into the `<ng-content>` slot. The lifecycle timing follows this split: ViewChild resolves in `ngAfterViewInit`, ContentChild in `ngAfterContentInit`. Use the wrong decorator and you get `undefined` with no helpful error message. ### When to use - `@ViewChild`: focus a form input, call a method on a child component, read a DOM element you defined in your own template - `@ContentChild`: building a reusable wrapper like a card, modal, or accordion that needs to interact with what was passed inside - `@ViewChildren`: multiple instances created by `*ngFor`, batch operations on sibling components - `@ContentChildren`: tab panels, accordion items, any repeated projected content - If you only need to pass data down, `@Input()` is simpler and no query is needed at all ### Comparison table | Aspect | @ViewChild | @ContentChild | |--------|------------|---------------| | Queries | Component's own template | Content passed via `<ng-content>` | | Available after | `ngAfterViewInit()` | `ngAfterContentInit()` | | Multiple elements | `@ViewChildren` | `@ContentChildren` | | Who defines the element | The component itself | The parent component | | Common use case | Form inputs, child component methods | Card headers, tab panels | ### How Angular resolves queries Angular scans the template (or projected content) for elements matching the selector. The selector is either a string that matches `#ref` template variables, or a type that matches the first instance of that component or directive class. For `@ViewChild`, the scan runs after the component's own DOM renders. For `@ContentChild`, it runs after the parent inserts projected content into the slot. The `static` option changes the timing. `{ static: true }` resolves the query before change detection, making the result available already in `ngOnInit`. Only use it for elements that always exist in the template and are never wrapped in `*ngIf` or `*ngFor`. In practice, `static: true` is one of the most overlooked details when Angular questions come up in interviews, and the default `{ static: false }` is correct for the overwhelming majority of cases. ### Signal queries (Angular 17+) Angular 17 introduced function-based signal queries as a modern alternative to decorators. ```typescript @Component({ /* ... */ }) export class MyComponent { // No lifecycle hook needed for access nameInput = viewChild.required<ElementRef>('nameInput'); chart = viewChild(ChartComponent); tabs = viewChildren(TabComponent); // Content queries title = contentChild<ElementRef>('title'); buttons = contentChildren(ButtonComponent); ngOnInit() { // Signals are reactive - no ngAfterViewInit required console.log(this.nameInput().nativeElement); } } ``` Signal queries are reactive by default. The value updates automatically when the template changes, without subscribing to `QueryList.changes`. ### Common mistakes **1. Accessing ViewChild in ngOnInit** The view has not rendered yet at that point. The result is always `undefined`. ```typescript // Wrong ngOnInit() { console.log(this.input.nativeElement); // TypeError: Cannot read properties of undefined } // Right ngAfterViewInit() { console.log(this.input.nativeElement); // works } ``` **2. Using @ViewChild for projected content** ```typescript // Wrong - returns undefined for elements the parent passed in @ViewChild('title') title!: ElementRef; // Right @ContentChild('title') title!: ElementRef; ``` **3. Treating QueryList as a static snapshot** `QueryList` is live. Cache the length once and it goes stale as soon as items are added or removed conditionally. ```typescript // Wrong - misses dynamically added items const count = this.items.length; // Right - reacts to additions and removals this.items.changes.subscribe(() => { console.log('Current count:', this.items.length); }); ``` **4. Missing the `read` option for directives** When a directive and a component share the same element, Angular defaults to returning the component instance. Use `read` to get the directive. ```typescript // Wrong - returns the component, not the directive @ViewChild(MyDirective) dir!: MyComponent; // Right @ViewChild(MyDirective, { read: MyDirective }) dir!: MyDirective; ``` **5. Updating template-bound data inside ngAfterViewInit** Setting a property that is bound in the template during `ngAfterViewInit` triggers `ExpressionChangedAfterItHasBeenCheckedError` because change detection has already completed. ```typescript // Wrong ngAfterViewInit() { this.title = 'new value'; // Error if title is bound in the template } // Right ngAfterViewInit() { this.title = 'new value'; this.cdr.detectChanges(); // tell Angular to run a second pass } ``` ### Real-world usage - Angular Material: `MatTabGroup` uses `@ContentChildren(MatTab)` to collect tab panels passed by the parent - Angular Forms: `FormGroupDirective` uses `@ViewChildren(FormControlName)` to track all registered controls - PrimeNG: `p-dropdown` uses `@ContentChild(PrimeTemplate)` to support custom item templates - Custom modals: `@ViewChild('closeBtn')` to move focus to the close button when the modal opens - Data tables: `@ViewChildren(TableRowComponent)` to update all visible rows after a filter change ### Follow-up questions **Q:** What is the difference between `@ViewChild('ref')` and `@ViewChild(MyComponent)`? **A:** String references match template variables (`#ref`). Type references find the first instance of that class in the template. Type queries are type-safe and easier to refactor when a component is renamed. **Q:** Why use `@ContentChild` instead of `@Input()`? **A:** `@Input()` passes data. `@ContentChild` gives you the actual DOM element or component instance, so you can call methods, read DOM properties, or apply styles directly. They solve completely different problems. **Q:** Can you use `@ViewChild` with `*ngIf`? **A:** Yes, but the result is `undefined` when the condition is false. Always null-check before accessing the element. Use `{ static: false }` (the default) so Angular waits for the element to exist before resolving the query. **Q:** What happens if multiple elements share the same template reference variable? **A:** `@ViewChild` returns only the first match. This is a common source of bugs inside loops. Use `@ViewChildren` to get all matching elements as a `QueryList`. **Q (senior):** How would you build a reusable form validation wrapper that works without the parent knowing which fields are inside? **A:** Create a `FormGroupComponent` that uses `@ContentChildren(FormControlName)` to discover controls inside `<ng-content>`. The wrapper handles validation logic and error display internally. The parent just wraps its fields and gets validation for free, with no tight coupling to the wrapper's internals. ## Examples ### Basic: controlling a native input with @ViewChild ```typescript @Component({ selector: 'app-search', template: ` <input #searchInput type="text" placeholder="Search..."> <button (click)="focusSearch()">Focus</button> ` }) export class SearchComponent implements AfterViewInit { @ViewChild('searchInput') searchInput!: ElementRef<HTMLInputElement>; ngAfterViewInit() { // DOM is ready here this.searchInput.nativeElement.focus(); } focusSearch() { this.searchInput.nativeElement.focus(); } } ``` `@ViewChild` resolves `#searchInput` after the component's view renders. Calling `.focus()` in `ngOnInit` would throw because the element does not exist yet at that stage. ### Intermediate: @ContentChild in a form wrapper ```typescript // Reusable wrapper - does not know which fields live inside @Component({ selector: 'app-form-group', template: `<div class="form-group"><ng-content></ng-content></div>` }) export class FormGroupComponent implements AfterContentInit { @ContentChild('password') passwordField!: ElementRef; ngAfterContentInit() { // The input was defined in the parent and projected here console.log('Field ready:', this.passwordField.nativeElement); } } // Parent projects the input into the wrapper @Component({ selector: 'app-login', template: ` <app-form-group> <input #password type="password" placeholder="Password"> </app-form-group> ` }) export class LoginComponent {} ``` The `#password` input lives in `LoginComponent`'s template. `FormGroupComponent` accesses it via `@ContentChild` because it was projected in, not defined inside the wrapper itself. ### Advanced: live tab system with @ContentChildren ```typescript @Component({ selector: 'app-tabs', template: ` <div class="tab-bar"> <button *ngFor="let tab of tabs; let i = index" (click)="selectTab(i)"> {{ tab.label }} </button> </div> <ng-content></ng-content> ` }) export class TabsComponent implements AfterContentInit { @ContentChildren('tabPanel') tabPanels!: QueryList<ElementRef>; tabs: { label: string }[] = []; ngAfterContentInit() { this.buildTabs(); // QueryList is live - updates when panels are added or removed this.tabPanels.changes.subscribe(() => this.buildTabs()); } buildTabs() { this.tabs = this.tabPanels.map((_, i) => ({ label: `Tab ${i + 1}` })); } selectTab(index: number) { this.tabPanels.forEach((panel, i) => { panel.nativeElement.hidden = i !== index; }); } } ``` Usage: ```html <app-tabs> <div #tabPanel>Content 1</div> <div #tabPanel>Content 2</div> <div #tabPanel>Content 3</div> </app-tabs> ``` `QueryList.changes` keeps the tab bar in sync when panels are added or removed at runtime. Without that subscription, the tab count would freeze at whatever it was during initialization.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.