Signals are a reactive primitive introduced in Angular 16 that wrap a value and notify interested consumers when the value changes. A signal is created with signal(initialValue), read by calling it as a function, and updated with set() or update(). Unlike BehaviorSubject, signals are synchronous and integrate automatically with Angular's change detection, so there is no need for subscribe() or the async pipe. computed() creates a derived signal whose value is automatically recalculated when any of its tracked signal dependencies change, and it is both lazy and memoized, recomputing only when read and a dependency has changed. effect() runs a side-effecting function whenever any signal it reads changes, executing asynchronously via the microtask queue after change detection. Effects must be created in an injection context unless an explicit injector is provided or manualCleanup is set. untracked() lets you read signals inside an effect without creating a dependency, useful for reading current values inside handlers. The toSignal() and toObservable() interop functions allow bridging between signals and RxJS.
Standalone components, directives, and pipes (introduced in Angular 14) do not need to be declared in an NgModule, instead declaring their own dependencies in the imports array of @Component(). The application is bootstrapped with bootstrapApplication(AppComponent, appConfig), which replaces the module-based bootstrapModule() flow. The appConfig object provides top-level services like provideRouter(routes, withComponentInputBinding(), withViewTransitions()) and provideHttpClient(withInterceptors([...]), withFetch()). Standalone exports require explicit re-export from barrel files when reusing across boundaries.
Angular 17 introduced new built-in block control flow that replaces the *ngIf, *ngFor, and *ngSwitch structural directives with native template syntax: @if (cond) { ... } @else { ... }, @for (item of items; track item.id) { ... } @empty { ... }, and @switch (val) { @case (x) { ... } @default { ... } }. The new control flow is faster, has built-in track, and requires no imports of CommonModule. The track expression is required in @for to identify items uniquely and avoid unnecessary DOM re-creation, throwing a compile-time error if omitted. The @empty block renders content when the iterable is empty. Angular 18 added the @let block, which declares a local template variable scoped to the surrounding block that is reactive and re-evaluates when signals it reads change. The @defer block enables fine-grained code-splitting per template block, lazily loading content only when a trigger fires, with @placeholder, @loading, and @error blocks providing graceful fallbacks. Triggers include on viewport(ref), on idle, on immediate, on timer(Xms), on interaction(ref), on hover(ref), and when condition(), and they can be combined with semicolons. Angular 17.1 added signal-based inputs via input(), with transform support for built-in numberAttribute and booleanAttribute helpers and alias support for decoupling public API names from internal class properties. model() creates a two-way bindable signal that replaces separate @Input() and @Output() pairs, and output() is the signal-era equivalent of @Output() with EventEmitter. Angular 17.2 added signal-based queries: viewChild() returns a single-element Signal while viewChildren() returns a Signal of all matches, and contentChild() queries projected content, all push-based and emitting when elements appear.