Key Takeaways
ngAfterContentInit()runs once per component instance, after Angular initializes and first checks its projected content.- Use it for one-time work that needs initialized content-query results, such as
@ContentChildor@ContentChildren. - Projected content comes from the parent; the component’s own template is its view. Use
ngAfterViewInit()for initialized view queries. - Reading a query is appropriate here; changing an already-checked binding can cause
ExpressionChangedAfterItHasBeenCheckedErrorin development mode.
Angular ngAfterContentInit is the lifecycle hook for one-time initialization that depends on content supplied by a parent component. Angular calls it after the initial content check, when content-query results are available—not after every subsequent content change.
What counts as projected content?
Projected content is markup that a parent places between a child component’s opening and closing tags. The child declares an <ng-content> slot to control where that markup appears.
The distinction is ownership: the parent declares the projected content, while the receiving component declares its own view. <ng-content> is a projection placeholder, not a structural directive and not a rendered DOM element. Our Angular ng-content example explains the projection mechanism in more detail.
When does Angular ngAfterContentInit run?
For a component’s initial lifecycle, the relevant order is:
ngOnChanges, if inputs change
|
v
ngOnInit
|
v
ngDoCheck
|
v
ngAfterContentInit projected content initialized; runs once
|
v
ngAfterContentChecked content checked; also runs on later checks
|
v
ngAfterViewInit component's own view initialized; runs once
|
v
ngAfterViewChecked view checked; also runs on later checksngAfterContentInit() follows the first ngDoCheck() and precedes ngAfterViewInit() for that component instance. This is a lifecycle sequence, not a guarantee that the browser has painted the page.
ngAfterContentInit example with ContentChild
This example has a parent that supplies a button and a panel that projects it. The panel queries the projected button with @ContentChild and its own heading with @ViewChild.
In a client-side standalone Angular application, replace src/main.ts with the following. The application’s index.html must contain <app-root></app-root>.
import {
Component,
ContentChild,
ElementRef,
ViewChild,
} from '@angular/core';
import type {
AfterContentInit,
AfterViewInit,
DoCheck,
OnInit,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Component({
selector: 'app-action-panel',
standalone: true,
template: `
<section>
<h2 #heading>Actions</h2>
<ng-content></ng-content>
</section>
`,
})
export class ActionPanelComponent
implements OnInit, DoCheck, AfterContentInit, AfterViewInit {
@ContentChild('action', { read: ElementRef })
action?: ElementRef<HTMLButtonElement>;
@ViewChild('heading', { read: ElementRef })
heading?: ElementRef<HTMLHeadingElement>;
ngOnInit(): void {
console.log('ngOnInit');
}
ngDoCheck(): void {
console.log('ngDoCheck');
}
ngAfterContentInit(): void {
console.log('ngAfterContentInit: action found', this.action !== undefined);
}
ngAfterViewInit(): void {
console.log('ngAfterViewInit: heading found', this.heading !== undefined);
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [ActionPanelComponent],
template: `
<app-action-panel>
<button #action type="button" (click)="clickMe()">
Click me
</button>
</app-action-panel>
`,
})
export class AppComponent {
clickMe(): void {
console.log('button clicked');
}
}
bootstrapApplication(AppComponent).catch((error: unknown) => {
console.error('Application bootstrap failed', error);
});The panel’s initialization logs appear in this order:
ngOnInit
ngDoCheck
ngAfterContentInit: action found true
ngAfterViewInit: heading found trueThe 'action' query locator matches the parent’s #action template reference variable. The read: ElementRef option requests an element reference from that match; it isn’t a CSS selector query. Angular resolves this dynamic content query before calling ngAfterContentInit().
Click the button and you’ll see button clicked. Clicking it doesn’t rerun ngAfterContentInit(); that hook has already completed for this panel instance. ngDoCheck() can run again when Angular checks the panel, so don’t treat its log count as an application contract.
ngAfterContentInit vs ngAfterViewInit
| Question | Projected content | Component’s own view |
|---|---|---|
| Who declares it? | The parent using the component | The component’s template |
| Which element in the example? | The button marked #action | The heading marked #heading |
| Which query? | @ContentChild or contentChild() | @ViewChild or viewChild() |
| Which initialization hook? | ngAfterContentInit() | ngAfterViewInit() |
Although both elements render inside the panel, they belong to different query categories. The heading is read in ngAfterViewInit() because it belongs to the panel’s template. See the Angular ngAfterViewInit example for the view-focused case.
When should you use ngAfterContentInit?
Use this hook when a component needs a one-time inspection of its initialized content-query results. For example, a reusable panel might check whether an optional projected action exists, or validate that callers supplied a required projected directive.
If initialization only depends on inputs or ordinary component state, prefer ngOnInit for component initialization. Don’t delay unrelated setup until content initialization.
ContentChild timing and missing results
@ContentChild uses static: false by default. Its result is ready by ngAfterContentInit(), and Angular updates it when matching content changes. A missing match can still produce undefined, which is why the example uses an optional property.
With static: true, a query resolves before ngOnInit() but does not update afterward. Use that option only when the target is guaranteed to be present, not as a workaround for conditional content.
ContentChildren for multiple projected items
@ContentChildren provides a QueryList of matching projected items, available in ngAfterContentInit(). Its changes observable lets you respond when the query results change, instead of repeatedly scanning them in a lifecycle hook.
Content queries do not look inside another component’s private template. A projected wrapper doesn’t make that wrapper’s internal view queryable by the receiving panel.
Using signal-based content queries
Angular recommends signal-based queries for new projects; decorator-based queries remain supported. A signal is a value you read by calling it, and Angular keeps query signals updated as matching content changes.
The following is a replacement fragment for the panel above. Merge these imports into src/main.ts, replace the earlier panel class, and keep the parent and bootstrap call.
// Replacement panel fragment; retain AppComponent and its bootstrap call.
import { Component, computed, contentChild, ElementRef } from '@angular/core';
import type { AfterContentInit } from '@angular/core';
@Component({
selector: 'app-action-panel',
standalone: true,
template: `
<section>
<h2>Actions</h2>
<ng-content></ng-content>
<p>Action available: {{ hasAction() }}</p>
</section>
`,
})
export class ActionPanelComponent implements AfterContentInit {
readonly action = contentChild<ElementRef<HTMLButtonElement>>('action');
readonly hasAction = computed(() => this.action() !== undefined);
ngAfterContentInit(): void {
console.log('Initial projected action:', this.action());
}
}contentChild() returns a signal containing the first match or undefined; contentChildren() returns a signal containing a read-only array of matches. Here, computed() derives whether an action exists without copying that result into mutable state inside the hook. If you only need that derived value, remove the logging hook entirely.
Common Mistakes
Changing an already-checked binding
Updating state that Angular has already checked—for example, a bound value on projected content or its parent—can trigger ExpressionChangedAfterItHasBeenCheckedError. Angular reports this error in development mode when its additional check finds that a binding changed after change detection.
Don’t respond by adding arbitrary timers or forcing extra checks. Move ordinary initialization earlier, or derive query-dependent values with a signal query and computed(), as shown above.
Expecting the hook to observe later content changes
ngAfterContentInit() does not rerun when conditional content appears later. ngAfterContentChecked() runs after subsequent content checks, but it runs frequently; prefer query signals, query-result notifications, or explicit events for targeted reactions. See ngAfterContentChecked timing for that distinction.
Likewise, ngDoCheck and custom change detection is not a general-purpose replacement for a one-time hook. Avoid putting expensive repeated work there.
FAQ
When does Angular call ngAfterContentInit?
Angular calls ngAfterContentInit once per component instance, after initializing and first checking its content. It follows the first ngDoCheck and runs before ngAfterViewInit for that instance.
Can I use ContentChild in ngAfterContentInit?
Yes. Dynamic @ContentChild results and @ContentChildren query lists are available by ngAfterContentInit. A query can still have no matching content, so handle an absent result when the content is optional.
How is ngAfterContentInit different from ngAfterViewInit?
ngAfterContentInit concerns content supplied by the parent and projected into the component. ngAfterViewInit concerns the component’s own template and is the initialization hook for view queries.
Can I force ngAfterContentInit to run more than once?
No, Angular schedules it once for each component instance. Calling the method yourself only executes your method body; it does not repeat Angular’s initialization lifecycle. Use query updates or explicit events to respond to later changes.
Why can changing a value in ngAfterContentInit cause ExpressionChangedAfterItHasBeenCheckedError?
Changing an already-checked binding during ngAfterContentInit can leave Angular’s change-detection results inconsistent. In development mode, Angular’s additional check detects that difference and throws the error. Initialize ordinary state earlier or derive query-dependent state with computed signals.
Further Reading
- Angular component lifecycle — hook timing, execution order, and state-change cautions.
- Content projection with ng-content — parent-supplied content versus the component’s own view.
- Referencing component children with queries — signal queries, decorator queries, and derived values.
- ContentChild API — query locators, read options, and static versus dynamic timing.
- ContentChildren API — multiple results and query-change notifications.
- Signal-based contentChild API — typed signal query declarations.
- Angular NG0100 error reference — why checked bindings must remain consistent.
- Using DOM APIs in Angular — render callbacks and browser-only DOM work.
