[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fstate":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fstate":1506,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fstate":1528},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":14,"pinned":15,"draft":15,"body":16,"seo":1502,"stem":1503,"id":1504,"extension":1505},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fstate","状态管理","Angular 没有强制单一状态库：服务 + DI、Signal、RxJS 即可覆盖多数场景；大型应用可选用 NgRx。涵盖选型、服务作为状态容器、跨组件共享、NgRx 与 SignalStore、常见坑。","技能小册",[12,13],"angular","state","2025-04-29",false,{"type":17,"value":18,"toc":1490},"minimark",[19,22,52,56,59,93,100,103,178,181,605,616,711,736,739,761,888,892,912,1033,1166,1181,1185,1264,1267,1315,1318,1398,1401,1423,1426,1429,1486],[20,21,8],"h1",{"id":8},[23,24,25,26,30,31,30,34,37,38,41,42,30,47,51],"p",{},"Angular 没有强制单一状态库：",[27,28,29],"strong",{},"服务 + DI","、",[27,32,33],{},"Signal",[27,35,36],{},"RxJS"," 即可覆盖多数场景；大型应用可选用 ",[27,39,40],{},"NgRx","。见 ",[43,44,46],"a",{"href":45},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Fbasics","Signal 基础",[43,48,50],{"href":49},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fdi","依赖注入","。",[53,54,55],"h2",{"id":55},"核心概念",[23,57,58],{},"Angular 的「状态」通常分三个层次：",[60,61,62,74,84],"ol",{},[63,64,65,68,69,73],"li",{},[27,66,67],{},"组件内状态","：只影响单个组件的 UI 状态（展开\u002F收起、当前 tab），用 ",[70,71,72],"code",{},"signal"," 即可。",[63,75,76,79,80,83],{},[27,77,78],{},"跨组件共享状态","：多个不直接父子关系的组件需要同一份数据（购物车、当前用户），用 ",[27,81,82],{},"可注入服务 + Signal"," 承载，借助 Angular DI 的单例特性天然共享。",[63,85,86,89,90,92],{},[27,87,88],{},"全局\u002F复杂业务状态","：涉及多步骤流程、时间旅行调试、严格的状态变更审计，用 ",[27,91,40],{}," 等 Redux 风格方案。",[23,94,95,96,99],{},"选型的关键是",[27,97,98],{},"不要过度设计","：多数中台\u002F后台系统用第 2 层（服务 + Signal）就足够，只有状态流转复杂、团队规模大、需要强约束时才引入 NgRx。",[53,101,102],{"id":102},"选型简表",[104,105,106,119],"table",{},[107,108,109],"thead",{},[110,111,112,116],"tr",{},[113,114,115],"th",{},"场景",[113,117,118],{},"建议",[120,121,122,136,147,165],"tbody",{},[110,123,124,128],{},[125,126,127],"td",{},"组件内 UI 状态",[125,129,130,132,133],{},[70,131,72],{}," \u002F ",[70,134,135],{},"computed",[110,137,138,141],{},[125,139,140],{},"跨组件共享（中等复杂度）",[125,142,143,146],{},[70,144,145],{},"@Injectable({ providedIn: 'root' })"," 服务 + signal",[110,148,149,152],{},[125,150,151],{},"异步服务端数据",[125,153,154,157,158,132,161,164],{},[70,155,156],{},"HttpClient"," + ",[70,159,160],{},"toSignal",[70,162,163],{},"async"," 管道",[110,166,167,170],{},[125,168,169],{},"复杂事件溯源、时间旅行调试",[125,171,172],{},[43,173,177],{"href":174,"rel":175},"https:\u002F\u002Fngrx.io\u002Fdocs\u002Fstore",[176],"nofollow","NgRx Store",[53,179,180],{"id":180},"服务作为状态容器",[182,183,188],"pre",{"className":184,"code":185,"language":186,"meta":187,"style":187},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","@Injectable({ providedIn: \"root\" })\nexport class CartService {\n  private items = signal\u003CCartItem[]>([]);\n  readonly itemsReadonly = this.items.asReadonly();\n  readonly total = computed(() => this.items().reduce((s, i) => s + i.price, 0));\n\n  add(item: CartItem) {\n    this.items.update((list) => [...list, item]);\n  }\n\n  remove(id: string) {\n    this.items.update((list) => list.filter((i) => i.id !== id));\n  }\n\n  clear() {\n    this.items.set([]);\n  }\n}\n","ts","",[70,189,190,234,252,288,319,395,402,422,466,472,477,498,556,561,566,576,594,599],{"__ignoreMap":187},[191,192,195,199,203,207,210,214,217,221,225,228,231],"span",{"class":193,"line":194},"line",1,[191,196,198],{"class":197},"sP7_E","@",[191,200,202],{"class":201},"sGLFI","Injectable",[191,204,206],{"class":205},"su5hD","(",[191,208,209],{"class":197},"{",[191,211,213],{"class":212},"skxfh"," providedIn",[191,215,216],{"class":197},":",[191,218,220],{"class":219},"sjJ54"," \"",[191,222,224],{"class":223},"s_sjI","root",[191,226,227],{"class":219},"\"",[191,229,230],{"class":197}," }",[191,232,233],{"class":205},")\n",[191,235,237,241,245,249],{"class":193,"line":236},2,[191,238,240],{"class":239},"sVHd0","export",[191,242,244],{"class":243},"sbsja"," class",[191,246,248],{"class":247},"sbgvK"," CartService",[191,250,251],{"class":197}," {\n",[191,253,255,258,262,266,270,273,276,279,282,285],{"class":193,"line":254},3,[191,256,257],{"class":243},"  private",[191,259,261],{"class":260},"sucvu"," items",[191,263,265],{"class":264},"smGrS"," =",[191,267,269],{"class":268},"sVXei"," signal",[191,271,272],{"class":197},"\u003C",[191,274,275],{"class":247},"CartItem",[191,277,278],{"class":205},"[]",[191,280,281],{"class":197},">",[191,283,284],{"class":205},"([])",[191,286,287],{"class":197},";\n",[191,289,291,294,297,299,303,306,309,311,314,317],{"class":193,"line":290},4,[191,292,293],{"class":243},"  readonly",[191,295,296],{"class":260}," itemsReadonly",[191,298,265],{"class":264},[191,300,302],{"class":301},"s39Yj"," this",[191,304,305],{"class":197},".",[191,307,308],{"class":205},"items",[191,310,305],{"class":197},[191,312,313],{"class":268},"asReadonly",[191,315,316],{"class":205},"()",[191,318,287],{"class":197},[191,320,322,324,327,329,332,334,336,339,341,343,345,347,349,352,354,356,360,363,366,369,371,374,377,379,381,384,386,390,393],{"class":193,"line":321},5,[191,323,293],{"class":243},[191,325,326],{"class":260}," total",[191,328,265],{"class":264},[191,330,331],{"class":268}," computed",[191,333,206],{"class":205},[191,335,316],{"class":197},[191,337,338],{"class":243}," =>",[191,340,302],{"class":301},[191,342,305],{"class":197},[191,344,308],{"class":268},[191,346,316],{"class":205},[191,348,305],{"class":197},[191,350,351],{"class":268},"reduce",[191,353,206],{"class":205},[191,355,206],{"class":197},[191,357,359],{"class":358},"s99_P","s",[191,361,362],{"class":197},",",[191,364,365],{"class":358}," i",[191,367,368],{"class":197},")",[191,370,338],{"class":243},[191,372,373],{"class":205}," s ",[191,375,376],{"class":264},"+",[191,378,365],{"class":205},[191,380,305],{"class":197},[191,382,383],{"class":205},"price",[191,385,362],{"class":197},[191,387,389],{"class":388},"srdBf"," 0",[191,391,392],{"class":205},"))",[191,394,287],{"class":197},[191,396,398],{"class":193,"line":397},6,[191,399,401],{"emptyLinePlaceholder":400},true,"\n",[191,403,405,408,410,413,415,418,420],{"class":193,"line":404},7,[191,406,407],{"class":268},"  add",[191,409,206],{"class":197},[191,411,412],{"class":358},"item",[191,414,216],{"class":264},[191,416,417],{"class":247}," CartItem",[191,419,368],{"class":197},[191,421,251],{"class":197},[191,423,425,428,430,432,434,437,439,441,444,446,448,451,454,456,458,461,464],{"class":193,"line":424},8,[191,426,427],{"class":301},"    this",[191,429,305],{"class":197},[191,431,308],{"class":205},[191,433,305],{"class":197},[191,435,436],{"class":201},"update",[191,438,206],{"class":212},[191,440,206],{"class":197},[191,442,443],{"class":358},"list",[191,445,368],{"class":197},[191,447,338],{"class":243},[191,449,450],{"class":212}," [",[191,452,453],{"class":264},"...",[191,455,443],{"class":205},[191,457,362],{"class":197},[191,459,460],{"class":205}," item",[191,462,463],{"class":212},"])",[191,465,287],{"class":197},[191,467,469],{"class":193,"line":468},9,[191,470,471],{"class":197},"  }\n",[191,473,475],{"class":193,"line":474},10,[191,476,401],{"emptyLinePlaceholder":400},[191,478,480,483,485,488,490,494,496],{"class":193,"line":479},11,[191,481,482],{"class":268},"  remove",[191,484,206],{"class":197},[191,486,487],{"class":358},"id",[191,489,216],{"class":264},[191,491,493],{"class":492},"sZMiF"," string",[191,495,368],{"class":197},[191,497,251],{"class":197},[191,499,501,503,505,507,509,511,513,515,517,519,521,524,526,529,531,533,536,538,540,542,544,546,549,552,554],{"class":193,"line":500},12,[191,502,427],{"class":301},[191,504,305],{"class":197},[191,506,308],{"class":205},[191,508,305],{"class":197},[191,510,436],{"class":201},[191,512,206],{"class":212},[191,514,206],{"class":197},[191,516,443],{"class":358},[191,518,368],{"class":197},[191,520,338],{"class":243},[191,522,523],{"class":205}," list",[191,525,305],{"class":197},[191,527,528],{"class":201},"filter",[191,530,206],{"class":212},[191,532,206],{"class":197},[191,534,535],{"class":358},"i",[191,537,368],{"class":197},[191,539,338],{"class":243},[191,541,365],{"class":205},[191,543,305],{"class":197},[191,545,487],{"class":205},[191,547,548],{"class":264}," !==",[191,550,551],{"class":205}," id",[191,553,392],{"class":212},[191,555,287],{"class":197},[191,557,559],{"class":193,"line":558},13,[191,560,471],{"class":197},[191,562,564],{"class":193,"line":563},14,[191,565,401],{"emptyLinePlaceholder":400},[191,567,569,572,574],{"class":193,"line":568},15,[191,570,571],{"class":268},"  clear",[191,573,316],{"class":197},[191,575,251],{"class":197},[191,577,579,581,583,585,587,590,592],{"class":193,"line":578},16,[191,580,427],{"class":301},[191,582,305],{"class":197},[191,584,308],{"class":205},[191,586,305],{"class":197},[191,588,589],{"class":201},"set",[191,591,284],{"class":212},[191,593,287],{"class":197},[191,595,597],{"class":193,"line":596},17,[191,598,471],{"class":197},[191,600,602],{"class":193,"line":601},18,[191,603,604],{"class":197},"}\n",[23,606,607,608,611,612,615],{},"组件通过 ",[70,609,610],{},"inject(CartService)"," 读取，模板使用 ",[70,613,614],{},"cart.total()","：",[182,617,619],{"className":184,"code":618,"language":186,"meta":187,"style":187},"@Component({\n  standalone: true,\n  template: `\n    \u003Cp>共 {{ cart.itemsReadonly().length }} 件，合计 {{ cart.total() }}\u003C\u002Fp>\n    \u003Cbutton (click)=\"cart.clear()\">清空\u003C\u002Fbutton>\n  `,\n})\nexport class CartSummaryComponent {\n  cart = inject(CartService);\n}\n",[70,620,621,633,647,657,662,667,674,681,692,707],{"__ignoreMap":187},[191,622,623,625,628,630],{"class":193,"line":194},[191,624,198],{"class":197},[191,626,627],{"class":201},"Component",[191,629,206],{"class":205},[191,631,632],{"class":197},"{\n",[191,634,635,638,640,644],{"class":193,"line":236},[191,636,637],{"class":212},"  standalone",[191,639,216],{"class":197},[191,641,643],{"class":642},"syTEX"," true",[191,645,646],{"class":197},",\n",[191,648,649,652,654],{"class":193,"line":254},[191,650,651],{"class":212},"  template",[191,653,216],{"class":197},[191,655,656],{"class":219}," `\n",[191,658,659],{"class":193,"line":290},[191,660,661],{"class":223},"    \u003Cp>共 {{ cart.itemsReadonly().length }} 件，合计 {{ cart.total() }}\u003C\u002Fp>\n",[191,663,664],{"class":193,"line":321},[191,665,666],{"class":223},"    \u003Cbutton (click)=\"cart.clear()\">清空\u003C\u002Fbutton>\n",[191,668,669,672],{"class":193,"line":397},[191,670,671],{"class":219},"  `",[191,673,646],{"class":197},[191,675,676,679],{"class":193,"line":404},[191,677,678],{"class":197},"}",[191,680,233],{"class":205},[191,682,683,685,687,690],{"class":193,"line":424},[191,684,240],{"class":239},[191,686,244],{"class":243},[191,688,689],{"class":247}," CartSummaryComponent",[191,691,251],{"class":197},[191,693,694,697,699,702,705],{"class":193,"line":468},[191,695,696],{"class":260},"  cart",[191,698,265],{"class":264},[191,700,701],{"class":268}," inject",[191,703,704],{"class":205},"(CartService)",[191,706,287],{"class":197},[191,708,709],{"class":193,"line":474},[191,710,604],{"class":197},[23,712,713,716,717,720,721,724,725,727,728,731,732,735],{},[27,714,715],{},"要点","：内部可写状态用 ",[70,718,719],{},"private items = signal(...)","，只暴露 ",[70,722,723],{},"asReadonly()"," 或 ",[70,726,135],{},"，防止外部组件绕过 ",[70,729,730],{},"add","\u002F",[70,733,734],{},"remove"," 直接改数据，保持变更路径可追踪。",[53,737,738],{"id":738},"跨模块共享与生命周期",[23,740,741,744,745,748,749,752,753,756,757,760],{},[70,742,743],{},"providedIn: 'root'"," 的服务在整个应用中是",[27,746,747],{},"单例","，只要不特意在某个组件\u002F路由层重新 ",[70,750,751],{},"provide","，所有注入点拿到的是同一份实例——这是 Angular DI 替代「全局 store」的关键机制。若需要",[27,754,755],{},"每个路由\u002F懒加载模块各自一份状态","，则在该路由的 ",[70,758,759],{},"providers"," 数组里重新声明该 Service，形成子作用域实例。",[182,762,764],{"className":184,"code":763,"language":186,"meta":187,"style":187},"export const routes: Routes = [\n  {\n    path: \"checkout\",\n    providers: [CartService], \u002F\u002F 该路由子树内单独一份 CartService，退出后随路由销毁\n    loadComponent: () => import(\".\u002Fcheckout.component\").then((m) => m.CheckoutComponent),\n  },\n];\n",[70,765,766,787,792,808,824,876,881],{"__ignoreMap":187},[191,767,768,770,773,777,779,782,784],{"class":193,"line":194},[191,769,240],{"class":239},[191,771,772],{"class":243}," const",[191,774,776],{"class":775},"s_hVV"," routes",[191,778,216],{"class":264},[191,780,781],{"class":247}," Routes",[191,783,265],{"class":264},[191,785,786],{"class":205}," [\n",[191,788,789],{"class":193,"line":236},[191,790,791],{"class":197},"  {\n",[191,793,794,797,799,801,804,806],{"class":193,"line":254},[191,795,796],{"class":212},"    path",[191,798,216],{"class":197},[191,800,220],{"class":219},[191,802,803],{"class":223},"checkout",[191,805,227],{"class":219},[191,807,646],{"class":197},[191,809,810,813,815,818,820],{"class":193,"line":290},[191,811,812],{"class":212},"    providers",[191,814,216],{"class":197},[191,816,817],{"class":205}," [CartService]",[191,819,362],{"class":197},[191,821,823],{"class":822},"sutJx"," \u002F\u002F 该路由子树内单独一份 CartService，退出后随路由销毁\n",[191,825,826,829,831,834,836,839,841,843,846,848,850,852,855,857,859,862,864,866,869,871,874],{"class":193,"line":321},[191,827,828],{"class":201},"    loadComponent",[191,830,216],{"class":197},[191,832,833],{"class":197}," ()",[191,835,338],{"class":243},[191,837,838],{"class":264}," import",[191,840,206],{"class":205},[191,842,227],{"class":219},[191,844,845],{"class":223},".\u002Fcheckout.component",[191,847,227],{"class":219},[191,849,368],{"class":205},[191,851,305],{"class":197},[191,853,854],{"class":201},"then",[191,856,206],{"class":205},[191,858,206],{"class":197},[191,860,861],{"class":358},"m",[191,863,368],{"class":197},[191,865,338],{"class":243},[191,867,868],{"class":205}," m",[191,870,305],{"class":197},[191,872,873],{"class":205},"CheckoutComponent)",[191,875,646],{"class":197},[191,877,878],{"class":193,"line":397},[191,879,880],{"class":197},"  },\n",[191,882,883,886],{"class":193,"line":404},[191,884,885],{"class":205},"]",[191,887,287],{"class":197},[53,889,891],{"id":890},"ngrx可选","NgRx（可选）",[23,893,894,898,899,30,902,30,905,30,908,911],{},[43,895,40],{"href":896,"rel":897},"https:\u002F\u002Fngrx.io\u002F",[176]," 实现 Redux 风格：",[70,900,901],{},"Store",[70,903,904],{},"Actions",[70,906,907],{},"Reducers",[70,909,910],{},"Effects","（副作用）。适合多团队协作、强约束数据流；小项目可能过重。",[182,913,915],{"className":184,"code":914,"language":186,"meta":187,"style":187},"\u002F\u002F 简化示意：Action、Reducer、Selector\nexport const increment = createAction(\"[Counter] Increment\");\nexport const counterReducer = createReducer(0, on(increment, (state) => state + 1));\nexport const selectCount = createFeatureSelector\u003Cnumber>(\"count\");\n",[70,916,917,922,949,999],{"__ignoreMap":187},[191,918,919],{"class":193,"line":194},[191,920,921],{"class":822},"\u002F\u002F 简化示意：Action、Reducer、Selector\n",[191,923,924,926,928,931,933,936,938,940,943,945,947],{"class":193,"line":236},[191,925,240],{"class":239},[191,927,772],{"class":243},[191,929,930],{"class":775}," increment",[191,932,265],{"class":264},[191,934,935],{"class":201}," createAction",[191,937,206],{"class":205},[191,939,227],{"class":219},[191,941,942],{"class":223},"[Counter] Increment",[191,944,227],{"class":219},[191,946,368],{"class":205},[191,948,287],{"class":197},[191,950,951,953,955,958,960,963,965,968,970,973,976,978,981,983,985,987,990,992,995,997],{"class":193,"line":254},[191,952,240],{"class":239},[191,954,772],{"class":243},[191,956,957],{"class":775}," counterReducer",[191,959,265],{"class":264},[191,961,962],{"class":201}," createReducer",[191,964,206],{"class":205},[191,966,967],{"class":388},"0",[191,969,362],{"class":197},[191,971,972],{"class":201}," on",[191,974,975],{"class":205},"(increment",[191,977,362],{"class":197},[191,979,980],{"class":197}," (",[191,982,13],{"class":358},[191,984,368],{"class":197},[191,986,338],{"class":243},[191,988,989],{"class":205}," state ",[191,991,376],{"class":264},[191,993,994],{"class":388}," 1",[191,996,392],{"class":205},[191,998,287],{"class":197},[191,1000,1001,1003,1005,1008,1010,1013,1015,1018,1020,1022,1024,1027,1029,1031],{"class":193,"line":290},[191,1002,240],{"class":239},[191,1004,772],{"class":243},[191,1006,1007],{"class":775}," selectCount",[191,1009,265],{"class":264},[191,1011,1012],{"class":201}," createFeatureSelector",[191,1014,272],{"class":197},[191,1016,1017],{"class":492},"number",[191,1019,281],{"class":197},[191,1021,206],{"class":205},[191,1023,227],{"class":219},[191,1025,1026],{"class":223},"count",[191,1028,227],{"class":219},[191,1030,368],{"class":205},[191,1032,287],{"class":197},[182,1034,1036],{"className":184,"code":1035,"language":186,"meta":187,"style":187},"@Component({\n  template: `\u003Cp>{{ count() }}\u003C\u002Fp>\u003Cbutton (click)=\"inc()\">+1\u003C\u002Fbutton>`,\n})\nexport class CounterComponent {\n  private store = inject(Store);\n  count = this.store.selectSignal(selectCount); \u002F\u002F NgRx 提供 Signal 化 selector\n  inc() {\n    this.store.dispatch(increment());\n  }\n}\n",[70,1037,1038,1048,1065,1071,1082,1098,1126,1135,1158,1162],{"__ignoreMap":187},[191,1039,1040,1042,1044,1046],{"class":193,"line":194},[191,1041,198],{"class":197},[191,1043,627],{"class":201},[191,1045,206],{"class":205},[191,1047,632],{"class":197},[191,1049,1050,1052,1054,1057,1060,1063],{"class":193,"line":236},[191,1051,651],{"class":212},[191,1053,216],{"class":197},[191,1055,1056],{"class":219}," `",[191,1058,1059],{"class":223},"\u003Cp>{{ count() }}\u003C\u002Fp>\u003Cbutton (click)=\"inc()\">+1\u003C\u002Fbutton>",[191,1061,1062],{"class":219},"`",[191,1064,646],{"class":197},[191,1066,1067,1069],{"class":193,"line":254},[191,1068,678],{"class":197},[191,1070,233],{"class":205},[191,1072,1073,1075,1077,1080],{"class":193,"line":290},[191,1074,240],{"class":239},[191,1076,244],{"class":243},[191,1078,1079],{"class":247}," CounterComponent",[191,1081,251],{"class":197},[191,1083,1084,1086,1089,1091,1093,1096],{"class":193,"line":321},[191,1085,257],{"class":243},[191,1087,1088],{"class":260}," store",[191,1090,265],{"class":264},[191,1092,701],{"class":268},[191,1094,1095],{"class":205},"(Store)",[191,1097,287],{"class":197},[191,1099,1100,1103,1105,1107,1109,1112,1114,1117,1120,1123],{"class":193,"line":397},[191,1101,1102],{"class":260},"  count",[191,1104,265],{"class":264},[191,1106,302],{"class":301},[191,1108,305],{"class":197},[191,1110,1111],{"class":205},"store",[191,1113,305],{"class":197},[191,1115,1116],{"class":268},"selectSignal",[191,1118,1119],{"class":205},"(selectCount)",[191,1121,1122],{"class":197},";",[191,1124,1125],{"class":822}," \u002F\u002F NgRx 提供 Signal 化 selector\n",[191,1127,1128,1131,1133],{"class":193,"line":404},[191,1129,1130],{"class":268},"  inc",[191,1132,316],{"class":197},[191,1134,251],{"class":197},[191,1136,1137,1139,1141,1143,1145,1148,1150,1153,1156],{"class":193,"line":424},[191,1138,427],{"class":301},[191,1140,305],{"class":197},[191,1142,1111],{"class":205},[191,1144,305],{"class":197},[191,1146,1147],{"class":201},"dispatch",[191,1149,206],{"class":212},[191,1151,1152],{"class":201},"increment",[191,1154,1155],{"class":212},"())",[191,1157,287],{"class":197},[191,1159,1160],{"class":193,"line":468},[191,1161,471],{"class":197},[191,1163,1164],{"class":193,"line":474},[191,1165,604],{"class":197},[23,1167,1168,1169,1176,1177,1180],{},"官方也提供 ",[27,1170,1171],{},[43,1172,1175],{"href":1173,"rel":1174},"https:\u002F\u002Fngrx.io\u002Fdocs\u002Fsignals",[176],"NgRx SignalStore","，与 Signal API 更贴近，比传统 ",[70,1178,1179],{},"Store + Reducer + Effects"," 写法更简洁，适合不需要严格 Action\u002FReducer 分层但仍想要结构化状态管理的团队。",[53,1182,1184],{"id":1183},"与-vue-pinia-react-redux-对照","与 Vue Pinia \u002F React Redux 对照",[104,1186,1187,1202],{},[107,1188,1189],{},[110,1190,1191,1193,1196,1199],{},[113,1192],{},[113,1194,1195],{},"Angular",[113,1197,1198],{},"Vue",[113,1200,1201],{},"React",[120,1203,1204,1218,1231,1250],{},[110,1205,1206,1209,1212,1215],{},[125,1207,1208],{},"轻量方案",[125,1210,1211],{},"Service + signal",[125,1213,1214],{},"Pinia",[125,1216,1217],{},"Zustand \u002F Context",[110,1219,1220,1223,1225,1228],{},[125,1221,1222],{},"重量方案",[125,1224,40],{},[125,1226,1227],{},"Vuex（旧）",[125,1229,1230],{},"Redux Toolkit",[110,1232,1233,1236,1240,1244],{},[125,1234,1235],{},"派生状态",[125,1237,1238],{},[70,1239,135],{},[125,1241,1242],{},[70,1243,135],{},[125,1245,1246,1249],{},[70,1247,1248],{},"useMemo"," \u002F selector",[110,1251,1252,1255,1258,1261],{},[125,1253,1254],{},"DevTools",[125,1256,1257],{},"NgRx Store DevTools",[125,1259,1260],{},"Pinia DevTools",[125,1262,1263],{},"Redux DevTools",[53,1265,1266],{"id":1266},"最佳实践",[1268,1269,1270,1277,1292,1302,1309],"ul",{},[63,1271,1272,1273,1276],{},"优先用",[27,1274,1275],{},"服务 + Signal","，只有确认业务复杂度需要（多步骤流程、跨团队协作、严格审计）才引入 NgRx。",[63,1278,1279,1280,720,1283,1286,1287,30,1289,1291],{},"Service 内部状态尽量",[27,1281,1282],{},"私有",[70,1284,1285],{},"readonly"," 的 signal\u002Fcomputed，写操作封装成有业务含义的方法（",[70,1288,730],{},[70,1290,803],{},"）。",[63,1293,1294,1295,1297,1298,1301],{},"服务端数据（HTTP 响应）用 ",[70,1296,160],{}," 转换为 Signal，避免手动 ",[70,1299,1300],{},"subscribe"," 后再赋值造成的内存泄漏风险。",[63,1303,1304,1305,1308],{},"若引入 NgRx，先从 ",[27,1306,1307],{},"SignalStore"," 或局部 feature store 开始，不必一次性把全应用状态搬进全局 Store。",[63,1310,1311,1312,1314],{},"路由级临时状态（如某个多步表单向导）用路由 ",[70,1313,759],{}," 声明子作用域服务，随路由销毁自动清理。",[53,1316,1317],{"id":1317},"常见坑",[104,1319,1320,1333],{},[107,1321,1322],{},[110,1323,1324,1327,1330],{},[113,1325,1326],{},"现象",[113,1328,1329],{},"常见原因",[113,1331,1332],{},"处理",[120,1334,1335,1352,1367,1378],{},[110,1336,1337,1340,1346],{},[125,1338,1339],{},"服务里的数据「跨路由丢失」",[125,1341,1342,1343,1345],{},"服务被声明在懒加载模块 ",[70,1344,759],{}," 里形成了新实例",[125,1347,1348,1349,1351],{},"确认服务应是 ",[70,1350,224],{}," 单例还是路由子作用域，按需选择",[110,1353,1354,1357,1364],{},[125,1355,1356],{},"组件外部直接改了服务内部数组",[125,1358,1359,1360,731,1362],{},"暴露了可写 signal 而非 ",[70,1361,723],{},[70,1363,135],{},[125,1365,1366],{},"只导出只读引用，写操作走方法",[110,1368,1369,1372,1375],{},[125,1370,1371],{},"NgRx 引入后代码量剧增、维护成本高",[125,1373,1374],{},"业务并不复杂却套用完整 Redux 分层",[125,1376,1377],{},"评估是否降级为 Service + Signal 或 SignalStore",[110,1379,1380,1385,1395],{},[125,1381,1382,1384],{},[70,1383,135],{}," 没有更新",[125,1386,1387,1388,1390,1391,1394],{},"依赖的 signal 未被在 ",[70,1389,135],{}," 函数体内",[27,1392,1393],{},"实际读取","（如被条件短路跳过）",[125,1396,1397],{},"确保依赖的 signal 在每次求值路径中都被调用",[53,1399,1400],{"id":1400},"延伸阅读",[1268,1402,1403,1408,1416],{},[63,1404,1405,1407],{},[43,1406,46],{"href":45},"：signal \u002F computed \u002F effect API",[63,1409,1410,615,1412,1415],{},[43,1411,50],{"href":49},[70,1413,1414],{},"providedIn"," 与作用域",[63,1417,1418,1422],{},[43,1419,1421],{"href":1420},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Frxjs","RxJS 互操作","：Observable 与 Signal 转换",[53,1424,1425],{"id":1425},"参考文献",[23,1427,1428],{},"以下链接在编写时均可正常访问：",[104,1430,1431,1441],{},[107,1432,1433],{},[110,1434,1435,1438],{},[113,1436,1437],{},"资料",[113,1439,1440],{},"说明",[120,1442,1443,1455,1465,1475],{},[110,1444,1445,1452],{},[125,1446,1447],{},[43,1448,1451],{"href":1449,"rel":1450},"https:\u002F\u002Fangular.dev\u002Fguide\u002Fsignals#using-signals-with-services",[176],"状态管理模式",[125,1453,1454],{},"官方 Signal + 服务",[110,1456,1457,1462],{},[125,1458,1459],{},[43,1460,177],{"href":174,"rel":1461},[176],[125,1463,1464],{},"英文",[110,1466,1467,1472],{},[125,1468,1469],{},[43,1470,1175],{"href":1173,"rel":1471},[176],[125,1473,1474],{},"Signal 风格",[110,1476,1477,1483],{},[125,1478,1479],{},[43,1480,50],{"href":1481,"rel":1482},"https:\u002F\u002Fangular.dev\u002Fguide\u002Fdi",[176],[125,1484,1485],{},"providedIn 作用域",[1487,1488,1489],"style",{},"html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVXei, html code.shiki .sVXei{--shiki-light:#E53935;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}",{"title":187,"searchDepth":236,"depth":236,"links":1491},[1492,1493,1494,1495,1496,1497,1498,1499,1500,1501],{"id":55,"depth":236,"text":55},{"id":102,"depth":236,"text":102},{"id":180,"depth":236,"text":180},{"id":738,"depth":236,"text":738},{"id":890,"depth":236,"text":891},{"id":1183,"depth":236,"text":1184},{"id":1266,"depth":236,"text":1266},{"id":1317,"depth":236,"text":1317},{"id":1400,"depth":236,"text":1400},{"id":1425,"depth":236,"text":1425},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Fstate","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Fstate.md","md",{"items":1507},[1508,1512,1514,1516,1520,1524],{"path":1509,"title":1510,"description":1511,"category":10,"score":397},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fecology","生态集成","Angular 官方维护路由、HTTP、表单、动画等；UI 与状态扩展由社区与官方 Material 团队提供。涵盖官方\u002F半官方库、常用第三方库、与 Vue\u002FReact 生态对照、选型建议与常见坑。",{"path":45,"title":46,"description":1513,"category":10,"score":397},"Signals 是 Angular 16 引入的响应式原语：可读写的 signal、派生的 computed、副作用 effect。用于组件内状态与跨组件细粒度更新，涵盖 API、input signal、与变更检测的关系、最佳实践与常见坑。",{"path":1420,"title":1421,"description":1515,"category":10,"score":397},"Angular 内置 RxJS 处理异步流（HTTP、HttpClient、路由事件、FormControl 的 valueChanges 等）。Signal 适合同步\u002F短链路状态；长生命周期流仍常用 Observable。涵盖 async 管道、toSignal\u002FtoObservable、常用操作符、最佳实践与常见坑。",{"path":1517,"title":1518,"description":1519,"category":10,"score":397},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fforms","表单","Angular 提供 响应式表单（Reactive Forms） 与 模板驱动表单（Template-driven）。复杂表单推荐 Reactive Forms（模型在 TypeScript 中，易测试）。涵盖 FormGroup\u002FFormArray、Typed Forms、自定义校验器、最佳实践与常见坑。",{"path":1521,"title":1522,"description":1523,"category":10,"score":397},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fhttp","HTTP 与数据","Angular 通过 HttpClient（@angular\u002Fcommon\u002Fhttp）发起 HTTP 请求，返回 RxJS Observable。见 RxJS 互操作、依赖注入。",{"path":1525,"title":1526,"description":1527,"category":10,"score":397},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fchange-detection","变更检测","Angular 在数据变化后更新 DOM 的过程称为 变更检测（Change Detection）。理解默认策略、Zone.js 机制、OnPush 与 Signal 协同，有助于性能调优。运行时技巧见 runtime。",{"series":1529,"title":12,"items":1530,"index":500,"prev":1570,"next":1571},"booklet\u002Fframe\u002Fangular",[1531,1535,1537,1539,1543,1545,1547,1549,1551,1555,1559,1563,1565,1566],{"path":1532,"title":1533,"date":1534,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fbasics","Angular 基础","2020-03-07",{"path":45,"title":46,"date":1536,"pinned":15},"2026-06-12",{"path":1525,"title":1526,"date":1538,"pinned":15},"2023-10-12",{"path":1540,"title":1541,"date":1542,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fcomponents","组件与模板","2021-07-11",{"path":49,"title":50,"date":1544,"pinned":15},"2021-01-24",{"path":1509,"title":1510,"date":1546,"pinned":15},"2026-07-12",{"path":1517,"title":1518,"date":1548,"pinned":15},"2025-05-26",{"path":1521,"title":1522,"date":1550,"pinned":15},"2024-12-27",{"path":1552,"title":1553,"date":1554,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fproject","工程化","2021-09-01",{"path":1556,"title":1557,"date":1558,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Frouter","路由","2023-02-04",{"path":1560,"title":1561,"date":1562,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime","运行时优化","2022-11-01",{"path":1420,"title":1421,"date":1564,"pinned":15},"2026-04-06",{"path":7,"title":8,"date":14,"pinned":15},{"path":1567,"title":1568,"date":1569,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Ftest","测试","2021-08-25",{"path":1420,"title":1421,"date":1564,"pinned":15},{"path":1567,"title":1568,"date":1569,"pinned":15}]