skip to content

Why was ComponentFactoryResolver removed in Angular 22, and how do you migrate code that still uses it?

level: middleimportance: nice to knowfreq 30%

answer

  1. a View Engine-era indirection
  2. deprecated since v13
  3. Ivy components carry their own definition
  4. pass the class directly

basics

~20 s

Since Ivy, a component class carries its own compiled definition, so the factory lookup became redundant; ComponentFactoryResolver was deprecated in v13 and removed in v22. Pass the class straight to ViewContainerRef.createComponent or the standalone createComponent.

solid answer

~40 s

In the View Engine era, creating a component from code needed a `ComponentFactory` produced by `ComponentFactoryResolver.resolveComponentFactory(Type)`, and the component had to be registered for it. Ivy compiles each component's definition onto the class itself, so the class is all Angular needs; the resolver became an extra indirection. It was deprecated in v13, dropped from the Router's APIs in v16, and in v22 `ComponentFactoryResolver` and `ComponentFactory` were removed from the public API. Migration: replace `vcr.createComponent(resolver.resolveComponentFactory(Widget))` with `vcr.createComponent(Widget)`; replace `factory.create(injector)` for detached components with the standalone `createComponent(Widget, { environmentInjector, elementInjector, hostElement })`; read selectors, inputs and outputs that code used to take from the factory via `reflectComponentType(Widget)`; delete the resolver injection.

code

ts · 22 lines
ts
import {
  ApplicationRef, EnvironmentInjector, Injectable, createComponent, inject, reflectComponentType,
} from '@angular/core';
import { Banner } from './banner';

@Injectable({ providedIn: 'root' })
export class BannerService {
  private readonly environmentInjector = inject(EnvironmentInjector);
  private readonly appRef = inject(ApplicationRef);

  show(text: string): () => void {
    // Before (removed in v22):
    //   const factory = this.cfr.resolveComponentFactory(Banner);
    //   const ref = factory.create(this.injector);
    const ref = createComponent(Banner, { environmentInjector: this.environmentInjector });
    ref.setInput('text', text);
    this.appRef.attachView(ref.hostView);
    document.body.appendChild(ref.location.nativeElement);
    console.log('rendered', reflectComponentType(Banner)?.selector);
    return () => ref.destroy();
  }
}

go deeper

for a junior

Recall that current Angular creates components from code by passing the class directly, and that ComponentFactoryResolver no longer exists.

for a middle

Explain why factories existed under View Engine, why Ivy made them redundant, and the one-to-one replacements for each factory-based call.

for a senior

Lead the migration: injector differences in standalone createComponent, ApplicationRef attachment, reflectComponentType for metadata, and replacing instance-field input writes.

for a principal

Plan the upgrade path for a large codebase with legacy dynamic-creation code, sequencing API removal with OnPush and zoneless default changes.

## What ComponentFactoryResolver was Before Ivy, Angular's View Engine compiled components into separate **factories**. Code that created a component at runtime had to ask a `ComponentFactoryResolver` for the factory of a class, then pass that factory to `ViewContainerRef.createComponent` or call `factory.create(injector)`. Components created this way also had to be listed as entry components so their factories were generated. ## Why it stopped being needed Ivy, the compiler and runtime that replaced View Engine (whose removal the Angular roadmap records as completed in 2022), compiles a component's definition as a static property **on the class itself**. Given the class, Angular already has everything the factory used to provide: the selector, inputs, outputs, template function and styles. The resolver added a lookup step that returned information the class already carried. The removal was gradual: 1. v13 deprecated the `ComponentFactoryResolver`-related symbols in `@angular/core`, as the v14 changelog records. 2. v14.2 added the standalone `createComponent` function for creating components outside a view container. 3. v16 removed `ComponentFactoryResolver` from the Router's APIs. 4. v22 removed `ComponentFactoryResolver` and `ComponentFactory` from the public API; the changelog tells you to pass the component class to the APIs that previously required a factory. ## Migration map | Old code | Replacement | |---|---| | `inject(ComponentFactoryResolver)` | delete it | | `vcr.createComponent(cfr.resolveComponentFactory(Widget))` | `vcr.createComponent(Widget)` | | `cfr.resolveComponentFactory(Widget).create(injector)` | `createComponent(Widget, { environmentInjector, elementInjector })` | | `factory.selector`, `factory.inputs`, `factory.outputs` | `reflectComponentType(Widget)?.selector`, `.inputs`, `.outputs` | | `ref.instance.someInput = value` (common alongside factories) | `ref.setInput('someInput', value)` or `inputBinding(...)` | | `entryComponents: [...]` leftovers | delete; the field itself was removed in v16 | ## Details that trip migrations - **Injectors.** `factory.create(injector)` took an element injector; the standalone `createComponent` requires an `environmentInjector` and accepts an optional `elementInjector`. Inside a service, `inject(EnvironmentInjector)` supplies the first. - **Attaching.** A component created with the standalone function is attached to nothing. Call `ApplicationRef.attachView(ref.hostView)` for change detection and insert `ref.location.nativeElement` (or your `hostElement`) into the DOM, exactly as with `factory.create`. - **Inputs.** Old code often assigned fields on `ref.instance`. Under v22's default `OnPush` and v21's default zoneless scheduling, that no longer reliably refreshes the view; move to `setInput` or creation-time bindings while you are touching the code. - **NgComponentOutlet.** Its `ngModuleFactory` input was removed earlier; use the class and, for legacy modules, `ngComponentOutletNgModule`. ## Why interviewers ask The question checks two things: whether you know the modern API (the class goes straight in), and whether you understand **why** the old API existed: View Engine's separate factories versus Ivy's self-describing classes. A good answer names the replacement for each old use and mentions the input-assignment trap that often travels with factory-era code. ## Common misconceptions - **"Dynamic components went away with the resolver."** Only the factory indirection went; both creation APIs are fully supported and gained creation-time bindings in v20. - **"entryComponents is still required."** It was a factory-era requirement, and the field itself was removed in v16. - **"The standalone createComponent is a drop-in for factory.create."** It is close, but it needs an `EnvironmentInjector`, and you still attach the view and insert the host element yourself. - **"The upgrade is purely mechanical."** Replacing the resolver is mechanical; the input-assignment code that usually surrounds it needs real attention under `OnPush` by default. ## Checklist for a codebase upgrade - Search for `ComponentFactoryResolver`, `resolveComponentFactory` and `ComponentFactory` and replace each use per the table. - Replace instance-field input writes with `setInput` or bindings. - Remove any `entryComponents` leftovers. - Run the component tests; dynamic components are where OnPush-by-default surprises show up first.

  • Where do you get a component's selector or input names now that ComponentFactory is gone?
    `reflectComponentType(ComponentClass)` returns a `ComponentMirror` with `selector`, `inputs` (each with `propName`, `templateName`, and whether it is a signal input), `outputs`, `ngContentSelectors` and `isStandalone`. It returns `null` for a class that is not a component.
  • What replaces factory.create(injector) for a component that must render outside any view container?
    The standalone `createComponent(Widget, { environmentInjector, elementInjector, hostElement })` from `@angular/core`. It returns an unattached `ComponentRef`, so you call `ApplicationRef.attachView(ref.hostView)` for change detection, put the host element into the DOM, and call `ref.destroy()` when finished.

saying these in an interview costs you the question

  • ComponentFactoryResolver was removed because dynamic components are no longer supported.
  • You still need entryComponents for components created from code.
  • The standalone createComponent inserts the component into the DOM automatically.
  • ComponentFactoryResolver still works in v22 with a deprecation warning.