[{"data":1,"prerenderedAt":1106},["ShallowReactive",2],{"content:\u002Fframework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular":3,"surroundings:\u002Fframework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular":1097},{"id":4,"title":5,"body":6,"description":1078,"extension":1079,"meta":1080,"navigation":1090,"path":1091,"seo":1092,"stem":1095,"__hash__":1096},"content\u002Fframework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular\u002Findex.md","Going Zoneless in Angular",{"type":7,"value":8,"toc":1059},"minimark",[9,13,28,44,133,138,167,171,177,183,189,199,203,208,478,482,487,491,500,504,515,634,638,641,645,651,655,673,792,796,842,846,849,853,881,885,891,897,903,909,913,925,934,946,955,964,973,982,991,1000,1009,1018,1022,1044,1049,1052,1055],[10,11,5],"h1",{"id":12},"going-zoneless-in-angular",[14,15,16,17,22,23,27],"p",{},"This guide is part of ",[18,19,21],"a",{"href":20},"\u002Fframework-performance\u002Fangular-performance\u002F","Angular Performance",", within ",[18,24,26],{"href":25},"\u002Fframework-performance\u002F","Framework Performance",". zone.js has powered Angular's change detection since the beginning: it patches browser APIs so Angular knows when any async work completes, then runs change detection to update the UI. It is convenient — mutate any property and the view updates — but it has costs. Every async callback, including ones that change nothing (scroll listeners, polling timers, third-party analytics), triggers a change detection cycle. zone.js adds bundle size, patches many APIs, and makes stack traces harder to read.",[14,29,30,31,35,36,39,40,43],{},"Zoneless change detection removes zone.js. Angular schedules change detection only when it is told something changed: a signal used in a template updates, an event bound in a template fires, ",[32,33,34],"code",{},"markForCheck"," is called, or the ",[32,37,38],{},"async"," pipe receives a value. Applications that already use signals and ",[32,41,42],{},"OnPush"," can often switch with little work; applications that rely on zone.js to notice arbitrary mutations need migration first.",[14,45,46],{},[47,48,54,55,54,62,54,66,54,69,54,78,54,84,54,91,54,99,54,104,54,108,54,114,54,118,54,122,54,129,54],"svg",{"viewBox":49,"width":50,"role":51,"ariaLabel":52,"style":53},"0 0 760 163","100%","img","Bar chart comparing the number of change detection cycles in a dashboard with zone.js and zoneless change detection.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[56,57],"rect",{"className":58,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],"svg-canvas","0","#ffffff",[63,64,65],"title",{},"Change detection cycles during 10 seconds of dashboard use",[67,68,52],"desc",{},[56,70],{"x":71,"y":71,"width":72,"height":73,"rx":74,"fill":75,"stroke":76,"style":77},"1","758","161","10","none","currentColor","stroke-opacity:0.18",[79,80,65],"text",{"x":81,"y":82,"fill":76,"style":83},"28.0","34.0","font-size:16px;font-weight:700",[79,85,90],{"x":86,"y":87,"fill":76,"style":88,"textAnchor":89},"221.5","70.0","font-size:13px","end","zone.js (polling, scroll, analytics)",[56,92],{"x":93,"y":94,"width":95,"height":96,"rx":97,"fill":75,"stroke":76,"style":98},"233.5","56.0","434.5","19","3","fill-opacity:0.7;stroke-opacity:0.35",[79,100,103],{"x":101,"y":87,"fill":76,"style":102},"674.0","font-size:12px;font-weight:600","640cycles",[79,105,107],{"x":86,"y":106,"fill":76,"style":88,"textAnchor":89},"101.0","zone.js + runOutsideAngular fixes",[56,109],{"x":93,"y":110,"width":111,"height":96,"rx":97,"fill":112,"stroke":112,"style":113},"87.0","122.2","#0466c8","fill-opacity:0.55;stroke-opacity:0.9",[79,115,117],{"x":116,"y":106,"fill":76,"style":102},"361.7","180cycles",[79,119,121],{"x":86,"y":120,"fill":76,"style":88,"textAnchor":89},"132.0","Zoneless with signals",[56,123],{"x":93,"y":124,"width":125,"height":96,"rx":97,"fill":126,"stroke":127,"style":128},"118.0","28.5","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[79,130,132],{"x":131,"y":120,"fill":76,"style":102},"268.1","42cycles",[134,135,137],"h2",{"id":136},"rapid-diagnosis","Rapid Diagnosis",[139,140,141,149,155,161],"ul",{},[142,143,144,148],"li",{},[145,146,147],"strong",{},"Count change detection cycles"," with the Angular DevTools profiler during typical use.",[142,150,151,154],{},[145,152,153],{},"Check for state mutations outside signals"," (plain class properties updated in callbacks).",[142,156,157,160],{},[145,158,159],{},"Check component strategies",": default change detection components rely on zone-triggered checks.",[142,162,163,166],{},[145,164,165],{},"Check third-party libraries"," for dependencies on zone.js behaviour.",[134,168,170],{"id":169},"root-cause-analysis","Root Cause Analysis",[14,172,173,176],{},[145,174,175],{},"1. Zone-triggered cycles."," Unrelated async work triggers change detection.",[14,178,179,182],{},[145,180,181],{},"2. Implicit updates."," Code mutates properties and relies on zone.js to refresh views.",[14,184,185,188],{},[145,186,187],{},"3. Default change detection."," Components checked on every cycle.",[14,190,191,194,195,198],{},[145,192,193],{},"4. Library assumptions."," Some libraries expect ",[32,196,197],{},"NgZone.onStable"," or zone-based behaviour.",[134,200,202],{"id":201},"step-by-step-resolution","Step-by-Step Resolution",[204,205,207],"h3",{"id":206},"_1-move-component-state-to-signals","1. Move component state to signals",[209,210,215],"pre",{"className":211,"code":212,"language":213,"meta":214,"style":214},"language-typescript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","import { Component, signal, computed, ChangeDetectionStrategy } from '@angular\u002Fcore';\n@Component({\n  selector: 'app-cart-summary',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\u003Cp>{{ count() }} items · {{ total() | currency }}\u003C\u002Fp>`,\n})\nexport class CartSummary {\n  items = signal\u003C{ price: number; qty: number }[]>([]);\n  count = computed(() => this.items().reduce((n, i) => n + i.qty, 0));\n  total = computed(() => this.items().reduce((s, i) => s + i.price * i.qty, 0));\n}\n\u002F\u002F trade-off: signals require updating state through set\u002Fupdate instead of direct\n\u002F\u002F mutation; the explicitness is what lets Angular update only affected views.\n","typescript","",[32,216,217,240,253,265,271,282,288,304,342,405,459,465,472],{"__ignoreMap":214},[218,219,222,226,230,233,237],"span",{"class":220,"line":221},"line",1,[218,223,225],{"class":224},"sPARh","import",[218,227,229],{"class":228},"saISM"," { Component, signal, computed, ChangeDetectionStrategy } ",[218,231,232],{"class":224},"from",[218,234,236],{"class":235},"sZ8jY"," '@angular\u002Fcore'",[218,238,239],{"class":228},";\n",[218,241,243,246,250],{"class":220,"line":242},2,[218,244,245],{"class":228},"@",[218,247,249],{"class":248},"smZ65","Component",[218,251,252],{"class":228},"({\n",[218,254,256,259,262],{"class":220,"line":255},3,[218,257,258],{"class":228},"  selector: ",[218,260,261],{"class":235},"'app-cart-summary'",[218,263,264],{"class":228},",\n",[218,266,268],{"class":220,"line":267},4,[218,269,270],{"class":228},"  changeDetection: ChangeDetectionStrategy.OnPush,\n",[218,272,274,277,280],{"class":220,"line":273},5,[218,275,276],{"class":228},"  template: ",[218,278,279],{"class":235},"`\u003Cp>{{ count() }} items · {{ total() | currency }}\u003C\u002Fp>`",[218,281,264],{"class":228},[218,283,285],{"class":220,"line":284},6,[218,286,287],{"class":228},"})\n",[218,289,291,294,297,301],{"class":220,"line":290},7,[218,292,293],{"class":224},"export",[218,295,296],{"class":224}," class",[218,298,300],{"class":299},"sQw3B"," CartSummary",[218,302,303],{"class":228}," {\n",[218,305,307,310,313,316,319,322,325,329,332,335,337,339],{"class":220,"line":306},8,[218,308,309],{"class":299},"  items",[218,311,312],{"class":224}," =",[218,314,315],{"class":248}," signal",[218,317,318],{"class":228},"\u003C{ ",[218,320,321],{"class":299},"price",[218,323,324],{"class":224},":",[218,326,328],{"class":327},"sPXB4"," number",[218,330,331],{"class":228},"; ",[218,333,334],{"class":299},"qty",[218,336,324],{"class":224},[218,338,328],{"class":327},[218,340,341],{"class":228}," }[]>([]);\n",[218,343,345,348,350,353,356,359,362,365,368,371,374,377,380,383,386,389,391,394,397,400,402],{"class":220,"line":344},9,[218,346,347],{"class":299},"  count",[218,349,312],{"class":224},[218,351,352],{"class":248}," computed",[218,354,355],{"class":228},"(() ",[218,357,358],{"class":224},"=>",[218,360,361],{"class":327}," this",[218,363,364],{"class":228},".",[218,366,367],{"class":248},"items",[218,369,370],{"class":228},"().",[218,372,373],{"class":248},"reduce",[218,375,376],{"class":228},"((",[218,378,379],{"class":299},"n",[218,381,382],{"class":228},", ",[218,384,385],{"class":299},"i",[218,387,388],{"class":228},") ",[218,390,358],{"class":224},[218,392,393],{"class":228}," n ",[218,395,396],{"class":224},"+",[218,398,399],{"class":228}," i.qty, ",[218,401,60],{"class":327},[218,403,404],{"class":228},"));\n",[218,406,408,411,413,415,417,419,421,423,425,427,429,431,434,436,438,440,442,445,447,450,453,455,457],{"class":220,"line":407},10,[218,409,410],{"class":299},"  total",[218,412,312],{"class":224},[218,414,352],{"class":248},[218,416,355],{"class":228},[218,418,358],{"class":224},[218,420,361],{"class":327},[218,422,364],{"class":228},[218,424,367],{"class":248},[218,426,370],{"class":228},[218,428,373],{"class":248},[218,430,376],{"class":228},[218,432,433],{"class":299},"s",[218,435,382],{"class":228},[218,437,385],{"class":299},[218,439,388],{"class":228},[218,441,358],{"class":224},[218,443,444],{"class":228}," s ",[218,446,396],{"class":224},[218,448,449],{"class":228}," i.price ",[218,451,452],{"class":224},"*",[218,454,399],{"class":228},[218,456,60],{"class":327},[218,458,404],{"class":228},[218,460,462],{"class":220,"line":461},11,[218,463,464],{"class":228},"}\n",[218,466,468],{"class":220,"line":467},12,[218,469,471],{"class":470},"sjfSM","\u002F\u002F trade-off: signals require updating state through set\u002Fupdate instead of direct\n",[218,473,475],{"class":220,"line":474},13,[218,476,477],{"class":470},"\u002F\u002F mutation; the explicitness is what lets Angular update only affected views.\n",[204,479,481],{"id":480},"_2-make-all-components-onpush","2. Make all components OnPush",[14,483,484,486],{},[32,485,42],{}," surfaces places where views depend on implicit updates; fix them before going zoneless.",[204,488,490],{"id":489},"_3-replace-implicit-updates","3. Replace implicit updates",[14,492,493,494,496,497,364],{},"Where code updates plain properties in callbacks (timers, subscriptions, third-party events), switch to signals, use the ",[32,495,38],{}," pipe, or call ",[32,498,499],{},"ChangeDetectorRef.markForCheck()",[204,501,503],{"id":502},"_4-enable-zoneless-and-remove-zonejs","4. Enable zoneless and remove zone.js",[14,505,506,507,510,511,514],{},"Add the zoneless provider (its name depends on the Angular version), remove ",[32,508,509],{},"zone.js"," from polyfills in ",[32,512,513],{},"angular.json",", and run tests and manual checks.",[14,516,517],{},[47,518,54,521,54,524,54,527,54,529,54,546,54,549,54,551,54,557,54,564,54,569,54,572,54,575,54,578,54,581,54,585,54,588,54,591,54,595,54,599,54,602,54,604,54,607,54,611,54,614,54,622,54,626,54,630,54],{"viewBox":519,"width":50,"role":51,"ariaLabel":520,"style":53},"0 0 760 149","Steps for migrating an Angular application to zoneless change detection.",[56,522],{"className":523,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,525,526],{},"Zoneless migration path",[67,528,520],{},[530,531,532],"defs",{},[533,534,541],"marker",{"id":535,"viewBox":536,"refX":537,"refY":538,"markerWidth":539,"markerHeight":539,"orient":540},"fac3feb07a","0 0 10 10","9","5","7","auto-start-reverse",[542,543],"path",{"d":544,"fill":76,"style":545},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[56,547],{"x":71,"y":71,"width":72,"height":548,"rx":74,"fill":75,"stroke":76,"style":77},"147",[79,550,526],{"x":81,"y":82,"fill":76,"style":83},[56,552],{"x":81,"y":94,"width":553,"height":554,"rx":555,"fill":112,"stroke":112,"style":556},"108.8","71.0","6","fill-opacity:0.14;stroke-opacity:0.9",[79,558,563],{"x":559,"y":560,"fill":76,"style":561,"textAnchor":562},"82.4","78.0","font-size:13px;font-weight:700","middle","Signals",[79,565,568],{"x":559,"y":566,"fill":76,"style":567,"textAnchor":562},"96.0","font-size:12px","component state",[56,570],{"x":571,"y":94,"width":553,"height":554,"rx":555,"fill":112,"stroke":112,"style":556},"176.8",[79,573,42],{"x":574,"y":560,"fill":76,"style":561,"textAnchor":562},"231.2",[79,576,577],{"x":574,"y":566,"fill":76,"style":567,"textAnchor":562},"all components",[56,579],{"x":580,"y":94,"width":553,"height":554,"rx":555,"fill":112,"stroke":112,"style":556},"325.6",[79,582,584],{"x":583,"y":560,"fill":76,"style":561,"textAnchor":562},"380.0","Fix implicit",[79,586,587],{"x":583,"y":566,"fill":76,"style":567,"textAnchor":562},"markForCheck \u002F",[79,589,38],{"x":583,"y":590,"fill":76,"style":567,"textAnchor":562},"112.0",[56,592],{"x":593,"y":94,"width":553,"height":554,"rx":555,"fill":126,"stroke":127,"style":594},"474.4","fill-opacity:0.24;stroke-opacity:0.9",[79,596,598],{"x":597,"y":560,"fill":76,"style":561,"textAnchor":562},"528.8","Zoneless",[79,600,601],{"x":597,"y":566,"fill":76,"style":567,"textAnchor":562},"provider + remove",[79,603,509],{"x":597,"y":590,"fill":76,"style":567,"textAnchor":562},[56,605],{"x":606,"y":94,"width":553,"height":554,"rx":555,"fill":112,"stroke":112,"style":556},"623.2",[79,608,610],{"x":609,"y":560,"fill":76,"style":561,"textAnchor":562},"677.6","Verify",[79,612,613],{"x":609,"y":566,"fill":76,"style":567,"textAnchor":562},"tests + profiling",[220,615],{"x1":616,"y1":617,"x2":618,"y2":617,"stroke":76,"strokeWidth":619,"style":620,"markerEnd":621},"136.8","91.5","174.8","1.5","stroke-opacity:0.6","url(#fac3feb07a)",[220,623],{"x1":624,"y1":617,"x2":625,"y2":617,"stroke":76,"strokeWidth":619,"style":620,"markerEnd":621},"285.6","323.6",[220,627],{"x1":628,"y1":617,"x2":629,"y2":617,"stroke":76,"strokeWidth":619,"style":620,"markerEnd":621},"434.4","472.4",[220,631],{"x1":632,"y1":617,"x2":633,"y2":617,"stroke":76,"strokeWidth":619,"style":620,"markerEnd":621},"583.2","621.2",[134,635,637],{"id":636},"verification","Verification",[14,639,640],{},"Profile the same interactions with Angular DevTools: change detection cycles should be far fewer and shorter. Check bundle size: zone.js removal saves its code. Test flows that rely on timers, websockets and third-party callbacks to ensure views update. In the field, compare INP for key interactions.",[134,642,644],{"id":643},"worked-example-a-trading-dashboard","Worked Example: A Trading Dashboard",[14,646,647,648,650],{},"A trading dashboard received price updates over a websocket ten times per second and had scroll-linked analytics, a polling notifications service and a chart library with internal timers. With zone.js and mostly default change detection, the app ran about 60 change detection cycles per second, each 6–12ms, leaving little main-thread time for user input; INP p75 was 380ms. The team converted price state to signals, made all components ",[32,649,42],{},", moved chart and analytics callbacks outside the zone as a first step, and then went zoneless. Change detection dropped to a handful of cycles per second, limited to the price cells that changed. INP p75 fell to 110ms, and CPU usage on low-end laptops dropped by half.",[134,652,654],{"id":653},"testing-strategy-for-zoneless","Testing Strategy for Zoneless",[14,656,657,658,661,662,382,665,668,669,672],{},"Unit tests that relied on ",[32,659,660],{},"fixture.detectChanges()"," and zone-based async helpers (",[32,663,664],{},"fakeAsync",[32,666,667],{},"waitForAsync",") may need updates: in zoneless tests, use ",[32,670,671],{},"await fixture.whenStable()"," and make sure state changes go through signals. End-to-end tests are the best safety net, because they catch views that no longer update after the switch. Run them against a zoneless build before release, paying attention to flows driven by timers, websockets and third-party widgets.",[14,674,675],{},[47,676,54,679,54,682,54,685,54,687,54,690,54,692,54,697,54,704,54,708,54,712,54,715,54,719,54,723,54,727,54,729,54,732,54,734,54,737,54,740,54,744,54,746,54,748,54,750,54,753,54,757,54,761,54,765,54,767,54,770,54,772,54,775,54,778,54,782,54,784,54,787,54,789,54],{"viewBox":677,"width":50,"role":51,"ariaLabel":678,"style":53},"0 0 760 244","Code patterns that rely on zone.js and their zoneless-compatible replacements.",[56,680],{"className":681,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,683,684],{},"Updating patterns for zoneless",[67,686,678],{},[56,688],{"x":71,"y":71,"width":72,"height":689,"rx":74,"fill":75,"stroke":76,"style":77},"242",[79,691,684],{"x":81,"y":82,"fill":76,"style":83},[56,693],{"x":81,"y":94,"width":694,"height":695,"rx":60,"fill":76,"stroke":76,"style":696},"210.0","30.0","fill-opacity:0.06;stroke-opacity:0.4",[79,698,703],{"x":699,"y":700,"fill":76,"style":701,"textAnchor":702},"38.0","75.5","font-size:12.5px;font-weight:700","start","Pattern with zone.js",[56,705],{"x":706,"y":94,"width":707,"height":695,"rx":60,"fill":76,"stroke":76,"style":696},"238.0","247.0",[79,709,711],{"x":710,"y":700,"fill":76,"style":701,"textAnchor":562},"361.5","Problem without zones",[56,713],{"x":714,"y":94,"width":707,"height":695,"rx":60,"fill":76,"stroke":76,"style":696},"485.0",[79,716,718],{"x":717,"y":700,"fill":76,"style":701,"textAnchor":562},"608.5","Replacement",[56,720],{"x":81,"y":721,"width":694,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},"86.0","stroke-opacity:0.35",[79,724,726],{"x":699,"y":725,"fill":76,"style":701,"textAnchor":702},"105.5","this.value = x in setTimeout",[56,728],{"x":706,"y":721,"width":707,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},[79,730,731],{"x":710,"y":725,"fill":76,"style":567,"textAnchor":562},"view not refreshed",[56,733],{"x":714,"y":721,"width":707,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},[79,735,736],{"x":717,"y":725,"fill":76,"style":567,"textAnchor":562},"signal.set(x)",[56,738],{"x":81,"y":739,"width":694,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},"116.0",[79,741,743],{"x":699,"y":742,"fill":76,"style":701,"textAnchor":702},"135.5","Subscription updating a field",[56,745],{"x":706,"y":739,"width":707,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},[79,747,731],{"x":710,"y":742,"fill":76,"style":567,"textAnchor":562},[56,749],{"x":714,"y":739,"width":707,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},[79,751,752],{"x":717,"y":742,"fill":76,"style":567,"textAnchor":562},"async pipe or toSignal",[56,754],{"x":81,"y":755,"width":694,"height":756,"rx":60,"fill":75,"stroke":76,"style":722},"146.0","46.0",[79,758,760],{"x":699,"y":759,"fill":76,"style":701,"textAnchor":702},"165.5","Third-party callback mutating",[79,762,764],{"x":699,"y":763,"fill":76,"style":701,"textAnchor":702},"181.5","state",[56,766],{"x":706,"y":755,"width":707,"height":756,"rx":60,"fill":75,"stroke":76,"style":722},[79,768,731],{"x":710,"y":769,"fill":76,"style":567,"textAnchor":562},"173.5",[56,771],{"x":714,"y":755,"width":707,"height":756,"rx":60,"fill":75,"stroke":76,"style":722},[79,773,774],{"x":717,"y":769,"fill":76,"style":567,"textAnchor":562},"signal or markForCheck",[56,776],{"x":81,"y":777,"width":694,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},"192.0",[79,779,781],{"x":699,"y":780,"fill":76,"style":701,"textAnchor":702},"211.5","NgZone.onStable waits",[56,783],{"x":706,"y":777,"width":707,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},[79,785,786],{"x":710,"y":780,"fill":76,"style":567,"textAnchor":562},"never fires",[56,788],{"x":714,"y":777,"width":707,"height":695,"rx":60,"fill":75,"stroke":76,"style":722},[79,790,791],{"x":717,"y":780,"fill":76,"style":567,"textAnchor":562},"afterNextRender",[134,793,795],{"id":794},"auditing-code-before-the-switch","Auditing Code Before the Switch",[14,797,798,799,802,803,806,807,810,811,814,815,817,818,821,822,382,825,828,829,832,833,835,836,838,839,841],{},"Before enabling zoneless, find code that depends on zone.js. Search for property assignments inside ",[32,800,801],{},"subscribe"," callbacks, ",[32,804,805],{},"setTimeout","\u002F",[32,808,809],{},"setInterval"," callbacks, promise ",[32,812,813],{},"then"," handlers and third-party library event handlers, where the component is not ",[32,816,42],{}," with signals. Each is a place where the view might stop updating. Also search for ",[32,819,820],{},"NgZone"," usage: ",[32,823,824],{},"onStable",[32,826,827],{},"onMicrotaskEmpty"," and ",[32,830,831],{},"run"," calls usually indicate zone-dependent logic that needs a different approach (",[32,834,791],{},", signals or explicit ",[32,837,34],{},"). Running the app with all components set to ",[32,840,42],{}," for a while before going zoneless surfaces most of these issues in a safer setting.",[134,843,845],{"id":844},"measuring-the-gain","Measuring the Gain",[14,847,848],{},"Record the same user flows before and after with the Angular DevTools profiler: the number of change detection cycles and their total duration. In the browser Performance panel, compare main-thread time during typical interactions and idle periods (zone.js-triggered cycles from timers and polling often run even when users are idle). In the field, compare INP p75 for the routes that changed, and check the bundle size difference from removing zone.js.",[134,850,852],{"id":851},"common-mistakes","Common Mistakes",[139,854,855,861,867,873],{},[142,856,857,860],{},[145,858,859],{},"Switching before migrating state."," Views stop updating.",[142,862,863,866],{},[145,864,865],{},"Leaving default change detection."," Hides implicit dependencies until zoneless breaks them.",[142,868,869,872],{},[145,870,871],{},"Forgetting third-party callbacks."," UI stops reflecting library events.",[142,874,875,878,879,364],{},[145,876,877],{},"Relying on NgZone.onStable."," It does not fire without zones; use ",[32,880,791],{},[134,882,884],{"id":883},"edge-cases","Edge Cases",[14,886,887,890],{},[145,888,889],{},"Libraries requiring zone.js."," Some older libraries assume zones; check compatibility or keep zone.js until replaced.",[14,892,893,896],{},[145,894,895],{},"Hybrid apps."," Partial migration is possible with OnPush and signals while zone.js remains; go zoneless at the end.",[14,898,899,902],{},[145,900,901],{},"SSR."," Zoneless works with SSR; ensure the app becomes stable for rendering by using pending tasks for async work.",[14,904,905,908],{},[145,906,907],{},"Forms."," Reactive forms work zoneless; ensure value changes that affect views go through signals or the async pipe.",[134,910,912],{"id":911},"faq","FAQ",[914,915,918,922],"details",{"className":916},[917],"faq-item",[919,920,921],"summary",{},"What does zone.js do in Angular?",[14,923,924],{},"It patches browser async APIs so Angular knows when async work completes and can run change detection automatically.",[914,926,928,931],{"className":927},[917],[919,929,930],{},"What are the benefits of zoneless?",[14,932,933],{},"Fewer and more targeted change detection cycles, smaller bundles, simpler stack traces and better interoperability with other code.",[914,935,937,940],{"className":936},[917],[919,938,939],{},"Do I need signals to go zoneless?",[14,941,942,943,945],{},"Signals are the most natural fit. The async pipe and explicit ",[32,944,34],{}," also notify Angular of changes.",[914,947,949,952],{"className":948},[917],[919,950,951],{},"Is zoneless stable?",[14,953,954],{},"It has been progressing from experimental to stable across Angular versions; check your version's documentation for its status and provider name.",[914,956,958,961],{"className":957},[917],[919,959,960],{},"How much faster is zoneless?",[14,962,963],{},"It depends on how many unnecessary cycles zone.js caused. Apps with frequent async activity and large trees see the largest improvements.",[914,965,967,970],{"className":966},[917],[919,968,969],{},"Can I go zoneless incrementally?",[14,971,972],{},"You can migrate components to signals and OnPush incrementally; the zoneless switch itself is app-wide.",[914,974,976,979],{"className":975},[917],[919,977,978],{},"What replaces NgZone.runOutsideAngular?",[14,980,981],{},"Without zones, it is not needed: async work does not trigger change detection unless it updates signals or marks views.",[914,983,985,988],{"className":984},[917],[919,986,987],{},"Does zoneless affect hydration?",[14,989,990],{},"Hydration works with zoneless. Make sure the app signals when it is stable (pending tasks), so SSR waits for async work correctly.",[914,992,994,997],{"className":993},[917],[919,995,996],{},"Does zoneless reduce bundle size?",[14,998,999],{},"Yes, by removing zone.js from the polyfills. The exact saving depends on the version, but it is typically tens of kilobytes uncompressed.",[914,1001,1003,1006],{"className":1002},[917],[919,1004,1005],{},"Do I still need OnPush with zoneless?",[14,1007,1008],{},"OnPush remains a good default: it limits which components are checked when change detection runs, complementing signal-driven scheduling.",[914,1010,1012,1015],{"className":1011},[917],[919,1013,1014],{},"Can libraries still use timers?",[14,1016,1017],{},"Yes. Without zones, their timers simply do not trigger change detection unless they update signals or mark views.",[134,1019,1021],{"id":1020},"related","Related",[139,1023,1024,1030,1037],{},[142,1025,1026,1029],{},[18,1027,1028],{"href":20},"Angular performance"," — the topic overview.",[142,1031,1032,1036],{},[18,1033,1035],{"href":1034},"\u002Fframework-performance\u002Fangular-performance\u002Fdeferring-components-with-angular-defer-blocks\u002F","Deferring components with Angular @defer blocks"," — smaller initial bundles.",[142,1038,1039,1043],{},[18,1040,1042],{"href":1041},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002F","Cutting Vue reactivity cost with shallowRef"," — reactivity trade-offs in Vue.",[1045,1046,1048],"script",{"type":1047},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Going Zoneless in Angular\",\n  \"description\": \"A migration guide to zoneless change detection in Angular, covering prerequisites, common breakages and how to verify the performance improvement.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Move component state to signals\",\n      \"text\": \"Move component state to signals\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Make all components OnPush\",\n      \"text\": \"OnPush surfaces places where views depend on implicit updates; fix them before going zoneless.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Replace implicit updates\",\n      \"text\": \"Where code updates plain properties in callbacks (timers, subscriptions, third-party events), switch to signals, use the async pipe, or call ChangeDetectorRef.markForCheck().\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Enable zoneless and remove zone.js\",\n      \"text\": \"Add the zoneless provider (its name depends on the Angular version), remove zone.js from polyfills in angular.json, and run tests and manual checks.\"\n    }\n  ]\n}\n",[1045,1050,1051],{"type":1047},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Going Zoneless in Angular\",\n  \"description\": \"A migration guide to zoneless change detection in Angular, covering prerequisites, common breakages and how to verify the performance improvement.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular\u002F\"\n  }\n}\n",[1045,1053,1054],{"type":1047},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Framework Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Angular Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002Fangular-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Going Zoneless in Angular\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular\u002F\"\n    }\n  ]\n}\n",[1056,1057,1058],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}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 .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);}",{"title":214,"searchDepth":242,"depth":242,"links":1060},[1061,1062,1063,1069,1070,1071,1072,1073,1074,1075,1076,1077],{"id":136,"depth":242,"text":137},{"id":169,"depth":242,"text":170},{"id":201,"depth":242,"text":202,"children":1064},[1065,1066,1067,1068],{"id":206,"depth":255,"text":207},{"id":480,"depth":255,"text":481},{"id":489,"depth":255,"text":490},{"id":502,"depth":255,"text":503},{"id":636,"depth":242,"text":637},{"id":643,"depth":242,"text":644},{"id":653,"depth":242,"text":654},{"id":794,"depth":242,"text":795},{"id":844,"depth":242,"text":845},{"id":851,"depth":242,"text":852},{"id":883,"depth":242,"text":884},{"id":911,"depth":242,"text":912},{"id":1020,"depth":242,"text":1021},"A migration guide to zoneless change detection in Angular, covering prerequisites, common breakages and how to verify the performance improvement.","md",{"slug":12,"type":1081,"breadcrumb":1082,"datePublished":1089,"dateModified":1089},"article",[1083,1085,1086,1087],{"name":1084,"url":806},"Home",{"name":26,"url":25},{"name":21,"url":20},{"name":5,"url":1088},"\u002Fframework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular\u002F","2026-10-06",true,"\u002Fframework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular",{"title":1093,"description":1094},"Going Zoneless in Angular for Faster Change Detection","Remove zone.js from Angular: migrate state to signals, use OnPush, fix code relying on implicit change detection, and measure the INP and bundle gains.","framework-performance\u002Fangular-performance\u002Fgoing-zoneless-in-angular\u002Findex","MKr2yO0kxc59waKN_EFO0FTLiMG5TZ5dRcbO1omhPZk",[1098,1102],{"title":1099,"path":1100,"stem":1101,"children":-1},"Deferring Components with Angular @defer Blocks","\u002Fframework-performance\u002Fangular-performance\u002Fdeferring-components-with-angular-defer-blocks","framework-performance\u002Fangular-performance\u002Fdeferring-components-with-angular-defer-blocks\u002Findex",{"title":1103,"path":1104,"stem":1105,"children":-1},"Astro & SvelteKit Performance","\u002Fframework-performance\u002Fastro-and-sveltekit-performance","framework-performance\u002Fastro-and-sveltekit-performance\u002Findex",1791308074711]