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