Angular PrimerAngular 22 · 2022 → 2026

Old → new cheatsheet

Every substitution from the 2022 way of writing Angular to the 2026 way, grouped by area. Keep this one open while you migrate.

Each line reads what you used to write → what you write now. Where the change is more than syntax, the topic that explains it is linked from the heading.

One computed over two inputs

The query and the topic both feed the same computed, so the table and the row count can never disagree. Type signal, or *ngIf, or pick a topic.

16 rows
BeforeNowTopic
*ngIf / *ngFor@if / @forTemplates
ngClass / ngStyle[class.x] / [style.x]Templates
structural directive microsyntax@switch / @letTemplates
NgModulesstandalone componentsComponents
@Input() / @Output()input() / output()Components
constructor injectioninject()DI
classes with providedIn@Service() with a scopeDI
HttpModule + map(res => res.json())provideHttpClient + httpResource()HTTP
subscribe() in ngOnInitresource() / signalsHTTP
RouterModule.forRootprovideRouter(routes)Routing
ActivatedRoute.snapshot.paramswithComponentInputBinding()Routing
class guards and resolversCanMatchFn and ResolveFn functionsRouting
new FormControl + Validators.xform() + field stateForms
zone.js patching everythingprovideZonelessChangeDetection()Runtime
OnPush written on every componenton by default, signals drive itRuntime
prerender as an add-onoutputMode: static + hydrationRuntime

State & reactivity

See Signals.

  • new BehaviorSubject<T>(initial)signal<T>(initial)
  • .next(value).set(value) / .update(fn)
  • .getValue() / .value → call it: count()
  • combineLatest([...]).pipe(map(...)) for derived state → computed(() => ...)
  • {{ stream$ | async }}{{ value() }}
  • A field plus ngOnChanges to derive it → computed() over the input signal
  • A field plus subscribe to keep it in sync → linkedSignal()
  • toSignal(observable$) — still right — for streams you want as state
  • effect() to keep state in sync → computed (derived) or linkedSignal (writable)
  • effect() for DOM access after render → afterRenderEffect()

Components & templates

See Standalone components, The component contract and Templates & control flow.

  • @NgModule + declarationsimports on the component that needs it
  • standalone: true → the default; the flag is going away
  • platformBrowserDynamic().bootstrapModule(AppModule)bootstrapApplication(App, appConfig)
  • providers: [...] in a module → providers in app.config.ts, or on a route/component
  • @Input() name!: stringname = input.required<string>()
  • @Input() name = 'x'name = input('x')
  • @Output() saved = new EventEmitter<T>()saved = output<T>()
  • @Input() + @Output() xChangex = model<T>()
  • @ViewChild('el') el!: ElementRefel = viewChild.required<ElementRef>('el')
  • @ViewChildren(X)viewChildren(X)
  • @ContentChild(X)contentChild(X)
  • *ngIf="cond"@if (cond) { }
  • *ngIf="a; else b" + ng-template@if (a) { } @else { }
  • *ngFor="let x of xs; trackBy: fn"@for (x of xs; track x.id) { }
  • *ngIf="xs.length" around a *ngFor@empty { } on the @for
  • [ngSwitch] + *ngSwitchCase@switch / @case / @default
  • An ng-container to hold a local value → @let
  • CommonModule imported for the basics → nothing; control flow is syntax, pipes import singly
  • A method call in the template → computed()

Dependency injection

See Dependency injection.

  • constructor(private api: Api) {}private readonly api = inject(Api);
  • @Injectable({ providedIn: 'root' })@Service()
  • APP_INITIALIZER multi-provider → provideAppInitializer(() => ...)
  • Constructor-only injection (guards, resolvers, factories) → inject() anywhere in an injection context
  • A lazily loaded dependency inside a constructor → injectAsync()
  • { provide: X, useClass: Y } — unchanged, still the seam for tests

Routing

See Routing.

  • RouterModule.forRoot(routes)provideRouter(routes, ...features)
  • RouterModule.forChild(routes) in a feature module → loadChildren: () => import('./x.routes').then((m) => m.X_ROUTES)
  • A lazy module → loadComponent: () => import('./page').then((m) => m.Page)
  • Title.setTitle() in a component → title on the route (static or a function)
  • A class CanActivate guard registered in providers → a CanActivateFn in the route array
  • ActivatedRoute.paramMap.subscribe(...)withComponentInputBinding() + slug = input.required<string>()
  • Navigate imperatively in a guard → return a UrlTree
  • Nothing (transitions came from CSS) → withViewTransitions()

Data & forms

See HTTP & async data and Forms.

  • HttpClientModuleprovideHttpClient(withFetch())
  • A class HttpInterceptor + HTTP_INTERCEPTORS → an HttpInterceptorFn + withInterceptors([...])
  • subscribe() + a loading boolean + an error field → resource({ params, loader })
  • switchMap on a param stream to refetch → resource({ params: () => this.query() })
  • A GET whose URL comes from signals → httpResource<T>(() => \/api?q=${this.query()}`)`
  • loading: true in the template → @if (res.isLoading())
  • Manual retry wiring → res.reload()
  • new FormGroup({...}) → still supported; or form(this.model, (path) => {...}) for Signal Forms
  • Validators.requiredrequired(path.x, { message })
  • form.valueChanges.subscribe(...) → read the field signal: signup.email().value()
  • form.statusChanges to drive the submit button → signup().invalid()

Change detection & runtime

See Zoneless change detection, Performance and SSR & hydration.

  • zone.js polyfill in angular.json → nothing; zoneless is the default
  • provideZoneChangeDetection()provideZonelessChangeDetection() (usually implicit)
  • changeDetection: ChangeDetectionStrategy.OnPush on every component → the default for new components
  • A plain field updated from setTimeout/websockets → a signal
  • markForCheck() / detectChanges() → make the value a signal
  • DetectChanges confusion in dev → provideCheckNoChangesConfig({ exhaustive: true })
  • @angular/platform-server + manual prerender wiring → ng add @angular/ssr + outputMode / routesFile
  • Re-rendering on the client after SSR → provideClientHydration(withEventReplay())
  • Losing clicks before hydration finishes → event replay (above)
  • isPlatformBrowser everywhere → afterNextRender() where the work is not render-related

Build & CLI

  • Webpack build → esbuild + Vite (the CLI's own pipeline)
  • ng build --prodng build (production is the default configuration)
  • ng lint with TSLint → ESLint via ng add @angular/eslint
  • A hand-rolled "did I break the bundle" check → build budgets in angular.json
  • Manual module conversion → ng generate @angular/core:standalone
  • Manual *ngIf rewrite → ng generate @angular/core:control-flow

The three rules behind all of it

If a substitution above is not obvious, it is probably one of these:

  1. Read state where you use it, write it where it changes. Signals make the dependency explicit, which is what the rest of the framework now leans on — see Signals.
  2. Derive, do not synchronise. If a value follows from other state, it is a computed; if it must also be writable, it is a linkedSignal.
  3. Let the framework decide when to render. No nudges, no tick(), no setTimeout to wait for the DOM — see Zoneless change detection.

And if you want the story in reading order rather than as a lookup table, start at Orientation.