Skip to content

Angular 20 RC Preview: Stable Zoneless, Signal Forms, and New Router API

The Angular 20 Release Candidate was published in early April 2025, with the stable release expected in May. Angular 20 follows Angular 19 (November 2024) as the next major version, with the core goal of fully stabilizing the Signal-based features accumulated over the past two years and opening the door to a "Zone-free Angular."

This article is based on Angular 20 RC. The final API may differ slightly.

Zoneless Change Detection: From Developer Preview to Stable

Angular 19's Zoneless was a developer preview; Angular 20 promotes it to a stable API:

typescript
// Angular 19(开发者预览)
import { provideZonelessChangeDetection } from "@angular/core";

// Angular 20(正式稳定)
// API 不变,但标记为 stable,移除实验性警告
bootstrapApplication(AppComponent, {
  providers: [
    provideZonelessChangeDetection(), // ✅ 正式版,可用于生产
  ],
});

At the same time, zone.js is removed from the default polyfills for new projects:

json
// angular.json(Angular 20 新项目默认)
{
  "build": {
    "options": {
      "polyfills": [] // 不再包含 "zone.js"
    }
  }
}

Bundle size improvement: Removing zone.js saves ~33 KB (~13 KB gzipped), which makes a real difference on low-end devices and poor network connections.

Signal Forms Developer Preview

Signal-based Forms enter developer preview in Angular 20:

typescript
import { formGroup, formControl, Validators } from "@angular/forms";

@Component({
  standalone: true,
  imports: [SignalFormsModule], // 新模块
  template: `
    <form (ngSubmit)="submit()">
      <input [sfControl]="form.controls.name" placeholder="姓名" />
      @if (form.controls.name.hasError("required")()) {
        <span class="error">姓名必填</span>
      }

      <input
        type="email"
        [sfControl]="form.controls.email"
        placeholder="邮箱"
      />
      @if (form.controls.email.hasError("email")()) {
        <span class="error">邮箱格式不正确</span>
      }

      <button type="submit" [disabled]="!form.valid()">提交</button>
    </form>
  `,
})
export class SignupFormComponent {
  form = formGroup({
    name: formControl("", [Validators.required, Validators.minLength(2)]),
    email: formControl("", [Validators.required, Validators.email]),
    age: formControl<number | null>(null, [Validators.min(18)]),
  });

  isLoading = signal(false);

  // 直接在 computed 中使用表单状态
  canSubmit = computed(() => this.form.valid() && !this.isLoading());

  submit() {
    if (!this.canSubmit()) return;
    this.isLoading.set(true);

    // form.value() 返回 Signal 当前值
    console.log(this.form.value());
    // { name: 'xxx', email: 'xxx@xxx.com', age: 25 }
  }
}

New Router: resource() API Integration

Angular 20 introduces the resource() API (experimental), enabling declarative asynchronous resource management in components with deep router integration:

typescript
import { resource, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({ standalone: true, ... })
export class UserDetailComponent {
  private route = inject(ActivatedRoute);
  private api = inject(UserApiService);

  // resource():声明式异步资源
  userId = toSignal(this.route.paramMap.pipe(map(p => p.get('id')!)));

  userResource = resource({
    request: this.userId,  // 当 userId Signal 变化时自动重新加载
    loader: ({ request: id }) => this.api.getUser(id),
  });

  // 访问状态
  user = this.userResource.value;      // Signal<User | undefined>
  isLoading = this.userResource.isLoading;  // Signal<boolean>
  error = this.userResource.error;     // Signal<Error | undefined>
}

In the template:

html
@if (userResource.isLoading()) {
<loading-spinner />
} @else if (userResource.error()) {
<error-message [error]="userResource.error()!" />
} @else if (userResource.value(); as user) {
<user-profile [user]="user" />
}

OnPush Becomes the Default for New Projects

Components generated by the Angular 20 CLI now default to OnPush change detection (used in conjunction with Signals):

bash
ng generate component user-card
# 生成的组件默认包含:
# changeDetection: ChangeDetectionStrategy.OnPush
typescript
// Angular 20 CLI 生成的组件模板
@Component({
  selector: "app-user-card",
  standalone: true,
  imports: [],
  templateUrl: "./user-card.component.html",
  changeDetection: ChangeDetectionStrategy.OnPush, // 新默认值
})
export class UserCardComponent {}

Upgrading to Angular 20 RC

bash
ng update @angular/core@20-rc @angular/cli@20-rc

# 自动迁移:将 zone.js 相关配置更新
# 查看迁移列表
ng update @angular/core@20-rc --dry-run

Conclusion

Angular 20 is the most sweeping major release in recent years: stable Zoneless change detection means "Angular without zone.js" is truly production-ready; Signal Forms developer preview marks the beginning of a modernized forms system; and the resource() API fills the gap in declarative async data management. For Angular teams, this is the most important milestone release since the Signal transformation began in 2022.

MIT Licensed