skip to content

In an @angular/upgrade hybrid app, what do downgradeComponent, downgradeInjectable and UpgradeComponent each let you do?

level: middleimportance: should knowfreq 40%

answer

  1. direction is the name
  2. downgrade: Angular used from AngularJS
  3. upgrade: AngularJS used from Angular
  4. services have a recipe, not an API

basics

~20 s

downgradeComponent wraps an Angular component as an AngularJS directive, downgradeInjectable exposes an Angular service to AngularJS's injector, and UpgradeComponent is a base class for an Angular directive that wraps an AngularJS component. AngularJS services are upgraded with a plain Angular provider.

solid answer

~40 s

The names describe the direction. **`downgradeComponent({component})`** returns a factory you register with `.directive()` on an AngularJS module, so AngularJS templates can use an Angular component; inputs and outputs are bound with Angular-style `[prop]` and `(event)` syntax on kebab-case attributes. **`downgradeInjectable(token)`** returns a factory you register with `.factory()`, so AngularJS code can inject an Angular service by name. **`UpgradeComponent`** goes the other way: you write an Angular `@Directive` that extends it, declares the AngularJS component's inputs and outputs, and calls `super('ng1Name', elementRef, injector)`. There is **no API for upgrading an AngularJS service**: you add an Angular provider whose factory reads it from the AngularJS `$injector`. Each DOM element stays owned by exactly one framework.

code

ts · 13 lines
ts
import { Directive, ElementRef, Injector, input, output } from '@angular/core';
import { UpgradeComponent } from '@angular/upgrade/static';

@Directive({ selector: 'legacy-hero-detail' })
export class LegacyHeroDetail extends UpgradeComponent {
  // Must mirror the AngularJS component's bindings.
  hero = input<{ name: string }>();
  deleted = output<void>();

  constructor(elementRef: ElementRef, injector: Injector) {
    super('legacyHeroDetail', elementRef, injector);
  }
}

go deeper

for a junior

Remember the direction rule: downgrade means Angular code used from AngularJS, upgrade means AngularJS code used from Angular.

for a middle

Explain how each helper is registered, what an UpgradeComponent wrapper must declare, and the provider recipe for AngularJS services.

for a senior

Anticipate the limits: unsupported directive features, the template cache requirement, and services unavailable before a downgraded module loads.

for a principal

Decide which direction to bridge first so the hybrid needs as few wrappers as possible over its lifetime.

A hybrid application runs AngularJS and Angular in one page. The `@angular/upgrade/static` package provides the bridges, and the naming follows one rule: **downgrading** makes an Angular thing usable from AngularJS, **upgrading** makes an AngularJS thing usable from Angular. ## The four bridges | Need | Tool | Registered where | | :-- | :-- | :-- | | Use an Angular component in an AngularJS template | `downgradeComponent()` | AngularJS module, `.directive(name, factory)` | | Inject an Angular service into AngularJS code | `downgradeInjectable()` | AngularJS module, `.factory(name, factory)` | | Use an AngularJS component in an Angular template | `UpgradeComponent` (base class) | An Angular `@Directive` you write | | Inject an AngularJS service into Angular code | A provider recipe (no API) | Angular `providers` | ## Downgrading a component `downgradeComponent({component: HeroList})` returns a directive factory. Registering it as `heroList` makes `<hero-list>` usable in AngularJS templates. Options include: - `component`: the Angular component class; - `downgradedModule`: needed only when several Angular modules are downgraded with `downgradeModule()`; - `propagateDigest` (default `true`): whether to run `detectChanges` on the component on every AngularJS `$digest`; with `false`, it is still checked when its inputs change. The older `inputs`, `outputs` and `selectors` options are deprecated and no longer used. ## Downgrading a service `downgradeInjectable(HeroesService)` returns an AngularJS factory. Register it as `.factory('heroesService', ...)`, and AngularJS controllers inject `heroesService` like any other service, receiving the Angular instance. With `downgradeModule()`, the service only exists once its Angular module has been bootstrapped, so it must not be used where that is not guaranteed. ## Upgrading a component `UpgradeComponent` is an `@Directive()` base class. The wrapper you write must: 1. declare the AngularJS component's element **selector** in its own `@Directive`; 2. declare **all inputs and outputs** the AngularJS component expects, with names matching its `<` and `&` bindings; 3. **extend `UpgradeComponent`** and call `super('ng1Hero', elementRef, injector)` with the AngularJS component's registered name. The metadata must be written out because Angular's compiler needs it statically. Limitations of the wrapped AngularJS directive: - it must be a **component-style directive with a template**; `compile` (without `link`), `replace` and `terminal` are not supported; - a `templateUrl` must already be in **`$templateCache`**, because loading templates asynchronously is not supported; - its lifecycle hooks (`$onInit`, `$doCheck`, `$postLink`, `$onChanges`, `$onDestroy`) are called by the wrapper. ## Upgrading a service There is no dedicated API. You write an Angular provider that pulls the service from the AngularJS `$injector`: ```ts export const legacyCartProvider = { provide: LegacyCart, useFactory: (i: any) => i.get('legacyCart'), deps: ['$injector'], }; ``` `UpgradeModule` makes the AngularJS `$injector` available to Angular under the `'$injector'` token, which is what the recipe relies on. ## The ownership rule behind all of this The `UpgradeModule` documentation states the mental model: - **Each DOM element is owned by exactly one framework**, whichever instantiated it. - AngularJS directives always execute inside AngularJS, and Angular components inside Angular, wherever they appear. - For an upgraded or downgraded component, the **host element** belongs to the framework using it, and the **view** belongs to the other one. - So attributes in AngularJS templates are written **kebab-case**, while the binding syntax is still **Angular-style** (`[prop]`, `(event)`). ## Common mistakes - Registering `downgradeComponent`'s factory with `.component()` instead of `.directive()`. - Forgetting to list inputs and outputs on an `UpgradeComponent` wrapper. - Using `templateUrl` in an upgraded component without pre-loading the template cache. - Using a downgraded service outside the part of the app where its Angular module is guaranteed to be bootstrapped.

  • What does downgradeComponent's propagateDigest option change?
    By default (true) the downgraded Angular component runs detectChanges on every AngularJS $digest. Setting it to false skips that, and the component is only checked when one of its inputs changes, which saves work in digest-heavy AngularJS screens.
  • Why can an upgraded AngularJS component not use an arbitrary templateUrl?
    UpgradeComponent compiles the AngularJS template synchronously and does not support loading it asynchronously. A templateUrl works only if that template is already in AngularJS's $templateCache, for example pre-populated at build time.

saying these in an interview costs you the question

  • downgradeComponent makes an AngularJS component usable in Angular.
  • There is an upgradeInjectable function for AngularJS services.
  • An UpgradeComponent wrapper discovers the inputs and outputs by itself.
  • A downgraded component's DOM is shared and updated by both frameworks.
  • Any AngularJS directive, including replace and terminal ones, can be upgraded.