Open-Source Wikis

/

Angular

/

Background

/

Standalone and signals

angular/angular

Standalone and signals

Two related transitions that reshaped Angular between v14 and v20. Together they are the largest user-visible changes since Ivy.

The standalone transition (v14 → v17)

What changed

Pre-v14, every component, directive, and pipe lived in an NgModule's declarations array. Modules also declared providers, imported other modules, and exported their declarations. Standalone components inverted the model:

  • Components, directives, and pipes declare their own dependencies via imports: [...].
  • Application providers go through bootstrapApplication's providers and feature-style provideXxx functions.
// Pre-v14 (NgModule)
@NgModule({
  declarations: [HomePage, NavBar, FormatDatePipe],
  imports: [CommonModule, RouterModule.forRoot(routes), HttpClientModule],
  providers: [{ provide: API_URL, useValue: '/api' }],
  bootstrap: [HomePage],
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);

// Post-v17 default (standalone)
bootstrapApplication(HomePage, {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
    { provide: API_URL, useValue: '/api' },
  ],
});

Timeline

  • v14 (June 2022)standalone: true opt-in introduced.
  • v15 (Nov 2022)provideRouter and standalone-friendly APIs added.
  • v16 (May 2023) — every framework feature has a standalone equivalent.
  • v17 (Nov 2023)standalone: true becomes the default for ng new. Documentation rewritten around the new model. The --standalone=false flag still exists for legacy projects.
  • v18+NgModule remains supported but deprecated for new code; migration tooling is mature.

Why

Three problems with the NgModule model that the team consistently cited:

  • Indirection at scale. A component's dependencies were defined two files away in the module's declarations and imports arrays. New contributors couldn't see them in one place.
  • Boilerplate. Even tiny apps needed a module.ts file with the same BrowserModule, FormsModule, ... imports every time.
  • Tree-shaking ambiguity. A module could "leak" providers or directives that the component didn't actually use. Ivy's compile-time scope resolution made this avoidable, but only if the framework moved away from NgModule as the primary scope.

The migration is the canonical "automated" Angular major-version step; almost every existing app can run ng update @angular/core --migrate-only=standalone-migration and get a working result.

The signals transition (v16 → v20)

What changed

Pre-v16, Angular state lived in plain class fields backed by zone.js-tracked reactivity. Change detection re-evaluated all bindings on every tick, with OnPush as an opt-in optimization. Signals replaced this with an explicit reactive primitive.

// Pre-v16
@Component({...})
export class Counter {
  count = 0;
  increment() { this.count++; }   // Zone tracks the click; full tree dirty-walk.
}

// Post-v17
@Component({...})
export class Counter {
  count = signal(0);
  increment() { this.count.update(c => c + 1); }   // Only LViews reading `count` schedule.
}

Timeline

  • v16 (May 2023)signal(), computed(), effect() shipped as developer-preview.
  • v17.1input() (signal-based component inputs) shipped.
  • v17.3model() (signal-based two-way bindings) shipped.
  • v18 — Block control flow becomes default in ng new. Signals graduate to "stable for production" in messaging.
  • v19input(), output(), model(), viewChild(), viewChildren(), contentChild(), contentChildren() all promoted to stable.
  • v20 — Zoneless change detection becomes a fully supported mode; documentation leads with it. Signal forms (@angular/forms/signals) launches.
  • v21+ — The framework's own test patterns (the "Act, Wait, Assert" rule in AGENTS.md) standardize on zoneless idioms.

Why

Three problems with the zone-based model the team consistently cited:

  • Coarse change detection. A click anywhere in the page caused tick() to walk the whole tree. OnPush mitigated this but required discipline; it wasn't the default.
  • zone.js as a dependency. Patching every async API in the platform is invasive. Some environments (Web Workers, certain mobile runtimes, server runtimes) couldn't run Zones reliably.
  • Coordination with React-style ecosystems. Other frameworks moved to fine-grained reactivity (Solid, Vue, Svelte) and the resulting performance gap was visible.

Signals are the answer. They make change detection precise: only the LViews that read a signal mark themselves dirty when the signal changes. Combined with zoneless mode, this gives the framework React-fiber-grade scheduling without losing the declarative template model.

What the transitions share

Both standalone and signals are additive. NgModules and zone-based change detection still work; the team explicitly avoided a flag-day break. The migration tooling is built to run safely on production codebases of any size, and CI tests both the legacy and new modes.

The framework now leads with the new model in documentation while keeping the old one supported. Migration is automated; adoption is up to the application team.

What they're laying the ground for

The team has signaled (no pun intended) two longer-term directions both transitions enable:

  1. Eventually deprecating @NgModule. With every framework feature reachable through provideXxx, the only remaining NgModule use case is third-party libraries that haven't migrated. Once the long tail catches up, removal becomes possible.
  2. Eventually deprecating zone.js. With zoneless mode tested and supported, zone.js is on a similar long-tail timeline. The package will keep shipping for backward compatibility, but new framework features assume the zoneless model.

Both are intentionally on indefinite timelines. The internal direction documents are not public; the public-facing posts on blog.angular.dev are the canonical announcements.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Standalone and signals – Angular wiki | Factory