[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Ftest":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Ftest":1949,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Ftest":1971},{"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":1945,"stem":1946,"id":1947,"extension":1948},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Ftest","测试","Angular CLI 默认使用 Karma + Jasmine 运行单元测试；也可按团队规范迁移 Jest 或 Vitest（需自行配置，非 CLI 默认）。涵盖 TestBed、模拟依赖、异步测试、端到端测试、最佳实践与常见坑。","技能小册",[12,13],"angular","test","2021-08-25",false,{"type":17,"value":18,"toc":1932},"minimark",[19,22,39,43,62,66,461,484,487,498,765,768,884,899,1170,1173,1428,1439,1442,1464,1496,1659,1662,1688,1691,1716,1719,1828,1831,1854,1857,1860,1928],[20,21,8],"h1",{"id":8},[23,24,25,26,30,31,34,35,38],"p",{},"Angular CLI 默认使用 ",[27,28,29],"strong",{},"Karma + Jasmine"," 运行单元测试；也可按团队规范迁移 ",[27,32,33],{},"Jest"," 或 ",[27,36,37],{},"Vitest","（需自行配置，非 CLI 默认）。",[40,41,42],"h2",{"id":42},"核心概念",[23,44,45,46,52,53,58,59,61],{},"Angular 的测试体系围绕 ",[27,47,48],{},[49,50,51],"code",{},"TestBed"," 展开：它会创建一个隔离的、临时的「测试用 Angular 模块」，把待测组件、依赖的服务\u002F管道\u002F指令都注册进去，然后生成一个 ",[27,54,55],{},[49,56,57],{},"ComponentFixture","（组件的测试宿主），提供访问组件实例、DOM 节点、触发变更检测的能力。理解 ",[49,60,51],{}," 的生命周期是写好 Angular 单测的基础。",[40,63,65],{"id":64},"默认单元测试testbed","默认单元测试（TestBed）",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","import { TestBed } from \"@angular\u002Fcore\u002Ftesting\";\nimport { CounterComponent } from \".\u002Fcounter.component\";\n\ndescribe(\"CounterComponent\", () => {\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      imports: [CounterComponent],\n    }).compileComponents();\n  });\n\n  it(\"点击后计数增加\", () => {\n    const fixture = TestBed.createComponent(CounterComponent);\n    fixture.detectChanges();\n    const button: HTMLButtonElement = fixture.nativeElement.querySelector(\"button\")!;\n    button.click();\n    fixture.detectChanges();\n    expect(fixture.nativeElement.textContent).toContain(\"1\");\n  });\n});\n","ts","",[49,74,75,112,135,142,172,190,209,229,248,258,263,286,315,330,374,389,402,442,451],{"__ignoreMap":72},[76,77,80,84,88,92,95,98,102,106,109],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"sVHd0","import",[76,85,87],{"class":86},"sP7_E"," {",[76,89,91],{"class":90},"su5hD"," TestBed",[76,93,94],{"class":86}," }",[76,96,97],{"class":82}," from",[76,99,101],{"class":100},"sjJ54"," \"",[76,103,105],{"class":104},"s_sjI","@angular\u002Fcore\u002Ftesting",[76,107,108],{"class":100},"\"",[76,110,111],{"class":86},";\n",[76,113,115,117,119,122,124,126,128,131,133],{"class":78,"line":114},2,[76,116,83],{"class":82},[76,118,87],{"class":86},[76,120,121],{"class":90}," CounterComponent",[76,123,94],{"class":86},[76,125,97],{"class":82},[76,127,101],{"class":100},[76,129,130],{"class":104},".\u002Fcounter.component",[76,132,108],{"class":100},[76,134,111],{"class":86},[76,136,138],{"class":78,"line":137},3,[76,139,141],{"emptyLinePlaceholder":140},true,"\n",[76,143,145,149,152,154,157,159,162,165,169],{"class":78,"line":144},4,[76,146,148],{"class":147},"sGLFI","describe",[76,150,151],{"class":90},"(",[76,153,108],{"class":100},[76,155,156],{"class":104},"CounterComponent",[76,158,108],{"class":100},[76,160,161],{"class":86},",",[76,163,164],{"class":86}," ()",[76,166,168],{"class":167},"sbsja"," =>",[76,170,171],{"class":86}," {\n",[76,173,175,178,181,184,186,188],{"class":78,"line":174},5,[76,176,177],{"class":147},"  beforeEach",[76,179,151],{"class":180},"skxfh",[76,182,183],{"class":167},"async",[76,185,164],{"class":86},[76,187,168],{"class":167},[76,189,171],{"class":86},[76,191,193,196,198,201,204,206],{"class":78,"line":192},6,[76,194,195],{"class":82},"    await",[76,197,91],{"class":90},[76,199,200],{"class":86},".",[76,202,203],{"class":147},"configureTestingModule",[76,205,151],{"class":180},[76,207,208],{"class":86},"{\n",[76,210,212,215,218,221,223,226],{"class":78,"line":211},7,[76,213,214],{"class":180},"      imports",[76,216,217],{"class":86},":",[76,219,220],{"class":180}," [",[76,222,156],{"class":90},[76,224,225],{"class":180},"]",[76,227,228],{"class":86},",\n",[76,230,232,235,238,240,243,246],{"class":78,"line":231},8,[76,233,234],{"class":86},"    }",[76,236,237],{"class":180},")",[76,239,200],{"class":86},[76,241,242],{"class":147},"compileComponents",[76,244,245],{"class":180},"()",[76,247,111],{"class":86},[76,249,251,254,256],{"class":78,"line":250},9,[76,252,253],{"class":86},"  }",[76,255,237],{"class":180},[76,257,111],{"class":86},[76,259,261],{"class":78,"line":260},10,[76,262,141],{"emptyLinePlaceholder":140},[76,264,266,269,271,273,276,278,280,282,284],{"class":78,"line":265},11,[76,267,268],{"class":147},"  it",[76,270,151],{"class":180},[76,272,108],{"class":100},[76,274,275],{"class":104},"点击后计数增加",[76,277,108],{"class":100},[76,279,161],{"class":86},[76,281,164],{"class":86},[76,283,168],{"class":167},[76,285,171],{"class":86},[76,287,289,292,296,300,302,304,307,309,311,313],{"class":78,"line":288},12,[76,290,291],{"class":167},"    const",[76,293,295],{"class":294},"s_hVV"," fixture",[76,297,299],{"class":298},"smGrS"," =",[76,301,91],{"class":90},[76,303,200],{"class":86},[76,305,306],{"class":147},"createComponent",[76,308,151],{"class":180},[76,310,156],{"class":90},[76,312,237],{"class":180},[76,314,111],{"class":86},[76,316,318,321,323,326,328],{"class":78,"line":317},13,[76,319,320],{"class":90},"    fixture",[76,322,200],{"class":86},[76,324,325],{"class":147},"detectChanges",[76,327,245],{"class":180},[76,329,111],{"class":86},[76,331,333,335,338,340,344,346,348,350,353,355,358,360,362,365,367,369,372],{"class":78,"line":332},14,[76,334,291],{"class":167},[76,336,337],{"class":294}," button",[76,339,217],{"class":298},[76,341,343],{"class":342},"sbgvK"," HTMLButtonElement",[76,345,299],{"class":298},[76,347,295],{"class":90},[76,349,200],{"class":86},[76,351,352],{"class":90},"nativeElement",[76,354,200],{"class":86},[76,356,357],{"class":147},"querySelector",[76,359,151],{"class":180},[76,361,108],{"class":100},[76,363,364],{"class":104},"button",[76,366,108],{"class":100},[76,368,237],{"class":180},[76,370,371],{"class":298},"!",[76,373,111],{"class":86},[76,375,377,380,382,385,387],{"class":78,"line":376},15,[76,378,379],{"class":90},"    button",[76,381,200],{"class":86},[76,383,384],{"class":147},"click",[76,386,245],{"class":180},[76,388,111],{"class":86},[76,390,392,394,396,398,400],{"class":78,"line":391},16,[76,393,320],{"class":90},[76,395,200],{"class":86},[76,397,325],{"class":147},[76,399,245],{"class":180},[76,401,111],{"class":86},[76,403,405,408,410,413,415,417,419,422,424,426,429,431,433,436,438,440],{"class":78,"line":404},17,[76,406,407],{"class":147},"    expect",[76,409,151],{"class":180},[76,411,412],{"class":90},"fixture",[76,414,200],{"class":86},[76,416,352],{"class":90},[76,418,200],{"class":86},[76,420,421],{"class":90},"textContent",[76,423,237],{"class":180},[76,425,200],{"class":86},[76,427,428],{"class":147},"toContain",[76,430,151],{"class":180},[76,432,108],{"class":100},[76,434,435],{"class":104},"1",[76,437,108],{"class":100},[76,439,237],{"class":180},[76,441,111],{"class":86},[76,443,445,447,449],{"class":78,"line":444},18,[76,446,253],{"class":86},[76,448,237],{"class":180},[76,450,111],{"class":86},[76,452,454,457,459],{"class":78,"line":453},19,[76,455,456],{"class":86},"}",[76,458,237],{"class":90},[76,460,111],{"class":86},[462,463,464,472],"ul",{},[465,466,467,471],"li",{},[27,468,469],{},[49,470,51],{},"：创建隔离的 Angular 测试模块。",[465,473,474,479,480,483],{},[27,475,476],{},[49,477,478],{},"fixture.detectChanges()","：触发变更检测，更新 DOM。",[27,481,482],{},"每次","修改了会影响视图的数据后都要重新调用，否则断言读到的是旧 DOM。",[40,485,486],{"id":486},"服务的单元测试",[23,488,489,490,493,494,497],{},"服务通常不依赖 DOM，可以直接 ",[49,491,492],{},"new"," 或通过 ",[49,495,496],{},"TestBed.inject"," 获取，测试更轻量：",[67,499,501],{"className":69,"code":500,"language":71,"meta":72,"style":72},"describe(\"CartService\", () => {\n  let service: CartService;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({});\n    service = TestBed.inject(CartService);\n  });\n\n  it(\"add 后 total 正确计算\", () => {\n    service.add({ id: \"1\", price: 10 } as CartItem);\n    service.add({ id: \"2\", price: 20 } as CartItem);\n    expect(service.total()).toBe(30);\n  });\n});\n",[49,502,503,524,539,543,555,573,595,603,607,628,676,718,749,757],{"__ignoreMap":72},[76,504,505,507,509,511,514,516,518,520,522],{"class":78,"line":79},[76,506,148],{"class":147},[76,508,151],{"class":90},[76,510,108],{"class":100},[76,512,513],{"class":104},"CartService",[76,515,108],{"class":100},[76,517,161],{"class":86},[76,519,164],{"class":86},[76,521,168],{"class":167},[76,523,171],{"class":86},[76,525,526,529,532,534,537],{"class":78,"line":114},[76,527,528],{"class":167},"  let",[76,530,531],{"class":90}," service",[76,533,217],{"class":298},[76,535,536],{"class":342}," CartService",[76,538,111],{"class":86},[76,540,541],{"class":78,"line":137},[76,542,141],{"emptyLinePlaceholder":140},[76,544,545,547,549,551,553],{"class":78,"line":144},[76,546,177],{"class":147},[76,548,151],{"class":180},[76,550,245],{"class":86},[76,552,168],{"class":167},[76,554,171],{"class":86},[76,556,557,560,562,564,566,569,571],{"class":78,"line":174},[76,558,559],{"class":90},"    TestBed",[76,561,200],{"class":86},[76,563,203],{"class":147},[76,565,151],{"class":180},[76,567,568],{"class":86},"{}",[76,570,237],{"class":180},[76,572,111],{"class":86},[76,574,575,578,580,582,584,587,589,591,593],{"class":78,"line":192},[76,576,577],{"class":90},"    service",[76,579,299],{"class":298},[76,581,91],{"class":90},[76,583,200],{"class":86},[76,585,586],{"class":147},"inject",[76,588,151],{"class":180},[76,590,513],{"class":90},[76,592,237],{"class":180},[76,594,111],{"class":86},[76,596,597,599,601],{"class":78,"line":211},[76,598,253],{"class":86},[76,600,237],{"class":180},[76,602,111],{"class":86},[76,604,605],{"class":78,"line":231},[76,606,141],{"emptyLinePlaceholder":140},[76,608,609,611,613,615,618,620,622,624,626],{"class":78,"line":250},[76,610,268],{"class":147},[76,612,151],{"class":180},[76,614,108],{"class":100},[76,616,617],{"class":104},"add 后 total 正确计算",[76,619,108],{"class":100},[76,621,161],{"class":86},[76,623,164],{"class":86},[76,625,168],{"class":167},[76,627,171],{"class":86},[76,629,630,632,634,637,639,642,645,647,649,651,653,655,658,660,664,666,669,672,674],{"class":78,"line":260},[76,631,577],{"class":90},[76,633,200],{"class":86},[76,635,636],{"class":147},"add",[76,638,151],{"class":180},[76,640,641],{"class":86},"{",[76,643,644],{"class":180}," id",[76,646,217],{"class":86},[76,648,101],{"class":100},[76,650,435],{"class":104},[76,652,108],{"class":100},[76,654,161],{"class":86},[76,656,657],{"class":180}," price",[76,659,217],{"class":86},[76,661,663],{"class":662},"srdBf"," 10",[76,665,94],{"class":86},[76,667,668],{"class":82}," as",[76,670,671],{"class":342}," CartItem",[76,673,237],{"class":180},[76,675,111],{"class":86},[76,677,678,680,682,684,686,688,690,692,694,697,699,701,703,705,708,710,712,714,716],{"class":78,"line":265},[76,679,577],{"class":90},[76,681,200],{"class":86},[76,683,636],{"class":147},[76,685,151],{"class":180},[76,687,641],{"class":86},[76,689,644],{"class":180},[76,691,217],{"class":86},[76,693,101],{"class":100},[76,695,696],{"class":104},"2",[76,698,108],{"class":100},[76,700,161],{"class":86},[76,702,657],{"class":180},[76,704,217],{"class":86},[76,706,707],{"class":662}," 20",[76,709,94],{"class":86},[76,711,668],{"class":82},[76,713,671],{"class":342},[76,715,237],{"class":180},[76,717,111],{"class":86},[76,719,720,722,724,727,729,732,735,737,740,742,745,747],{"class":78,"line":288},[76,721,407],{"class":147},[76,723,151],{"class":180},[76,725,726],{"class":90},"service",[76,728,200],{"class":86},[76,730,731],{"class":147},"total",[76,733,734],{"class":180},"())",[76,736,200],{"class":86},[76,738,739],{"class":147},"toBe",[76,741,151],{"class":180},[76,743,744],{"class":662},"30",[76,746,237],{"class":180},[76,748,111],{"class":86},[76,750,751,753,755],{"class":78,"line":317},[76,752,253],{"class":86},[76,754,237],{"class":180},[76,756,111],{"class":86},[76,758,759,761,763],{"class":78,"line":332},[76,760,456],{"class":86},[76,762,237],{"class":90},[76,764,111],{"class":86},[40,766,767],{"id":767},"模拟依赖",[67,769,771],{"className":69,"code":770,"language":71,"meta":72,"style":72},"TestBed.configureTestingModule({\n  imports: [UserListComponent],\n  providers: [{ provide: UserApi, useValue: { list: () => of([{ id: 1, name: \"Test\" }]) } }],\n});\n",[49,772,773,785,797,876],{"__ignoreMap":72},[76,774,775,777,779,781,783],{"class":78,"line":79},[76,776,51],{"class":90},[76,778,200],{"class":86},[76,780,203],{"class":147},[76,782,151],{"class":90},[76,784,208],{"class":86},[76,786,787,790,792,795],{"class":78,"line":114},[76,788,789],{"class":180},"  imports",[76,791,217],{"class":86},[76,793,794],{"class":90}," [UserListComponent]",[76,796,228],{"class":86},[76,798,799,802,804,806,808,811,813,816,818,821,823,825,828,830,832,834,837,840,842,844,846,849,851,854,856,858,861,863,865,868,870,872,874],{"class":78,"line":137},[76,800,801],{"class":180},"  providers",[76,803,217],{"class":86},[76,805,220],{"class":90},[76,807,641],{"class":86},[76,809,810],{"class":180}," provide",[76,812,217],{"class":86},[76,814,815],{"class":90}," UserApi",[76,817,161],{"class":86},[76,819,820],{"class":180}," useValue",[76,822,217],{"class":86},[76,824,87],{"class":86},[76,826,827],{"class":147}," list",[76,829,217],{"class":86},[76,831,164],{"class":86},[76,833,168],{"class":167},[76,835,836],{"class":147}," of",[76,838,839],{"class":90},"([",[76,841,641],{"class":86},[76,843,644],{"class":180},[76,845,217],{"class":86},[76,847,848],{"class":662}," 1",[76,850,161],{"class":86},[76,852,853],{"class":180}," name",[76,855,217],{"class":86},[76,857,101],{"class":100},[76,859,860],{"class":104},"Test",[76,862,108],{"class":100},[76,864,94],{"class":86},[76,866,867],{"class":90},"]) ",[76,869,456],{"class":86},[76,871,94],{"class":86},[76,873,225],{"class":90},[76,875,228],{"class":86},[76,877,878,880,882],{"class":78,"line":144},[76,879,456],{"class":86},[76,881,237],{"class":90},[76,883,111],{"class":86},[23,885,886,887,890,891,894,895,898],{},"对于依赖 ",[49,888,889],{},"HttpClient"," 的服务，推荐使用官方提供的 ",[49,892,893],{},"HttpClientTestingModule","（或新版 ",[49,896,897],{},"provideHttpClientTesting","）拦截真实请求：",[67,900,902],{"className":69,"code":901,"language":71,"meta":72,"style":72},"import { HttpTestingController, provideHttpClientTesting } from \"@angular\u002Fcommon\u002Fhttp\u002Ftesting\";\nimport { provideHttpClient } from \"@angular\u002Fcommon\u002Fhttp\";\n\nTestBed.configureTestingModule({\n  providers: [provideHttpClient(), provideHttpClientTesting()],\n});\n\nconst httpMock = TestBed.inject(HttpTestingController);\n\nit(\"请求了正确的接口\", () => {\n  service.getUser(1).subscribe();\n  const req = httpMock.expectOne(\"\u002Fapi\u002Fusers\u002F1\");\n  req.flush({ id: 1, name: \"Test\" });\n  httpMock.verify(); \u002F\u002F 确保没有未处理的请求\n});\n",[49,903,904,931,953,957,969,991,999,1003,1024,1028,1050,1075,1105,1143,1162],{"__ignoreMap":72},[76,905,906,908,910,913,915,918,920,922,924,927,929],{"class":78,"line":79},[76,907,83],{"class":82},[76,909,87],{"class":86},[76,911,912],{"class":90}," HttpTestingController",[76,914,161],{"class":86},[76,916,917],{"class":90}," provideHttpClientTesting",[76,919,94],{"class":86},[76,921,97],{"class":82},[76,923,101],{"class":100},[76,925,926],{"class":104},"@angular\u002Fcommon\u002Fhttp\u002Ftesting",[76,928,108],{"class":100},[76,930,111],{"class":86},[76,932,933,935,937,940,942,944,946,949,951],{"class":78,"line":114},[76,934,83],{"class":82},[76,936,87],{"class":86},[76,938,939],{"class":90}," provideHttpClient",[76,941,94],{"class":86},[76,943,97],{"class":82},[76,945,101],{"class":100},[76,947,948],{"class":104},"@angular\u002Fcommon\u002Fhttp",[76,950,108],{"class":100},[76,952,111],{"class":86},[76,954,955],{"class":78,"line":137},[76,956,141],{"emptyLinePlaceholder":140},[76,958,959,961,963,965,967],{"class":78,"line":144},[76,960,51],{"class":90},[76,962,200],{"class":86},[76,964,203],{"class":147},[76,966,151],{"class":90},[76,968,208],{"class":86},[76,970,971,973,975,977,980,982,984,986,989],{"class":78,"line":174},[76,972,801],{"class":180},[76,974,217],{"class":86},[76,976,220],{"class":90},[76,978,979],{"class":147},"provideHttpClient",[76,981,245],{"class":90},[76,983,161],{"class":86},[76,985,917],{"class":147},[76,987,988],{"class":90},"()]",[76,990,228],{"class":86},[76,992,993,995,997],{"class":78,"line":192},[76,994,456],{"class":86},[76,996,237],{"class":90},[76,998,111],{"class":86},[76,1000,1001],{"class":78,"line":211},[76,1002,141],{"emptyLinePlaceholder":140},[76,1004,1005,1008,1011,1013,1015,1017,1019,1022],{"class":78,"line":231},[76,1006,1007],{"class":167},"const",[76,1009,1010],{"class":294}," httpMock",[76,1012,299],{"class":298},[76,1014,91],{"class":90},[76,1016,200],{"class":86},[76,1018,586],{"class":147},[76,1020,1021],{"class":90},"(HttpTestingController)",[76,1023,111],{"class":86},[76,1025,1026],{"class":78,"line":250},[76,1027,141],{"emptyLinePlaceholder":140},[76,1029,1030,1033,1035,1037,1040,1042,1044,1046,1048],{"class":78,"line":260},[76,1031,1032],{"class":147},"it",[76,1034,151],{"class":90},[76,1036,108],{"class":100},[76,1038,1039],{"class":104},"请求了正确的接口",[76,1041,108],{"class":100},[76,1043,161],{"class":86},[76,1045,164],{"class":86},[76,1047,168],{"class":167},[76,1049,171],{"class":86},[76,1051,1052,1055,1057,1060,1062,1064,1066,1068,1071,1073],{"class":78,"line":265},[76,1053,1054],{"class":90},"  service",[76,1056,200],{"class":86},[76,1058,1059],{"class":147},"getUser",[76,1061,151],{"class":180},[76,1063,435],{"class":662},[76,1065,237],{"class":180},[76,1067,200],{"class":86},[76,1069,1070],{"class":147},"subscribe",[76,1072,245],{"class":180},[76,1074,111],{"class":86},[76,1076,1077,1080,1083,1085,1087,1089,1092,1094,1096,1099,1101,1103],{"class":78,"line":288},[76,1078,1079],{"class":167},"  const",[76,1081,1082],{"class":294}," req",[76,1084,299],{"class":298},[76,1086,1010],{"class":90},[76,1088,200],{"class":86},[76,1090,1091],{"class":147},"expectOne",[76,1093,151],{"class":180},[76,1095,108],{"class":100},[76,1097,1098],{"class":104},"\u002Fapi\u002Fusers\u002F1",[76,1100,108],{"class":100},[76,1102,237],{"class":180},[76,1104,111],{"class":86},[76,1106,1107,1110,1112,1115,1117,1119,1121,1123,1125,1127,1129,1131,1133,1135,1137,1139,1141],{"class":78,"line":317},[76,1108,1109],{"class":90},"  req",[76,1111,200],{"class":86},[76,1113,1114],{"class":147},"flush",[76,1116,151],{"class":180},[76,1118,641],{"class":86},[76,1120,644],{"class":180},[76,1122,217],{"class":86},[76,1124,848],{"class":662},[76,1126,161],{"class":86},[76,1128,853],{"class":180},[76,1130,217],{"class":86},[76,1132,101],{"class":100},[76,1134,860],{"class":104},[76,1136,108],{"class":100},[76,1138,94],{"class":86},[76,1140,237],{"class":180},[76,1142,111],{"class":86},[76,1144,1145,1148,1150,1153,1155,1158],{"class":78,"line":332},[76,1146,1147],{"class":90},"  httpMock",[76,1149,200],{"class":86},[76,1151,1152],{"class":147},"verify",[76,1154,245],{"class":180},[76,1156,1157],{"class":86},";",[76,1159,1161],{"class":1160},"sutJx"," \u002F\u002F 确保没有未处理的请求\n",[76,1163,1164,1166,1168],{"class":78,"line":376},[76,1165,456],{"class":86},[76,1167,237],{"class":90},[76,1169,111],{"class":86},[40,1171,1172],{"id":1172},"异步与信号测试",[67,1174,1176],{"className":69,"code":1175,"language":71,"meta":72,"style":72},"import { fakeAsync, tick, waitForAsync } from \"@angular\u002Fcore\u002Ftesting\";\n\nit(\"debounce 后才发起搜索\", fakeAsync(() => {\n  component.onInput(\"abc\");\n  tick(300); \u002F\u002F 快进虚拟时钟 300ms，触发 debounceTime\n  expect(component.results().length).toBeGreaterThan(0);\n}));\n\nit(\"异步初始化完成后渲染数据\", waitForAsync(async () => {\n  fixture.detectChanges();\n  await fixture.whenStable();\n  expect(fixture.nativeElement.textContent).toContain(\"加载完成\");\n}));\n",[49,1177,1178,1209,1213,1238,1261,1278,1316,1325,1329,1356,1369,1385,1420],{"__ignoreMap":72},[76,1179,1180,1182,1184,1187,1189,1192,1194,1197,1199,1201,1203,1205,1207],{"class":78,"line":79},[76,1181,83],{"class":82},[76,1183,87],{"class":86},[76,1185,1186],{"class":90}," fakeAsync",[76,1188,161],{"class":86},[76,1190,1191],{"class":90}," tick",[76,1193,161],{"class":86},[76,1195,1196],{"class":90}," waitForAsync",[76,1198,94],{"class":86},[76,1200,97],{"class":82},[76,1202,101],{"class":100},[76,1204,105],{"class":104},[76,1206,108],{"class":100},[76,1208,111],{"class":86},[76,1210,1211],{"class":78,"line":114},[76,1212,141],{"emptyLinePlaceholder":140},[76,1214,1215,1217,1219,1221,1224,1226,1228,1230,1232,1234,1236],{"class":78,"line":137},[76,1216,1032],{"class":147},[76,1218,151],{"class":90},[76,1220,108],{"class":100},[76,1222,1223],{"class":104},"debounce 后才发起搜索",[76,1225,108],{"class":100},[76,1227,161],{"class":86},[76,1229,1186],{"class":147},[76,1231,151],{"class":90},[76,1233,245],{"class":86},[76,1235,168],{"class":167},[76,1237,171],{"class":86},[76,1239,1240,1243,1245,1248,1250,1252,1255,1257,1259],{"class":78,"line":144},[76,1241,1242],{"class":90},"  component",[76,1244,200],{"class":86},[76,1246,1247],{"class":147},"onInput",[76,1249,151],{"class":180},[76,1251,108],{"class":100},[76,1253,1254],{"class":104},"abc",[76,1256,108],{"class":100},[76,1258,237],{"class":180},[76,1260,111],{"class":86},[76,1262,1263,1266,1268,1271,1273,1275],{"class":78,"line":174},[76,1264,1265],{"class":147},"  tick",[76,1267,151],{"class":180},[76,1269,1270],{"class":662},"300",[76,1272,237],{"class":180},[76,1274,1157],{"class":86},[76,1276,1277],{"class":1160}," \u002F\u002F 快进虚拟时钟 300ms，触发 debounceTime\n",[76,1279,1280,1283,1285,1288,1290,1293,1295,1297,1300,1302,1304,1307,1309,1312,1314],{"class":78,"line":192},[76,1281,1282],{"class":147},"  expect",[76,1284,151],{"class":180},[76,1286,1287],{"class":90},"component",[76,1289,200],{"class":86},[76,1291,1292],{"class":147},"results",[76,1294,245],{"class":180},[76,1296,200],{"class":86},[76,1298,1299],{"class":294},"length",[76,1301,237],{"class":180},[76,1303,200],{"class":86},[76,1305,1306],{"class":147},"toBeGreaterThan",[76,1308,151],{"class":180},[76,1310,1311],{"class":662},"0",[76,1313,237],{"class":180},[76,1315,111],{"class":86},[76,1317,1318,1320,1323],{"class":78,"line":211},[76,1319,456],{"class":86},[76,1321,1322],{"class":90},"))",[76,1324,111],{"class":86},[76,1326,1327],{"class":78,"line":231},[76,1328,141],{"emptyLinePlaceholder":140},[76,1330,1331,1333,1335,1337,1340,1342,1344,1346,1348,1350,1352,1354],{"class":78,"line":250},[76,1332,1032],{"class":147},[76,1334,151],{"class":90},[76,1336,108],{"class":100},[76,1338,1339],{"class":104},"异步初始化完成后渲染数据",[76,1341,108],{"class":100},[76,1343,161],{"class":86},[76,1345,1196],{"class":147},[76,1347,151],{"class":90},[76,1349,183],{"class":167},[76,1351,164],{"class":86},[76,1353,168],{"class":167},[76,1355,171],{"class":86},[76,1357,1358,1361,1363,1365,1367],{"class":78,"line":260},[76,1359,1360],{"class":90},"  fixture",[76,1362,200],{"class":86},[76,1364,325],{"class":147},[76,1366,245],{"class":180},[76,1368,111],{"class":86},[76,1370,1371,1374,1376,1378,1381,1383],{"class":78,"line":265},[76,1372,1373],{"class":82},"  await",[76,1375,295],{"class":90},[76,1377,200],{"class":86},[76,1379,1380],{"class":147},"whenStable",[76,1382,245],{"class":180},[76,1384,111],{"class":86},[76,1386,1387,1389,1391,1393,1395,1397,1399,1401,1403,1405,1407,1409,1411,1414,1416,1418],{"class":78,"line":288},[76,1388,1282],{"class":147},[76,1390,151],{"class":180},[76,1392,412],{"class":90},[76,1394,200],{"class":86},[76,1396,352],{"class":90},[76,1398,200],{"class":86},[76,1400,421],{"class":90},[76,1402,237],{"class":180},[76,1404,200],{"class":86},[76,1406,428],{"class":147},[76,1408,151],{"class":180},[76,1410,108],{"class":100},[76,1412,1413],{"class":104},"加载完成",[76,1415,108],{"class":100},[76,1417,237],{"class":180},[76,1419,111],{"class":86},[76,1421,1422,1424,1426],{"class":78,"line":317},[76,1423,456],{"class":86},[76,1425,1322],{"class":90},[76,1427,111],{"class":86},[23,1429,1430,1431,1434,1435,1438],{},"对基于 Signal 的组件，测试通常更简单：直接调用 ",[49,1432,1433],{},"signal.set()"," 后 ",[49,1436,1437],{},"detectChanges()","，无需处理 Zone\u002FObservable 的异步细节。",[40,1440,1441],{"id":1441},"端到端测试",[23,1443,1444,1445,34,1448,1451,1452,1455,1456,1463],{},"CLI 可选用 ",[27,1446,1447],{},"Playwright",[27,1449,1450],{},"Cypress"," 脚手架（",[49,1453,1454],{},"ng e2e"," 相关方案以当前 ",[1457,1458,1462],"a",{"href":1459,"rel":1460},"https:\u002F\u002Fangular.dev\u002Fguide\u002Ftesting",[1461],"nofollow","Angular 测试指南"," 为准）：",[67,1465,1469],{"className":1466,"code":1467,"language":1468,"meta":72,"style":72},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","ng add @playwright\u002Ftest    # 或社区\u002F官方推荐的当前方案\nnpx playwright test\n","bash",[49,1470,1471,1485],{"__ignoreMap":72},[76,1472,1473,1476,1479,1482],{"class":78,"line":79},[76,1474,1475],{"class":342},"ng",[76,1477,1478],{"class":104}," add",[76,1480,1481],{"class":104}," @playwright\u002Ftest",[76,1483,1484],{"class":1160},"    # 或社区\u002F官方推荐的当前方案\n",[76,1486,1487,1490,1493],{"class":78,"line":114},[76,1488,1489],{"class":342},"npx",[76,1491,1492],{"class":104}," playwright",[76,1494,1495],{"class":104}," test\n",[67,1497,1499],{"className":69,"code":1498,"language":71,"meta":72,"style":72},"\u002F\u002F e2e 示例（Playwright）\ntest(\"登录后跳转到首页\", async ({ page }) => {\n  await page.goto(\"\u002Flogin\");\n  await page.fill('input[name=\"email\"]', \"a@b.com\");\n  await page.click('button[type=\"submit\"]');\n  await expect(page).toHaveURL(\"\u002Fhome\");\n});\n",[49,1500,1501,1506,1538,1562,1596,1619,1651],{"__ignoreMap":72},[76,1502,1503],{"class":78,"line":79},[76,1504,1505],{"class":1160},"\u002F\u002F e2e 示例（Playwright）\n",[76,1507,1508,1510,1512,1514,1517,1519,1521,1524,1527,1531,1534,1536],{"class":78,"line":114},[76,1509,13],{"class":147},[76,1511,151],{"class":90},[76,1513,108],{"class":100},[76,1515,1516],{"class":104},"登录后跳转到首页",[76,1518,108],{"class":100},[76,1520,161],{"class":86},[76,1522,1523],{"class":167}," async",[76,1525,1526],{"class":86}," ({",[76,1528,1530],{"class":1529},"s99_P"," page",[76,1532,1533],{"class":86}," })",[76,1535,168],{"class":167},[76,1537,171],{"class":86},[76,1539,1540,1542,1544,1546,1549,1551,1553,1556,1558,1560],{"class":78,"line":137},[76,1541,1373],{"class":82},[76,1543,1530],{"class":90},[76,1545,200],{"class":86},[76,1547,1548],{"class":147},"goto",[76,1550,151],{"class":180},[76,1552,108],{"class":100},[76,1554,1555],{"class":104},"\u002Flogin",[76,1557,108],{"class":100},[76,1559,237],{"class":180},[76,1561,111],{"class":86},[76,1563,1564,1566,1568,1570,1573,1575,1578,1581,1583,1585,1587,1590,1592,1594],{"class":78,"line":144},[76,1565,1373],{"class":82},[76,1567,1530],{"class":90},[76,1569,200],{"class":86},[76,1571,1572],{"class":147},"fill",[76,1574,151],{"class":180},[76,1576,1577],{"class":100},"'",[76,1579,1580],{"class":104},"input[name=\"email\"]",[76,1582,1577],{"class":100},[76,1584,161],{"class":86},[76,1586,101],{"class":100},[76,1588,1589],{"class":104},"a@b.com",[76,1591,108],{"class":100},[76,1593,237],{"class":180},[76,1595,111],{"class":86},[76,1597,1598,1600,1602,1604,1606,1608,1610,1613,1615,1617],{"class":78,"line":174},[76,1599,1373],{"class":82},[76,1601,1530],{"class":90},[76,1603,200],{"class":86},[76,1605,384],{"class":147},[76,1607,151],{"class":180},[76,1609,1577],{"class":100},[76,1611,1612],{"class":104},"button[type=\"submit\"]",[76,1614,1577],{"class":100},[76,1616,237],{"class":180},[76,1618,111],{"class":86},[76,1620,1621,1623,1626,1628,1631,1633,1635,1638,1640,1642,1645,1647,1649],{"class":78,"line":192},[76,1622,1373],{"class":82},[76,1624,1625],{"class":147}," expect",[76,1627,151],{"class":180},[76,1629,1630],{"class":90},"page",[76,1632,237],{"class":180},[76,1634,200],{"class":86},[76,1636,1637],{"class":147},"toHaveURL",[76,1639,151],{"class":180},[76,1641,108],{"class":100},[76,1643,1644],{"class":104},"\u002Fhome",[76,1646,108],{"class":100},[76,1648,237],{"class":180},[76,1650,111],{"class":86},[76,1652,1653,1655,1657],{"class":78,"line":211},[76,1654,456],{"class":86},[76,1656,237],{"class":90},[76,1658,111],{"class":86},[40,1660,1661],{"id":1661},"建议",[462,1663,1664,1671],{},[465,1665,1666,1667,1670],{},"测 ",[27,1668,1669],{},"行为与 DOM 结果","，而非私有字段。",[465,1672,1673,1674,1677,1678,34,1681,1684,1685,1687],{},"异步使用 ",[49,1675,1676],{},"fakeAsync"," + ",[49,1679,1680],{},"tick",[49,1682,1683],{},"waitForAsync"," \u002F ",[49,1686,183],{}," 工具。",[40,1689,1690],{"id":1690},"最佳实践",[462,1692,1693,1696,1703,1710,1713],{},[465,1694,1695],{},"优先测「用户能看到\u002F操作的行为」（点击按钮后文案变化），少测组件私有实现细节（内部变量、私有方法）。",[465,1697,1698,1699,1702],{},"HTTP 依赖统一走 ",[49,1700,1701],{},"HttpTestingController"," 模拟，不要在单测里发真实网络请求。",[465,1704,1705,1706,1709],{},"组件测试关注",[27,1707,1708],{},"渲染结果与交互结果","，业务逻辑复杂的部分尽量抽到独立的、更容易测的 Service\u002F纯函数中。",[465,1711,1712],{},"CI 中固定 Node\u002F浏览器（Headless Chrome）版本，避免「本地通过、CI 挂」的环境差异问题。",[465,1714,1715],{},"逐步为核心业务组件补齐测试，而非追求一次性 100% 覆盖率，优先覆盖高风险路径（支付、登录、权限判断）。",[40,1717,1718],{"id":1718},"常见坑",[1720,1721,1722,1738],"table",{},[1723,1724,1725],"thead",{},[1726,1727,1728,1732,1735],"tr",{},[1729,1730,1731],"th",{},"现象",[1729,1733,1734],{},"常见原因",[1729,1736,1737],{},"处理",[1739,1740,1741,1755,1775,1789,1813],"tbody",{},[1726,1742,1743,1747,1752],{},[1744,1745,1746],"td",{},"断言读到旧的 DOM 内容",[1744,1748,1749,1750],{},"数据变化后忘记调用 ",[49,1751,478],{},[1744,1753,1754],{},"每次影响视图的操作后都调用",[1726,1756,1757,1762,1768],{},[1744,1758,1759,1761],{},[49,1760,1676],{}," 测试报「还有未清理的计时器」",[1744,1763,1764,1765,1767],{},"组件内计时器未被 ",[49,1766,1680],{}," 完全消费或未清理",[1744,1769,1770,1771,1774],{},"用 ",[49,1772,1773],{},"tick(足够时长)","，或在测试后清理订阅",[1726,1776,1777,1780,1783],{},[1744,1778,1779],{},"HTTP 测试报「预期恰好一次请求」失败",[1744,1781,1782],{},"组件触发了多次或零次请求",[1744,1784,1785,1786,1788],{},"检查触发条件，必要时用 ",[49,1787,1091],{}," 前先确认请求次数",[1726,1790,1791,1794,1801],{},[1744,1792,1793],{},"测试之间状态串扰",[1744,1795,1796,1797,1800],{},"服务是 ",[49,1798,1799],{},"providedIn: 'root'"," 单例，测试间未重置",[1744,1802,1803,1804,1806,1807,1810,1811],{},"每个 ",[49,1805,1032],{}," 前用 ",[49,1808,1809],{},"beforeEach"," 重新 ",[49,1812,203],{},[1726,1814,1815,1818,1821],{},[1744,1816,1817],{},"CI 上 Karma 报浏览器无法启动",[1744,1819,1820],{},"缺少 Headless Chrome 或相关依赖",[1744,1822,1823,1824,1827],{},"使用官方 CI 镜像或配置 ",[49,1825,1826],{},"ChromeHeadlessCI"," launcher",[40,1829,1830],{"id":1830},"延伸阅读",[462,1832,1833,1840,1847],{},[465,1834,1835,1839],{},[1457,1836,1838],{"href":1837},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fforms","表单","：表单组件的测试要点",[465,1841,1842,1846],{},[1457,1843,1845],{"href":1844},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fproject","工程化","：CLI 测试相关配置",[465,1848,1849,1853],{},[1457,1850,1852],{"href":1851},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Fbasics","Signal 基础","：Signal 组件更易测试的原因",[40,1855,1856],{"id":1856},"参考文献",[23,1858,1859],{},"以下链接在编写时均可正常访问：",[1720,1861,1862,1872],{},[1723,1863,1864],{},[1726,1865,1866,1869],{},[1729,1867,1868],{},"资料",[1729,1870,1871],{},"说明",[1739,1873,1874,1884,1895,1907,1918],{},[1726,1875,1876,1881],{},[1744,1877,1878],{},[1457,1879,1462],{"href":1459,"rel":1880},[1461],[1744,1882,1883],{},"官方",[1726,1885,1886,1892],{},[1744,1887,1888],{},[1457,1889,51],{"href":1890,"rel":1891},"https:\u002F\u002Fangular.dev\u002Fapi\u002Fcore\u002Ftesting\u002FTestBed",[1461],[1744,1893,1894],{},"API",[1726,1896,1897,1904],{},[1744,1898,1899],{},[1457,1900,1903],{"href":1901,"rel":1902},"https:\u002F\u002Fjasmine.github.io\u002F",[1461],"Jasmine 文档",[1744,1905,1906],{},"断言库",[1726,1908,1909,1915],{},[1744,1910,1911],{},[1457,1912,893],{"href":1913,"rel":1914},"https:\u002F\u002Fangular.dev\u002Fapi\u002Fcommon\u002Fhttp\u002Ftesting\u002FHttpTestingController",[1461],[1744,1916,1917],{},"HTTP 模拟",[1726,1919,1920,1926],{},[1744,1921,1922],{},[1457,1923,1447],{"href":1924,"rel":1925},"https:\u002F\u002Fplaywright.dev\u002F",[1461],[1744,1927,1441],{},[1929,1930,1931],"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 .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--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 .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}",{"title":72,"searchDepth":114,"depth":114,"links":1933},[1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944],{"id":42,"depth":114,"text":42},{"id":64,"depth":114,"text":65},{"id":486,"depth":114,"text":486},{"id":767,"depth":114,"text":767},{"id":1172,"depth":114,"text":1172},{"id":1441,"depth":114,"text":1441},{"id":1661,"depth":114,"text":1661},{"id":1690,"depth":114,"text":1690},{"id":1718,"depth":114,"text":1718},{"id":1830,"depth":114,"text":1830},{"id":1856,"depth":114,"text":1856},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Ftest","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Ftest.md","md",{"items":1950},[1951,1955,1957,1961,1963,1967],{"path":1952,"title":1953,"description":1954,"category":10,"score":192},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fecology","生态集成","Angular 官方维护路由、HTTP、表单、动画等；UI 与状态扩展由社区与官方 Material 团队提供。涵盖官方\u002F半官方库、常用第三方库、与 Vue\u002FReact 生态对照、选型建议与常见坑。",{"path":1851,"title":1852,"description":1956,"category":10,"score":192},"Signals 是 Angular 16 引入的响应式原语：可读写的 signal、派生的 computed、副作用 effect。用于组件内状态与跨组件细粒度更新，涵盖 API、input signal、与变更检测的关系、最佳实践与常见坑。",{"path":1958,"title":1959,"description":1960,"category":10,"score":192},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Frxjs","RxJS 互操作","Angular 内置 RxJS 处理异步流（HTTP、HttpClient、路由事件、FormControl 的 valueChanges 等）。Signal 适合同步\u002F短链路状态；长生命周期流仍常用 Observable。涵盖 async 管道、toSignal\u002FtoObservable、常用操作符、最佳实践与常见坑。",{"path":1837,"title":1838,"description":1962,"category":10,"score":192},"Angular 提供 响应式表单（Reactive Forms） 与 模板驱动表单（Template-driven）。复杂表单推荐 Reactive Forms（模型在 TypeScript 中，易测试）。涵盖 FormGroup\u002FFormArray、Typed Forms、自定义校验器、最佳实践与常见坑。",{"path":1964,"title":1965,"description":1966,"category":10,"score":192},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fstate","状态管理","Angular 没有强制单一状态库：服务 + DI、Signal、RxJS 即可覆盖多数场景；大型应用可选用 NgRx。涵盖选型、服务作为状态容器、跨组件共享、NgRx 与 SignalStore、常见坑。",{"path":1968,"title":1969,"description":1970,"category":10,"score":192},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fhttp","HTTP 与数据","Angular 通过 HttpClient（@angular\u002Fcommon\u002Fhttp）发起 HTTP 请求，返回 RxJS Observable。见 RxJS 互操作、依赖注入。",{"series":1972,"title":12,"items":1973,"index":317,"prev":2013,"next":2014},"booklet\u002Fframe\u002Fangular",[1974,1978,1980,1984,1988,1992,1994,1996,1998,2000,2004,2008,2010,2012],{"path":1975,"title":1976,"date":1977,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fbasics","Angular 基础","2020-03-07",{"path":1851,"title":1852,"date":1979,"pinned":15},"2026-06-12",{"path":1981,"title":1982,"date":1983,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fchange-detection","变更检测","2023-10-12",{"path":1985,"title":1986,"date":1987,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fcomponents","组件与模板","2021-07-11",{"path":1989,"title":1990,"date":1991,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fdi","依赖注入","2021-01-24",{"path":1952,"title":1953,"date":1993,"pinned":15},"2026-07-12",{"path":1837,"title":1838,"date":1995,"pinned":15},"2025-05-26",{"path":1968,"title":1969,"date":1997,"pinned":15},"2024-12-27",{"path":1844,"title":1845,"date":1999,"pinned":15},"2021-09-01",{"path":2001,"title":2002,"date":2003,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Frouter","路由","2023-02-04",{"path":2005,"title":2006,"date":2007,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime","运行时优化","2022-11-01",{"path":1958,"title":1959,"date":2009,"pinned":15},"2026-04-06",{"path":1964,"title":1965,"date":2011,"pinned":15},"2025-04-29",{"path":7,"title":8,"date":14,"pinned":15},{"path":1964,"title":1965,"date":2011,"pinned":15},null]