skip to content

In Angular, in what order are host directives and their host component constructed, given inputs and applied host bindings, and what does that order imply?

level: middleimportance: should knowfreq 30%

answer

  1. the host goes last
  2. three phases, same order each time
  3. nested chains run innermost first
  4. last writer wins on the element

basics

~20 s

Host directives run before the component or directive that declares them in every phase: they are constructed first, receive inputs and ngOnInit first, and apply host bindings first. The host therefore applies its host bindings last and can override theirs.

solid answer

~40 s

Angular orders host directives ahead of their host in each phase. For `hostDirectives: [MenuBehavior]` on `AdminMenu`, the documented order is: `MenuBehavior` constructed, `AdminMenu` constructed, `MenuBehavior` receives inputs and runs `ngOnInit`, `AdminMenu` does the same, `MenuBehavior` applies host bindings, then `AdminMenu`. Chains extend this depth-first: if `EvenMoreCustomTooltip` composes `CustomTooltip`, which composes `Tooltip`, each phase runs `Tooltip`, then `CustomTooltip`, then `EvenMoreCustomTooltip`. Several entries in one array run in the order listed, each after its own nested host directives. The practical consequences: the host's host bindings are applied last, so a component can override a class or attribute a host directive sets, and a host directive's constructor runs before the host's.

code

ts · 22 lines
ts
import { Component, Directive, OnInit, inject, input } from '@angular/core';

@Directive({ host: { 'role': 'tooltip-host' } })
export class Tooltip implements OnInit {
  readonly text = input('');
  ngOnInit(): void {
    console.log('1: Tooltip ngOnInit, text =', this.text());
  }
}

@Component({
  selector: 'app-menu-item',
  template: '<ng-content />',
  host: { 'role': 'menuitem' },
  hostDirectives: [{ directive: Tooltip, inputs: ['text: hint'] }],
})
export class MenuItem implements OnInit {
  private readonly tooltip = inject(Tooltip);
  ngOnInit(): void {
    console.log('2: MenuItem ngOnInit, hint =', this.tooltip.text());
  }
}

go deeper

for a junior

Recall that host directives run before the component that declares them, so the component's host bindings are applied last.

for a middle

Walk through the three phases, the depth-first order for nested chains and array order for siblings.

for a senior

Use the order to reason about overrides and initialisation, and avoid constructor-time assumptions between a directive and its host.

for a principal

Rely on the host-last rule when designing composed behaviours: directives supply defaults, components own final decisions on their element.

## Why the order matters A component with host directives has several classes contributing to one element. They may all set attributes or classes, they may inject each other, and their lifecycle hooks may depend on each other's state. Knowing which runs first answers questions like "who wins when both set `role`?" and "can the component read the directive's input in `ngOnInit`?". ## The documented order Angular's rule is simple: **host directives execute their constructor, lifecycle hooks and bindings before the component or directive on which they are applied.** For one host directive: 1. `MenuBehavior` is instantiated. 2. `AdminMenu` is instantiated. 3. `MenuBehavior` receives its inputs and runs `ngOnInit`. 4. `AdminMenu` receives its inputs and runs `ngOnInit`. 5. `MenuBehavior` applies its host bindings. 6. `AdminMenu` applies its host bindings. Within each phase, host directives go first and the host goes last. ## Nested chains and several entries Host directives can have host directives of their own. Angular resolves the tree **depth-first**, so the innermost directive comes first: | Phase | 1st | 2nd | 3rd | | --- | --- | --- | --- | | Instantiation | `Tooltip` | `CustomTooltip` | `EvenMoreCustomTooltip` | | Inputs and `ngOnInit` | `Tooltip` | `CustomTooltip` | `EvenMoreCustomTooltip` | | Host bindings | `Tooltip` | `CustomTooltip` | `EvenMoreCustomTooltip` | Here `EvenMoreCustomTooltip` composes `CustomTooltip`, which composes `Tooltip`. When one array lists several entries, such as `[Tooltip, FocusRing]`, they are processed in array order, each preceded by its own nested host directives, and the host comes after all of them. On an element that also has selector-matched directives, the source resolves a component's host directives, then the component, then the host directives of other matched directives, then those directives. ## What follows from it - **The host can override host bindings.** Because the host applies its host bindings last, if a `Tooltip` host directive sets `'[attr.role]'` and `MenuItem` sets `'role': 'menuitem'`, the component's binding is the one that sticks. The documentation calls this out as the reason for the order. - **Constructor-time injection works in both directions** since both instances exist on the same element injector, but the host directive is constructed first, so it should not assume the host has finished setting up during its own constructor. - **Inputs arrive before the host's `ngOnInit`.** Because inputs and `ngOnInit` run for the host directive first, the component's `ngOnInit` can read the host directive's inputs through an injected instance. - **Array order is a design decision.** If two host directives in the same array bind the same attribute, the later one's host binding is applied after the earlier one's. ## Dependency injection alongside the order The documented execution order interacts with two DI rules: - **Mutual injection.** The component can inject its host directives and a host directive can inject the component, because they share the element's injector. Keep constructors free of logic that depends on the other class having finished its setup; read shared state in `ngOnInit` or later, when the documented order guarantees inputs have arrived. - **Provider precedence.** If the component and a host directive both provide the same token, the providers of the class that declares `hostDirectives` take precedence. This mirrors the host-last rule for bindings: the composing class has the final say. ## Destroying The documentation specifies the order for construction, inputs, `ngOnInit` and host bindings. It does not add a separate rule for teardown, so code should not depend on whether a host directive's `ngOnDestroy` runs before or after the component's. If cleanup must be ordered, put it in one place, typically the component, or register it through `DestroyRef` where each class cleans up only its own resources. ## Interview pitfalls A common wrong answer is that the component is created first and then "decorated" by its host directives, as a wrapper would be. The opposite is true: the directives are the foundation, and the component is applied on top. Another is assuming a flat, arbitrary order; the order is deterministic and documented.

  • In `hostDirectives: [Tooltip, FocusRing]`, where `Tooltip` itself has `hostDirectives: [Positioner]`, what is the instantiation order?
    Depth-first and in array order: `Positioner`, then `Tooltip`, then `FocusRing`, and finally the component that declares the array. The same order applies to inputs and `ngOnInit`, and to host bindings.
  • Why did Angular choose to apply the host's bindings after its host directives' bindings?
    So the component stays in control of its own element. A reusable directive sets sensible defaults, and the component composing it can override any of them with its own host binding, which it could not do if the directive were applied last.

saying these in an interview costs you the question

  • The component is created first and its host directives wrap it afterwards.
  • Host directive host bindings override the component's own host bindings.
  • Host directives in one array run in an unspecified order.
  • In a nested chain, the outermost host directive runs first.
  • A host directive's inputs are set only after the component's ngOnInit.