
signal/computed/effect、input/output/model 等能力standalone: true,直接在 imports 中引用;应用入口使用 bootstrapApplicationmain.ts:
import { bootstrapApplication } from '@angular/platform-browser'
import { provideRouter } from '@angular/router'
import { AppComponent } from './app/app.component'
import { routes } from './app/app.routes'
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)]
})app.component.ts:
import { Component } from '@angular/core'
import { RouterLink } from '@angular/router'
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterLink],
template: `
<h1>Angular 17 Standalone + Signals</h1>
<a routerLink="/counter">Counter</a>
`
})
export class AppComponent {}app.routes.ts:
import { Routes } from '@angular/router'
import { CounterComponent } from './counter.component'
export const routes: Routes = [
{ path: 'counter', loadComponent: () => import('./counter.component').then(m => m.CounterComponent) },
{ path: '', pathMatch: 'full', redirectTo: 'counter' }
]loadComponent 懒加载组件;无需 NgModule 汇总providers 注入;在任意位置使用 inject() 获取服务counter.service.ts:
import { Injectable, signal, computed } from '@angular/core'
@Injectable({ providedIn: 'root' })
export class CounterService {
readonly count = signal(0)
readonly double = computed(() => this.count() * 2)
inc() { this.count.update(n => n + 1) }
reset() { this.count.set(0) }
}counter.component.ts:
import { Component, effect, inject } from '@angular/core'
import { CounterService } from './counter.service'
@Component({
standalone: true,
selector: 'app-counter',
template: `
<button (click)="svc.inc()">Count: {{ svc.count() }}</button>
<p>Double: {{ svc.double() }}</p>
`
})
export class CounterComponent {
svc = inject(CounterService)
constructor() {
effect(() => {
console.log('count changed:', this.svc.count())
})
}
}signal<T>(initial):可读/可写状态源,读取用调用 signal(),更新用 set/updatecomputed(fn):派生信号,依赖源变化时重新计算effect(fn):副作用,追踪依赖并在变化时执行,支持清理逻辑信号 API:
import { signal, computed, effect } from '@angular/core'
const count = signal(0)
const double = computed(() => count() * 2)
const stop = effect(onCleanup => {
// 使用依赖
double()
// 清理
onCleanup(() => console.log('cleanup'))
})
count.set(1)
stop()input():声明输入为信号,自动追踪父组件传值变化output():事件输出为信号的写端,保持类型安全model():双向绑定的模型信号,对应 [(model)] 或 [(value)]child.component.ts:
import { Component, input, output, model } from '@angular/core'
@Component({
selector: 'app-child',
standalone: true,
template: `
<h3>{{ title() }}</h3>
<button (click)="changed.emit(count())">Emit</button>
<button (click)="count.update(n => n + 1)">+1</button>
`
})
export class ChildComponent {
readonly title = input<string>('默认标题')
readonly changed = output<number>()
readonly count = model<number>(0)
}父组件:
@Component({ standalone: true, template: `
<app-child [title]="name" [(count)]="value" (changed)="onChanged($event)"></app-child>
` })
export class ParentComponent {
name = 'Demo'
value = 0
onChanged(v:number){ console.log(v) }
}@angular/core/rxjs-interop 中提供 toSignal/toObservableimport { toSignal, toObservable } from '@angular/core/rxjs-interop'
import { interval, map } from 'rxjs'
const tick$ = interval(1000).pipe(map(n => n))
const tick = toSignal(tick$, { initialValue: 0 })
const tick$2 = toObservable(signal(42))provideRouter + loadComponent,组件直接懒加载canActivate、canMatch 守卫函数,返回布尔或可观察对象export const routes: Routes = [
{ path: 'dashboard', loadComponent: () => import('./dashboard.component').then(m => m.DashboardComponent), canActivate: [() => true] }
]{{ signal() }}model() 配合表单控件的 [(ngModel)] 或 Reactive Forms 的 valueChanges 互转provideExperimentalZoneless),减少全局变更检测开销bootstrapApplication 与 provideRouter,再将核心页面改为 standalonesignal 与 computed,结合 effect 做副作用input/output/model 信号统一输入输出协议,替换 @Input/@Output 旧写法toSignalcount() 而非 countstop@Input() 与 input():建议统一使用信号版,减少同步问题standalone: true:会导致路由加载失败TestBed.configureTestingModule({ imports: [Component] }) 挂载独立组件set/update 并断言模板渲染或副作用触发bootstrapApplication、standalone、signal/computed/effect 与 input/output/model,可构建更现代的前端架构signal/computed/effect、input/output/model 等能力standalone: true,直接在 imports 中引用;应用入口使用 bootstrapApplicationmain.ts:
import { bootstrapApplication } from '@angular/platform-browser'
import { provideRouter } from '@angular/router'
import { AppComponent } from './app/app.component'
import { routes } from './app/app.routes'
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)]
})app.component.ts:
import { Component } from '@angular/core'
import { RouterLink } from '@angular/router'
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterLink],
template: `
<h1>Angular 17 Standalone + Signals</h1>
<a routerLink="/counter">Counter</a>
`
})
export class AppComponent {}app.routes.ts:
import { Routes } from '@angular/router'
import { CounterComponent } from './counter.component'
export const routes: Routes = [
{ path: 'counter', loadComponent: () => import('./counter.component').then(m => m.CounterComponent) },
{ path: '', pathMatch: 'full', redirectTo: 'counter' }
]loadComponent 懒加载组件;无需 NgModule 汇总providers 注入;在任意位置使用 inject() 获取服务counter.service.ts:
import { Injectable, signal, computed } from '@angular/core'
@Injectable({ providedIn: 'root' })
export class CounterService {
readonly count = signal(0)
readonly double = computed(() => this.count() * 2)
inc() { this.count.update(n => n + 1) }
reset() { this.count.set(0) }
}counter.component.ts:
import { Component, effect, inject } from '@angular/core'
import { CounterService } from './counter.service'
@Component({
standalone: true,
selector: 'app-counter',
template: `
<button (click)="svc.inc()">Count: {{ svc.count() }}</button>
<p>Double: {{ svc.double() }}</p>
`
})
export class CounterComponent {
svc = inject(CounterService)
constructor() {
effect(() => {
console.log('count changed:', this.svc.count())
})
}
}signal<T>(initial):可读/可写状态源,读取用调用 signal(),更新用 set/updatecomputed(fn):派生信号,依赖源变化时重新计算effect(fn):副作用,追踪依赖并在变化时执行,支持清理逻辑信号 API:
import { signal, computed, effect } from '@angular/core'
const count = signal(0)
const double = computed(() => count() * 2)
const stop = effect(onCleanup => {
// 使用依赖
double()
// 清理
onCleanup(() => console.log('cleanup'))
})
count.set(1)
stop()input():声明输入为信号,自动追踪父组件传值变化output():事件输出为信号的写端,保持类型安全model():双向绑定的模型信号,对应 [(model)] 或 [(value)]child.component.ts:
import { Component, input, output, model } from '@angular/core'
@Component({
selector: 'app-child',
standalone: true,
template: `
<h3>{{ title() }}</h3>
<button (click)="changed.emit(count())">Emit</button>
<button (click)="count.update(n => n + 1)">+1</button>
`
})
export class ChildComponent {
readonly title = input<string>('默认标题')
readonly changed = output<number>()
readonly count = model<number>(0)
}父组件:
@Component({ standalone: true, template: `
<app-child [title]="name" [(count)]="value" (changed)="onChanged($event)"></app-child>
` })
export class ParentComponent {
name = 'Demo'
value = 0
onChanged(v:number){ console.log(v) }
}@angular/core/rxjs-interop 中提供 toSignal/toObservableimport { toSignal, toObservable } from '@angular/core/rxjs-interop'
import { interval, map } from 'rxjs'
const tick$ = interval(1000).pipe(map(n => n))
const tick = toSignal(tick$, { initialValue: 0 })
const tick$2 = toObservable(signal(42))provideRouter + loadComponent,组件直接懒加载canActivate、canMatch 守卫函数,返回布尔或可观察对象export const routes: Routes = [
{ path: 'dashboard', loadComponent: () => import('./dashboard.component').then(m => m.DashboardComponent), canActivate: [() => true] }
]{{ signal() }}model() 配合表单控件的 [(ngModel)] 或 Reactive Forms 的 valueChanges 互转provideExperimentalZoneless),减少全局变更检测开销bootstrapApplication 与 provideRouter,再将核心页面改为 standalonesignal 与 computed,结合 effect 做副作用input/output/model 信号统一输入输出协议,替换 @Input/@Output 旧写法toSignalcount() 而非 countstop@Input() 与 input():建议统一使用信号版,减少同步问题standalone: true:会导致路由加载失败TestBed.configureTestingModule({ imports: [Component] }) 挂载独立组件set/update 并断言模板渲染或副作用触发bootstrapApplication、standalone、signal/computed/effect 与 input/output/model,可构建更现代的前端架构