Why was ComponentFactoryResolver removed in Angular 22, and how do you migrate code that still uses it?
answer
- a View Engine-era indirection
- deprecated since v13
- Ivy components carry their own definition
- pass the class directly
basics
~20 sSince 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 sIn 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 linesimport {
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
Recall that current Angular creates components from code by passing the class directly, and that ComponentFactoryResolver no longer exists.
Explain why factories existed under View Engine, why Ivy made them redundant, and the one-to-one replacements for each factory-based call.
Lead the migration: injector differences in standalone createComponent, ApplicationRef attachment, reflectComponentType for metadata, and replacing instance-field input writes.
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.