[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"categories-en":3,"post:\u002Fpost\u002Fangular-22-replace-controlvalueaccessor-with-signal-forms":37},[4,11,17,24,30],{"id":5,"slug":6,"name":7,"colorHex":8,"iconName":9,"postCount":10},"0e2c1225-f78a-4c20-8c93-998ab9dc93b9","frontend","Frontend","#40484f",null,25,{"id":12,"slug":13,"name":14,"colorHex":15,"iconName":9,"postCount":16},"bc8df70e-b7fb-4d89-a80c-fd458d36d58c","code-development","Code & Development","#8b5cf6",23,{"id":18,"slug":19,"name":20,"colorHex":21,"iconName":22,"postCount":23},"550e8400-e29b-41d4-a716-446655440001","inteligencia-artificial","Artificial Intelligence","#6366f1","brain",14,{"id":25,"slug":26,"name":27,"colorHex":28,"iconName":9,"postCount":29},"ea63b69c-2eb4-4100-81f3-70ce52d33743","tech-news","Tech News","#e50606",4,{"id":31,"slug":32,"name":33,"colorHex":34,"iconName":35,"postCount":36},"550e8400-e29b-41d4-a716-446655440017","how-to-guides","How-To & Guides","#be123c","book-open",2,{"post":38,"related":1875,"translations":1946,"resolved":9},{"id":39,"slug":40,"title":41,"excerpt":42,"image":43,"featured":44,"groupId":45,"publishedAt":46,"readingTime":47,"views":48,"author":49,"category":51,"tags":52,"contentJson":59,"metaTitle":41,"metaDescription":1873,"updatedAt":1874},"42fb3430-7877-4a2c-9e52-b1cd46af1f14","angular-22-replace-controlvalueaccessor-with-signal-forms","Angular 22: Replace ControlValueAccessor with Signal Forms","Discover how Angular 22 Signal Forms streamline form development, allowing you to build custom controls and reactive state with minimal boilerplate.","https:\u002F\u002Fmfgiomkkbihnlpfdxwrt.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fblog-assets\u002Fmedia\u002Ffeatured\u002F1783824967158-featured-pxdvca.webp",false,"63d1d8ce-b076-4932-955c-91714cb80417","2026-07-12T02:59:02.581763+00:00",13,0,{"name":50,"avatar":9},"Genildo Souza",{"slug":13,"name":14,"color":15},[53,56],{"slug":54,"name":55},"angular","Angular",{"slug":57,"name":58},"typescript","TypeScript",{"type":60,"content":61},"doc",[62,73,81,107,113,136,164,176,178,184,190,199,217,225,242,250,261,271,300,301,307,318,326,363,364,370,376,382,389,395,402,424,443,444,450,467,474,492,498,499,505,516,523,524,530,916,917,923,938,944,1043,1049,1078,1101,1111,1133,1140,1173,1229,1230,1236,1242,1339,1417,1423,1482,1483,1489,1495,1501,1511,1535,1551,1595,1603,1604,1610,1856,1857],{"type":63,"attrs":64},"summaryBox",{"id":65,"items":66,"title":72},"sum_j40j0uwd",[67,68,69,70,71],"Signal Forms replace the complex ControlValueAccessor pattern with a simpler, signal-based API for custom form controls.","The new form() function creates a reactive FieldTree that mirrors your data model, providing automatic validation and state tracking.","Custom components now only need to expose a model signal to integrate with the form system, eliminating the need for boilerplate providers.","Signal Forms are fully compatible with existing Reactive Forms, allowing for incremental migration within your application.","The model signal acts as the single source of truth, removing the need for manual data extraction or complex subscription management.","Angular 22 Signal Forms Overview",{"type":74,"attrs":75,"content":77},"heading",{"id":76,"level":36},"h_xwghzvic",[78],{"text":79,"type":80},"The Problem Signal Forms Solve","text",{"type":82,"attrs":83,"content":85},"paragraph",{"id":84},"p_6jnz7ozn",[86,88,93,95,99,101,105],{"text":87,"type":80},"There is a specific moment every Angular developer knows. You need a custom input component — a date picker, a rating widget, a rich-text field. And you know what's coming: providers array, ",{"text":89,"type":80,"marks":90},"forwardRef",[91],{"type":92},"code",{"text":94,"type":80},", four interface methods, manual ",{"text":96,"type":80,"marks":97},"onChange",[98],{"type":92},{"text":100,"type":80}," and ",{"text":102,"type":80,"marks":103},"onTouched",[104],{"type":92},{"text":106,"type":80}," callbacks. Every time. For every control.",{"type":82,"attrs":108,"content":110},{"id":109},"p_e8mb4hcy",[111],{"text":112,"type":80},"Signal Forms don't simplify this. They replace it entirely.",{"type":82,"attrs":114,"content":116},{"id":115},"p_wqyzt04h",[117,119,123,124,128,130,134],{"text":118,"type":80},"The second pain point is state management. In Reactive Forms, the form state lives in ",{"text":120,"type":80,"marks":121},"FormControl",[122],{"type":92},{"text":100,"type":80},{"text":125,"type":80,"marks":126},"FormGroup",[127],{"type":92},{"text":129,"type":80}," — Observable-based, imperative to update, fragile to type correctly with nested structures. Watching a field change means subscribing to ",{"text":131,"type":80,"marks":132},"valueChanges",[133],{"type":92},{"text":135,"type":80},", managing the subscription, unsubscribing on destroy. Signal Forms make every piece of state a signal — readable, composable, and cleaned up automatically.",{"type":137,"attrs":138,"content":140},"blockquote",{"id":139},"quote_ol9q2wmu",[141],{"type":82,"attrs":142,"content":144},{"id":143},"p_3qm3ulv7",[145,150,152,156,158,162],{"text":146,"type":80,"marks":147},"⚠️ Important:",[148],{"type":149},"bold",{"text":151,"type":80}," ",{"text":153,"type":80,"marks":154},"ControlValueAccessor",[155],{"type":92},{"text":157,"type":80}," is ",{"text":159,"type":80,"marks":160},"not deprecated",[161],{"type":149},{"text":163,"type":80}," in Angular 22. It still works, and existing controls still compile. But for every new control, Signal Forms are the way forward. Angular 22 guarantees full compatibility between the two worlds.",{"type":137,"attrs":165,"content":167},{"id":166},"quote_hrodg9ba",[168],{"type":82,"attrs":169,"content":171},{"id":170},"p_0dew0l2h",[172],{"text":173,"type":80,"marks":174},"You don't have to migrate everything. You just have to stop writing the old way for anything new.",[175],{"type":149},{"type":177},"horizontalRule",{"type":74,"attrs":179,"content":181},{"id":180,"level":36},"h_lf72emrz",[182],{"text":183,"type":80},"The New API: Three Primitives",{"type":82,"attrs":185,"content":187},{"id":186},"p_vjoge2ie",[188],{"text":189,"type":80},"Learn these three and you've learned Signal Forms.",{"type":74,"attrs":191,"content":194},{"id":192,"level":193},"h_ziaopiie",3,[195],{"text":196,"type":80,"marks":197},"form()",[198],{"type":92},{"type":82,"attrs":200,"content":202},{"id":201},"p_gv3ox1sm",[203,205,209,211,215],{"text":204,"type":80},"The entry point. Pass a signal (or ",{"text":206,"type":80,"marks":207},"linkedSignal",[208],{"type":92},{"text":210,"type":80},") holding your model, and a validation function. Returns a typed ",{"text":212,"type":80,"marks":213},"FieldTree",[214],{"type":92},{"text":216,"type":80}," — a mirror of your model shape where every leaf is a reactive field with value, validity, dirty, touched, and pending state.",{"type":74,"attrs":218,"content":220},{"id":219,"level":193},"h_sgxd0sl9",[221],{"text":222,"type":80,"marks":223},"FormValueControl\u003CT>",[224],{"type":92},{"type":82,"attrs":226,"content":228},{"id":227},"p_o0neqk9s",[229,231,234,236,240],{"text":230,"type":80},"The interface your custom component implements instead of ",{"text":153,"type":80,"marks":232},[233],{"type":92},{"text":235,"type":80},". Minimum requirement: expose a ",{"text":237,"type":80,"marks":238},"value = model\u003CT>()",[239],{"type":92},{"text":241,"type":80}," signal. The directive wires everything else — syncs the field's value signal, propagates touched\u002Fdirty, handles disabled state.",{"type":74,"attrs":243,"content":245},{"id":244,"level":193},"h_tyg4k3fg",[246],{"text":247,"type":80,"marks":248},"SignalFormControl",[249],{"type":92},{"type":82,"attrs":251,"content":253},{"id":252},"p_6k082jae",[254,256,259],{"text":255,"type":80},"A bridge for incremental migration. Drop a signal-based control into an existing ",{"text":125,"type":80,"marks":257},[258],{"type":92},{"text":260,"type":80}," without rewriting the parent form. The validation errors and state propagate both ways — signal form into the reactive form tree, and vice versa.",{"type":74,"attrs":262,"content":264},{"id":263,"level":193},"h_9iyoduh2",[265,269],{"text":266,"type":80,"marks":267},"[formField]",[268],{"type":92},{"text":270,"type":80}," directive",{"type":82,"attrs":272,"content":274},{"id":273},"p_udrwxxds",[275,277,281,283,287,289,293,294,298],{"text":276,"type":80},"The template binding directive. Apply it to any element alongside ",{"text":278,"type":80,"marks":279},"ngModel",[280],{"type":92},{"text":282,"type":80}," and it wires the form field state. Sets CSS classes (",{"text":284,"type":80,"marks":285},".ng-valid",[286],{"type":92},{"text":288,"type":80},", ",{"text":290,"type":80,"marks":291},".ng-dirty",[292],{"type":92},{"text":288,"type":80},{"text":295,"type":80,"marks":296},".ng-touched",[297],{"type":92},{"text":299,"type":80},"), manages ARIA attributes, and keeps value in sync automatically.",{"type":177},{"type":74,"attrs":302,"content":304},{"id":303,"level":36},"h_z5hz4ss7",[305],{"text":306,"type":80},"Building a Form with Validation",{"type":82,"attrs":308,"content":310},{"id":309},"p_u06r3dac",[311,313,316],{"text":312,"type":80},"The ",{"text":196,"type":80,"marks":314},[315],{"type":92},{"text":317,"type":80}," function takes your model signal and a schema function. Validators run on the field value — no more passing validator arrays and hoping the types line up.",{"type":319,"attrs":320,"content":323},"codeBlock",{"id":321,"language":57,"highlighted":322},"code_ebrewjqg","\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { Component, inject, signal }           \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fcore'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { form, required, minLength, pattern }  \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fforms\u002Fsignals'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { UserService }                          \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '.\u002Fuser.service'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">interface\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> RegisterModel\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  username\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  email\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  password\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">Component\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  selector: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">'app-register'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  standalone: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  imports: [SignalFormsModule],\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  template: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">`\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">    &#x3C;form (ngSubmit)=\"submit()\">\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;label>Username\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;input [formField]=\"f.username\" \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        @if (f.username().errors()?.required) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">          &#x3C;span class=\"error\">Required&#x3C;\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        @if (f.username().errors()?.minLength) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">          &#x3C;span class=\"error\">Min 3 characters&#x3C;\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        @if (f.username().pending()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">          &#x3C;span class=\"info\">Checking availability…&#x3C;\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;\u002Flabel>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;label>Email\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;input type=\"email\" [formField]=\"f.email\" \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        @if (f.email().errors()?.pattern) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">          &#x3C;span class=\"error\">Invalid email format&#x3C;\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;\u002Flabel>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;label>Password\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;input type=\"password\" [formField]=\"f.password\" \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;\u002Flabel>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;button type=\"submit\" [disabled]=\"!f.valid()\">Register&#x3C;\u002Fbutton>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">    &#x3C;\u002Fform>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">  `\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">})\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> RegisterComponent\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  private\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\"> userSvc\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> inject\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(UserService);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F Model as a writable signal\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  private\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\"> model\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> signal\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">RegisterModel\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">>({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    username: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    email:    \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    password: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  });\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F form() generates the FieldTree — typed to RegisterModel\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  f\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> form\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.model, (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">field\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.username);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    minLength\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.username, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F Async validator — checks username availability\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    field.username.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">addAsyncValidator\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">async\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">value\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">      if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">value) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">      const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> taken\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> await\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.userSvc.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">isUsernameTaken\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(value);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">      return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> taken \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">?\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { usernameTaken: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> } \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    }, { debounce: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">400\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> }); \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F built-in debounce — no more timer juggling\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.email);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    pattern\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.email,\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> \u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">^\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">[\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">^\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">\\s@]\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#DBEDFF\">@\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">[\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">^\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">\\s@]\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold\">\\.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">[\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">^\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">\\s@]\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">+$\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">\u002F\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.password);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    minLength\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.password, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">8\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  });\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  submit\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.f.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">valid\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    console.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">log\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">model\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()); \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F typed RegisterModel — no .getRawValue()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",[324],{"text":325,"type":80},"import { Component, inject, signal }           from '@angular\u002Fcore';\nimport { form, required, minLength, pattern }  from '@angular\u002Fforms\u002Fsignals';\nimport { UserService }                          from '.\u002Fuser.service';\n\ninterface RegisterModel {\n  username: string;\n  email: string;\n  password: string;\n}\n\n@Component({\n  selector: 'app-register',\n  standalone: true,\n  imports: [SignalFormsModule],\n  template: `\n    \u003Cform (ngSubmit)=\"submit()\">\n\n      \u003Clabel>Username\n        \u003Cinput [formField]=\"f.username\" \u002F>\n        @if (f.username().errors()?.required) {\n          \u003Cspan class=\"error\">Required\u003C\u002Fspan>\n        }\n        @if (f.username().errors()?.minLength) {\n          \u003Cspan class=\"error\">Min 3 characters\u003C\u002Fspan>\n        }\n        @if (f.username().pending()) {\n          \u003Cspan class=\"info\">Checking availability…\u003C\u002Fspan>\n        }\n      \u003C\u002Flabel>\n\n      \u003Clabel>Email\n        \u003Cinput type=\"email\" [formField]=\"f.email\" \u002F>\n        @if (f.email().errors()?.pattern) {\n          \u003Cspan class=\"error\">Invalid email format\u003C\u002Fspan>\n        }\n      \u003C\u002Flabel>\n\n      \u003Clabel>Password\n        \u003Cinput type=\"password\" [formField]=\"f.password\" \u002F>\n      \u003C\u002Flabel>\n\n      \u003Cbutton type=\"submit\" [disabled]=\"!f.valid()\">Register\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  `,\n})\nexport class RegisterComponent {\n  private userSvc = inject(UserService);\n\n  \u002F\u002F Model as a writable signal\n  private model = signal\u003CRegisterModel>({\n    username: '',\n    email:    '',\n    password: '',\n  });\n\n  \u002F\u002F form() generates the FieldTree — typed to RegisterModel\n  f = form(this.model, (field) => {\n    required(field.username);\n    minLength(field.username, 3);\n\n    \u002F\u002F Async validator — checks username availability\n    field.username.addAsyncValidator(async (value) => {\n      if (!value) return null;\n      const taken = await this.userSvc.isUsernameTaken(value);\n      return taken ? { usernameTaken: true } : null;\n    }, { debounce: 400 }); \u002F\u002F built-in debounce — no more timer juggling\n\n    required(field.email);\n    pattern(field.email, \u002F^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\u002F);\n\n    required(field.password);\n    minLength(field.password, 8);\n  });\n\n  submit() {\n    if (!this.f.valid()) return;\n    console.log(this.model()); \u002F\u002F typed RegisterModel — no .getRawValue()\n  }\n}\n",{"type":137,"attrs":327,"content":329},{"id":328},"quote_5mb1bp5x",[330],{"type":82,"attrs":331,"content":333},{"id":332},"p_7xiu5a6d",[334,338,340,343,345,349,351,355,357,361],{"text":335,"type":80,"marks":336},"✅ Key insight:",[337],{"type":149},{"text":339,"type":80}," The model signal is the source of truth. ",{"text":196,"type":80,"marks":341},[342],{"type":92},{"text":344,"type":80}," does not own the data — your signal does. Reading ",{"text":346,"type":80,"marks":347},"this.model()",[348],{"type":92},{"text":350,"type":80}," gives you the current typed value, without ",{"text":352,"type":80,"marks":353},".getRawValue()",[354],{"type":92},{"text":356,"type":80},", without ",{"text":358,"type":80,"marks":359},".value",[360],{"type":92},{"text":362,"type":80},", without manual extraction. Signal Forms are a lens over your data, not a container for it.",{"type":177},{"type":74,"attrs":365,"content":367},{"id":366,"level":36},"h_iu3cueop",[368],{"text":369,"type":80},"Custom Controls: Before and After",{"type":82,"attrs":371,"content":373},{"id":372},"p_i4gbtq4x",[374],{"text":375,"type":80},"This is the biggest quality-of-life improvement in the entire release.",{"type":74,"attrs":377,"content":379},{"id":378,"level":193},"h_lf7q2zeg",[380],{"text":381,"type":80},"Before — ControlValueAccessor (~40 lines)",{"type":319,"attrs":383,"content":386},{"id":384,"language":57,"highlighted":385},"code_d4zudxgq","\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">Component\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  selector: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">'app-rating'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  providers: [{\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    provide: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">NG_VALUE_ACCESSOR\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    useExisting: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">forwardRef\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(() \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> RatingComponent),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    multi: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }],\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">})\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> RatingComponent\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> ControlValueAccessor\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  protected\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\"> value\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  protected\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\"> disabled\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> false\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  private\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> onChange\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">v\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> void\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> () \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {};\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">  private\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> onTouched\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> () \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> void\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">         =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> () \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {};\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  writeValue\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">v\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.value \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> v \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">??\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  registerOnChange\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">fn\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">v\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> void\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.onChange \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> fn;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  registerOnTouched\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">fn\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> () \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> void\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.onTouched \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> fn;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  setDisabledState\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">d\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> boolean\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.disabled \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> d;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  select\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">star\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.value \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> star;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">onChange\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(star);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">onTouched\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",[387],{"text":388,"type":80},"@Component({\n  selector: 'app-rating',\n  providers: [{\n    provide: NG_VALUE_ACCESSOR,\n    useExisting: forwardRef(() => RatingComponent),\n    multi: true,\n  }],\n})\nexport class RatingComponent implements ControlValueAccessor {\n\n  protected value    = 0;\n  protected disabled = false;\n\n  private onChange: (v: number) => void = () => {};\n  private onTouched: () => void         = () => {};\n\n  writeValue(v: number) {\n    this.value = v ?? 0;\n  }\n  registerOnChange(fn: (v: number) => void) {\n    this.onChange = fn;\n  }\n  registerOnTouched(fn: () => void) {\n    this.onTouched = fn;\n  }\n  setDisabledState(d: boolean) {\n    this.disabled = d;\n  }\n\n  select(star: number) {\n    this.value = star;\n    this.onChange(star);\n    this.onTouched();\n  }\n}\n",{"type":74,"attrs":390,"content":392},{"id":391,"level":193},"h_4esse07g",[393],{"text":394,"type":80},"After — FormValueControl (3 lines)",{"type":319,"attrs":396,"content":399},{"id":397,"language":57,"highlighted":398},"code_niawx0ze","\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { model, Component }  \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fcore'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { FormValueControl }  \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fforms\u002Fsignals'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">Component\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  selector: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">'app-rating'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  standalone: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">})\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> RatingComponent\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> FormValueControl\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F This single signal is the entire API\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  value\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> model\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F Optional inputs — Signal Forms wires them automatically\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F disabled?: InputSignal&#x3C;boolean>;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F touched?:  InputSignal&#x3C;boolean>;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F name?:     InputSignal&#x3C;string>;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  select\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">star\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.value.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">set\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(star);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F No manual onChange\u002FonTouched calls needed\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F The directive handles all propagation\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F Usage in template — identical to before:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F &#x3C;app-rating [formField]=\"f.rating\" \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",[400],{"text":401,"type":80},"import { model, Component }  from '@angular\u002Fcore';\nimport { FormValueControl }  from '@angular\u002Fforms\u002Fsignals';\n\n@Component({\n  selector: 'app-rating',\n  standalone: true,\n})\nexport class RatingComponent implements FormValueControl\u003Cnumber> {\n\n  \u002F\u002F This single signal is the entire API\n  value = model(0);\n\n  \u002F\u002F Optional inputs — Signal Forms wires them automatically\n  \u002F\u002F disabled?: InputSignal\u003Cboolean>;\n  \u002F\u002F touched?:  InputSignal\u003Cboolean>;\n  \u002F\u002F name?:     InputSignal\u003Cstring>;\n\n  select(star: number) {\n    this.value.set(star);\n    \u002F\u002F No manual onChange\u002FonTouched calls needed\n    \u002F\u002F The directive handles all propagation\n  }\n}\n\n\u002F\u002F Usage in template — identical to before:\n\u002F\u002F \u003Capp-rating [formField]=\"f.rating\" \u002F>\n",{"type":82,"attrs":403,"content":405},{"id":404},"p_gjckweda",[406,408,411,413,417,419,422],{"text":407,"type":80},"The component contract went from 4 mandatory methods + a providers array + a ",{"text":89,"type":80,"marks":409},[410],{"type":92},{"text":412,"type":80}," call to ",{"text":414,"type":80,"marks":415},"one signal",[416],{"type":149},{"text":418,"type":80},". That's it. Everything else — synchronization, dirty tracking, touched propagation, disabled state — is handled by the ",{"text":266,"type":80,"marks":420},[421],{"type":92},{"text":423,"type":80}," directive automatically.",{"type":425,"attrs":426,"content":430},"callout",{"id":427,"type":428,"title":429},"call_1wqf49xl","note","Simplified Custom Component Contracts with Signal Forms",[431,437],{"type":82,"attrs":432,"content":434},{"id":433},"p_ko7w0vnv",[435],{"text":436,"type":80},"The traditional contract requiring 4 mandatory methods, a providers array, and a forwardRef call has been replaced by a single signal property in custom components. This drastically reduces boilerplate and complexity. ",{"type":82,"attrs":438,"content":440},{"id":439},"p_wf01d80x",[441],{"text":442,"type":80},"All synchronization tasks—such as dirty tracking, touched state propagation, and disabled state handling—are now automatically managed by the formField directive, enhancing developer experience and code clarity.",{"type":177},{"type":74,"attrs":445,"content":447},{"id":446,"level":36},"h_smhmxe11",[448],{"text":449,"type":80},"Editing Server Data: linkedSignal + httpResource",{"type":82,"attrs":451,"content":453},{"id":452},"p_wa8ddf7d",[454,456,460,462,465],{"text":455,"type":80},"The most powerful pattern in Signal Forms: load data via ",{"text":457,"type":80,"marks":458},"httpResource",[459],{"type":92},{"text":461,"type":80},", bridge it into the form via ",{"text":206,"type":80,"marks":463},[464],{"type":92},{"text":466,"type":80},", and the form updates automatically whenever the resource reloads.",{"type":319,"attrs":468,"content":471},{"id":469,"language":57,"highlighted":470},"code_zfq9okh1","\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { Component, inject, input }           \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fcore'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { httpResource }                        \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fcommon\u002Fhttp'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { linkedSignal }                        \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fcore'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { form, required, minLength }           \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fforms\u002Fsignals'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">interface\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> UserProfile\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  username\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  bio\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">      string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  website\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">  string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">Component\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  selector: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">'app-edit-profile'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  standalone: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  template: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">`\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">    @if (userResource.isLoading()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;p>Loading profile...&#x3C;\u002Fp>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">    } @else {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;form (ngSubmit)=\"save()\">\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;input [formField]=\"f.username\" placeholder=\"Username\" \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;textarea [formField]=\"f.bio\" placeholder=\"Bio\">&#x3C;\u002Ftextarea>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;input [formField]=\"f.website\" placeholder=\"Website\" \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;button [disabled]=\"!f.valid() || !f.dirty()\">Save changes&#x3C;\u002Fbutton>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">        &#x3C;button type=\"button\" (click)=\"reset()\">Discard&#x3C;\u002Fbutton>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">      &#x3C;\u002Fform>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">  `\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">})\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">export\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> EditProfileComponent\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  userId\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> input.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">number\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F 1. Fetch from server — refetches when userId changes\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  userResource\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> httpResource\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">UserProfile\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">>(\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    () \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> `\u002Fapi\u002Fusers\u002F${\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">userId\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">()\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">}`\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  );\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F 2. Bridge resource → writable signal via linkedSignal\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F    Updates automatically when userResource reloads\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  editableProfile\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> linkedSignal\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    () \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.userResource.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">??\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { username: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, bio: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, website: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  );\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F 3. Form observes editableProfile — resets when profile reloads\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  f\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> form\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.editableProfile, (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">field\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.username);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    minLength\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.username, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.bio);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  });\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  save\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.f.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">valid\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F this.editableProfile() holds the current form values\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">    console.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">log\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">'Saving:'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">editableProfile\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  reset\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F Force linkedSignal to re-read from the resource\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">    this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.editableProfile.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">set\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">.userResource.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">()\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",[472],{"text":473,"type":80},"import { Component, inject, input }           from '@angular\u002Fcore';\nimport { httpResource }                        from '@angular\u002Fcommon\u002Fhttp';\nimport { linkedSignal }                        from '@angular\u002Fcore';\nimport { form, required, minLength }           from '@angular\u002Fforms\u002Fsignals';\n\ninterface UserProfile {\n  username: string;\n  bio:      string;\n  website:  string;\n}\n\n@Component({\n  selector: 'app-edit-profile',\n  standalone: true,\n  template: `\n    @if (userResource.isLoading()) {\n      \u003Cp>Loading profile...\u003C\u002Fp>\n    } @else {\n      \u003Cform (ngSubmit)=\"save()\">\n        \u003Cinput [formField]=\"f.username\" placeholder=\"Username\" \u002F>\n        \u003Ctextarea [formField]=\"f.bio\" placeholder=\"Bio\">\u003C\u002Ftextarea>\n        \u003Cinput [formField]=\"f.website\" placeholder=\"Website\" \u002F>\n        \u003Cbutton [disabled]=\"!f.valid() || !f.dirty()\">Save changes\u003C\u002Fbutton>\n        \u003Cbutton type=\"button\" (click)=\"reset()\">Discard\u003C\u002Fbutton>\n      \u003C\u002Fform>\n    }\n  `,\n})\nexport class EditProfileComponent {\n  userId = input.required\u003Cnumber>();\n\n  \u002F\u002F 1. Fetch from server — refetches when userId changes\n  userResource = httpResource\u003CUserProfile>(\n    () => `\u002Fapi\u002Fusers\u002F${this.userId()}`\n  );\n\n  \u002F\u002F 2. Bridge resource → writable signal via linkedSignal\n  \u002F\u002F    Updates automatically when userResource reloads\n  editableProfile = linkedSignal(\n    () => this.userResource.value() ?? { username: '', bio: '', website: '' }\n  );\n\n  \u002F\u002F 3. Form observes editableProfile — resets when profile reloads\n  f = form(this.editableProfile, (field) => {\n    required(field.username);\n    minLength(field.username, 3);\n    required(field.bio);\n  });\n\n  save() {\n    if (!this.f.valid()) return;\n    \u002F\u002F this.editableProfile() holds the current form values\n    console.log('Saving:', this.editableProfile());\n  }\n\n  reset() {\n    \u002F\u002F Force linkedSignal to re-read from the resource\n    this.editableProfile.set(this.userResource.value()!);\n  }\n}\n",{"type":82,"attrs":475,"content":477},{"id":476},"p_w7in7yub",[478,480,484,486,490],{"text":479,"type":80},"This pattern removes an entire category of code from edit forms: no more ",{"text":481,"type":80,"marks":482},"ngOnInit",[483],{"type":92},{"text":485,"type":80}," with ",{"text":487,"type":80,"marks":488},"patchValue()",[489],{"type":92},{"text":491,"type":80},", no more subscription to route params to trigger a reload, no more manual reset logic. The reactive graph handles all of it.",{"type":493,"attrs":494},"svgDiagram",{"id":495,"svg":496,"label":497},"svg_j1k4cqqu","\u003Csvg viewBox=\"0 0 402 813\" xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" font-family=\"system-ui, -apple-system, sans-serif\">\n\u003Cdefs>\u003Cmarker id=\"dgArrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\u003Cpath d=\"M 0 1 L 9 5 L 0 9 z\" fill=\"#64748b\"\u002F>\u003C\u002Fmarker>\u003C\u002Fdefs>\n\u003Crect x=\"0\" y=\"0\" width=\"402\" height=\"813\" fill=\"#ffffff\" rx=\"0\"\u002F>\n\u003Ctext x=\"201\" y=\"36\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"16\" font-weight=\"700\" fill=\"#0f172a\">Editing Server Data Pattern with Signal Forms\u003C\u002Ftext>\n\u003Cpath d=\"M 200.59375 143 C 200.59375 171, 200.6015625 171, 200.6015625 199\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#dgArrow)\"\u002F>\n\u003Ctext x=\"200.59765625\" y=\"171\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"11\" fill=\"#475569\" paint-order=\"stroke\" stroke=\"#ffffff\" stroke-width=\"4\" stroke-linejoin=\"round\">outputs\u003C\u002Ftext>\n\u003Cpath d=\"M 200.6015625 279 C 200.6015625 307, 200.59375 307, 200.59375 335\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#dgArrow)\"\u002F>\n\u003Ctext x=\"200.59765625\" y=\"307\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"11\" fill=\"#475569\" paint-order=\"stroke\" stroke=\"#ffffff\" stroke-width=\"4\" stroke-linejoin=\"round\">input to linkedSignal\u003C\u002Ftext>\n\u003Cpath d=\"M 200.59375 398 C 200.59375 426, 200.59375 426, 200.59375 454\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#dgArrow)\"\u002F>\n\u003Ctext x=\"200.59375\" y=\"426\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"11\" fill=\"#475569\" paint-order=\"stroke\" stroke=\"#ffffff\" stroke-width=\"4\" stroke-linejoin=\"round\">creates writable signal\u003C\u002Ftext>\n\u003Cpath d=\"M 200.59375 534 C 200.59375 562, 200.6015625 562, 200.6015625 590\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#dgArrow)\"\u002F>\n\u003Ctext x=\"200.59765625\" y=\"562\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"11\" fill=\"#475569\" paint-order=\"stroke\" stroke=\"#ffffff\" stroke-width=\"4\" stroke-linejoin=\"round\">input to form\u003C\u002Ftext>\n\u003Cpath d=\"M 200.6015625 653 C 200.6015625 681, 200.59375 681, 200.59375 709\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#dgArrow)\"\u002F>\n\u003Ctext x=\"200.59765625\" y=\"681\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"11\" fill=\"#475569\" paint-order=\"stroke\" stroke=\"#ffffff\" stroke-width=\"4\" stroke-linejoin=\"round\">validates fields\u003C\u002Ftext>\n\u003Cpath d=\"M 200.59375 534 C 200.59375 562, 200.59375 681, 200.59375 709\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#dgArrow)\"\u002F>\n\u003Ctext x=\"200.59375\" y=\"621.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"11\" fill=\"#475569\" paint-order=\"stroke\" stroke=\"#ffffff\" stroke-width=\"4\" stroke-linejoin=\"round\">form fields reflect signal\u003C\u002Ftext>\n\u003Cpath d=\"M 200.6015625 279 C 200.6015625 307, 200.6015625 562, 200.6015625 590\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#dgArrow)\"\u002F>\n\u003Ctext x=\"200.6015625\" y=\"434.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"11\" fill=\"#475569\" paint-order=\"stroke\" stroke=\"#ffffff\" stroke-width=\"4\" stroke-linejoin=\"round\">form resets on reload\u003C\u002Ftext>\n\u003Crect x=\"77.578125\" y=\"80\" width=\"246.03125\" height=\"63\" rx=\"10\" fill=\"#ffffff\" stroke=\"#94a3b8\" stroke-width=\"1.5\"\u002F>\n\u003Ctext x=\"200.59375\" y=\"101.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"14\" font-weight=\"600\" fill=\"#1e293b\">httpResource: Load server data\u003C\u002Ftext>\n\u003Ctext x=\"200.59375\" y=\"123\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"12\" font-weight=\"400\" fill=\"#64748b\">Fetches UserProfile by userId\u003C\u002Ftext>\n\u003Crect x=\"83.109375\" y=\"199\" width=\"234.984375\" height=\"80\" rx=\"10\" fill=\"#eef2ff\" stroke=\"#6366f1\" stroke-width=\"1.5\"\u002F>\n\u003Ctext x=\"200.6015625\" y=\"217.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"10\" font-weight=\"600\" fill=\"#6366f1\" letter-spacing=\"0.08em\">HTTP_RESOURCE\u003C\u002Ftext>\n\u003Ctext x=\"200.6015625\" y=\"237.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"14\" font-weight=\"600\" fill=\"#1e293b\">userResource.value()\u003C\u002Ftext>\n\u003Ctext x=\"200.6015625\" y=\"259\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"12\" font-weight=\"400\" fill=\"#64748b\">Current server data or empty profile\u003C\u002Ftext>\n\u003Crect x=\"60.15625\" y=\"335\" width=\"280.875\" height=\"63\" rx=\"10\" fill=\"#ffffff\" stroke=\"#94a3b8\" stroke-width=\"1.5\"\u002F>\n\u003Ctext x=\"200.59375\" y=\"356.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"14\" font-weight=\"600\" fill=\"#1e293b\">linkedSignal: Bridge resource to form\u003C\u002Ftext>\n\u003Ctext x=\"200.59375\" y=\"378\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"12\" font-weight=\"400\" fill=\"#64748b\">Creates writable signal from userResource\u003C\u002Ftext>\n\u003Crect x=\"78.78125\" y=\"454\" width=\"243.625\" height=\"80\" rx=\"10\" fill=\"#f0f9ff\" stroke=\"#0ea5e9\" stroke-width=\"1.5\"\u002F>\n\u003Ctext x=\"200.59375\" y=\"472.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"10\" font-weight=\"600\" fill=\"#0ea5e9\" letter-spacing=\"0.08em\">LINKED_SIGNAL\u003C\u002Ftext>\n\u003Ctext x=\"200.59375\" y=\"492.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"14\" font-weight=\"600\" fill=\"#1e293b\">editableProfile signal\u003C\u002Ftext>\n\u003Ctext x=\"200.59375\" y=\"514\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"12\" font-weight=\"400\" fill=\"#64748b\">Writable signal bridged from resource\u003C\u002Ftext>\n\u003Crect x=\"63.671875\" y=\"590\" width=\"273.859375\" height=\"63\" rx=\"10\" fill=\"#ffffff\" stroke=\"#94a3b8\" stroke-width=\"1.5\"\u002F>\n\u003Ctext x=\"200.6015625\" y=\"611.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"14\" font-weight=\"600\" fill=\"#1e293b\">form: Form observes editableProfile\u003C\u002Ftext>\n\u003Ctext x=\"200.6015625\" y=\"633\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"12\" font-weight=\"400\" fill=\"#64748b\">Validates and tracks form state\u003C\u002Ftext>\n\u003Crect x=\"63.234375\" y=\"709\" width=\"274.71875\" height=\"80\" rx=\"10\" fill=\"#ecfdf5\" stroke=\"#10b981\" stroke-width=\"1.5\"\u002F>\n\u003Ctext x=\"200.59375\" y=\"727.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"10\" font-weight=\"600\" fill=\"#10b981\" letter-spacing=\"0.08em\">FORM\u003C\u002Ftext>\n\u003Ctext x=\"200.59375\" y=\"747.5\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"14\" font-weight=\"600\" fill=\"#1e293b\">Form fields &amp; validation\u003C\u002Ftext>\n\u003Ctext x=\"200.59375\" y=\"769\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"12\" font-weight=\"400\" fill=\"#64748b\">username, bio with required and minLength\u003C\u002Ftext>\n\u003C\u002Fsvg>","Editing Server Data Pattern with Signal Forms",{"type":177},{"type":74,"attrs":500,"content":502},{"id":501,"level":36},"h_vswzk1q4",[503],{"text":504,"type":80},"Cross-Field Validation",{"type":82,"attrs":506,"content":508},{"id":507},"p_dpkg6uln",[509,511,514],{"text":510,"type":80},"Cross-field validation used to require a custom validator at the ",{"text":125,"type":80,"marks":512},[513],{"type":92},{"text":515,"type":80}," level with an awkward API. In Signal Forms, a validator on one field can directly read any sibling field's signal.",{"type":319,"attrs":517,"content":520},{"id":518,"language":57,"highlighted":519},"code_9aelat4d","\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">interface\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> PasswordForm\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  password\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">  confirm\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">  string\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">f \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> form\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">signal\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">PasswordForm\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">>({ password: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, confirm: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> }), (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">field\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.password);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  minLength\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.password, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">8\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">  required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field.confirm);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F Cross-field rule: reads the sibling field's signal directly\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  field.confirm.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">addValidator\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">((\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">confirmValue\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> pwd\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> field.password.\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">value\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(); \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F sibling signal — no workarounds\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> confirmValue \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">!==\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> pwd\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">      ?\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { passwordMismatch: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">      :\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  });\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">});\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F In the template:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F @if (f.confirm().errors()?.passwordMismatch) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F   &#x3C;span>Passwords don't match&#x3C;\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",[521],{"text":522,"type":80},"interface PasswordForm {\n  password: string;\n  confirm:  string;\n}\n\nf = form(signal\u003CPasswordForm>({ password: '', confirm: '' }), (field) => {\n  required(field.password);\n  minLength(field.password, 8);\n\n  required(field.confirm);\n\n  \u002F\u002F Cross-field rule: reads the sibling field's signal directly\n  field.confirm.addValidator((confirmValue) => {\n    const pwd = field.password.value(); \u002F\u002F sibling signal — no workarounds\n    return confirmValue !== pwd\n      ? { passwordMismatch: true }\n      : null;\n  });\n});\n\n\u002F\u002F In the template:\n\u002F\u002F @if (f.confirm().errors()?.passwordMismatch) {\n\u002F\u002F   \u003Cspan>Passwords don't match\u003C\u002Fspan>\n\u002F\u002F }\n",{"type":177},{"type":74,"attrs":525,"content":527},{"id":526,"level":36},"h_xiu3xyrz",[528],{"text":529,"type":80},"Reactive Forms vs Signal Forms",{"type":531,"attrs":532,"content":534},"table",{"id":533},"tbl_c55y9m25",[535,567,597,644,686,736,770,812,853,887],{"type":536,"content":537},"tableRow",[538,549,558],{"type":539,"attrs":540,"content":542},"tableHeader",{"align":9,"colspan":541,"rowspan":541,"colwidth":9},1,[543],{"type":82,"attrs":544,"content":546},{"id":545},"p_b35mxam1",[547],{"text":548,"type":80},"Criterion",{"type":539,"attrs":550,"content":551},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[552],{"type":82,"attrs":553,"content":555},{"id":554},"p_d1z0tf0a",[556],{"text":557,"type":80},"Reactive Forms",{"type":539,"attrs":559,"content":560},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[561],{"type":82,"attrs":562,"content":564},{"id":563},"p_6i2s8yqw",[565],{"text":566,"type":80},"Signal Forms (Angular 22)",{"type":536,"content":568},[569,579,588],{"type":570,"attrs":571,"content":572},"tableCell",{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[573],{"type":82,"attrs":574,"content":576},{"id":575},"p_jtq5kj1q",[577],{"text":578,"type":80},"State model",{"type":570,"attrs":580,"content":581},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[582],{"type":82,"attrs":583,"content":585},{"id":584},"p_hkqkp9gg",[586],{"text":587,"type":80},"Observable-based — subscribe, unsubscribe, manage lifecycle",{"type":570,"attrs":589,"content":590},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[591],{"type":82,"attrs":592,"content":594},{"id":593},"p_stbcugbj",[595],{"text":596,"type":80},"Signal-based — read, derive, auto-cleanup ✅",{"type":536,"content":598},[599,608,625],{"type":570,"attrs":600,"content":601},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[602],{"type":82,"attrs":603,"content":605},{"id":604},"p_8pa30p9g",[606],{"text":607,"type":80},"Custom controls",{"type":570,"attrs":609,"content":610},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[611],{"type":82,"attrs":612,"content":614},{"id":613},"p_z6fl5n0f",[615,618,620,623],{"text":153,"type":80,"marks":616},[617],{"type":92},{"text":619,"type":80}," — 4 methods + provider + ",{"text":89,"type":80,"marks":621},[622],{"type":92},{"text":624,"type":80}," (~40 lines)",{"type":570,"attrs":626,"content":627},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[628],{"type":82,"attrs":629,"content":631},{"id":630},"p_b4k4hqwt",[632,636,638,642],{"text":633,"type":80,"marks":634},"FormValueControl",[635],{"type":92},{"text":637,"type":80}," — expose ",{"text":639,"type":80,"marks":640},"model()",[641],{"type":92},{"text":643,"type":80}," signal (3 lines) ✅",{"type":536,"content":645},[646,655,672],{"type":570,"attrs":647,"content":648},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[649],{"type":82,"attrs":650,"content":652},{"id":651},"p_9odpibdp",[653],{"text":654,"type":80},"Nested forms",{"type":570,"attrs":656,"content":657},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[658],{"type":82,"attrs":659,"content":661},{"id":660},"p_1cdijz0j",[662,665,667,670],{"text":125,"type":80,"marks":663},[664],{"type":92},{"text":666,"type":80}," inside ",{"text":125,"type":80,"marks":668},[669],{"type":92},{"text":671,"type":80}," — type inference breaks at depth",{"type":570,"attrs":673,"content":674},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[675],{"type":82,"attrs":676,"content":678},{"id":677},"p_4qt4jf0s",[679,681,684],{"text":680,"type":80},"Model shape mirrors naturally — ",{"text":212,"type":80,"marks":682},[683],{"type":92},{"text":685,"type":80}," is fully typed ✅",{"type":536,"content":687},[688,697,718],{"type":570,"attrs":689,"content":690},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[691],{"type":82,"attrs":692,"content":694},{"id":693},"p_a1cx441s",[695],{"text":696,"type":80},"Async validation",{"type":570,"attrs":698,"content":699},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[700],{"type":82,"attrs":701,"content":703},{"id":702},"p_66btv8tg",[704,706,710,712,716],{"text":705,"type":80},"Return ",{"text":707,"type":80,"marks":708},"Observable",[709],{"type":92},{"text":711,"type":80}," or ",{"text":713,"type":80,"marks":714},"Promise",[715],{"type":92},{"text":717,"type":80}," — manual debounce setup",{"type":570,"attrs":719,"content":720},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[721],{"type":82,"attrs":722,"content":724},{"id":723},"p_tfs7cbwx",[725,729,730,734],{"text":726,"type":80,"marks":727},"addAsyncValidator",[728],{"type":92},{"text":485,"type":80},{"text":731,"type":80,"marks":732},"{ debounce: 400 }",[733],{"type":92},{"text":735,"type":80}," built-in ✅",{"type":536,"content":737},[738,747,761],{"type":570,"attrs":739,"content":740},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[741],{"type":82,"attrs":742,"content":744},{"id":743},"p_84tbzjsf",[745],{"text":746,"type":80},"Cross-field rules",{"type":570,"attrs":748,"content":749},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[750],{"type":82,"attrs":751,"content":753},{"id":752},"p_shruz6ds",[754,756,759],{"text":755,"type":80},"Custom validator at ",{"text":125,"type":80,"marks":757},[758],{"type":92},{"text":760,"type":80}," level — awkward API",{"type":570,"attrs":762,"content":763},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[764],{"type":82,"attrs":765,"content":767},{"id":766},"p_0hx6402m",[768],{"text":769,"type":80},"Read any sibling's signal directly inside the schema ✅",{"type":536,"content":771},[772,781,795],{"type":570,"attrs":773,"content":774},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[775],{"type":82,"attrs":776,"content":778},{"id":777},"p_j1f1ccmd",[779],{"text":780,"type":80},"Server data binding",{"type":570,"attrs":782,"content":783},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[784],{"type":82,"attrs":785,"content":787},{"id":786},"p_xgjonkko",[788,790,793],{"text":789,"type":80},"Manual ",{"text":487,"type":80,"marks":791},[792],{"type":92},{"text":794,"type":80}," after HTTP response",{"type":570,"attrs":796,"content":797},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[798],{"type":82,"attrs":799,"content":801},{"id":800},"p_b2tdahgj",[802,805,807,810],{"text":206,"type":80,"marks":803},[804],{"type":92},{"text":806,"type":80}," + ",{"text":457,"type":80,"marks":808},[809],{"type":92},{"text":811,"type":80}," — auto-sync ✅",{"type":536,"content":813},[814,823,841],{"type":570,"attrs":815,"content":816},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[817],{"type":82,"attrs":818,"content":820},{"id":819},"p_rwbl4tmp",[821],{"text":822,"type":80},"Reading the value",{"type":570,"attrs":824,"content":825},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[826],{"type":82,"attrs":827,"content":829},{"id":828},"p_b82sx379",[830,834,835,839],{"text":831,"type":80,"marks":832},"form.getRawValue()",[833],{"type":92},{"text":711,"type":80},{"text":836,"type":80,"marks":837},"form.value",[838],{"type":92},{"text":840,"type":80}," (nulls possible)",{"type":570,"attrs":842,"content":843},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[844],{"type":82,"attrs":845,"content":847},{"id":846},"p_k8v2phbi",[848,851],{"text":639,"type":80,"marks":849},[850],{"type":92},{"text":852,"type":80}," — always the full typed value, no nulls ✅",{"type":536,"content":854},[855,864,873],{"type":570,"attrs":856,"content":857},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[858],{"type":82,"attrs":859,"content":861},{"id":860},"p_brvd6xv6",[862],{"text":863,"type":80},"Compatibility",{"type":570,"attrs":865,"content":866},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[867],{"type":82,"attrs":868,"content":870},{"id":869},"p_8mefhd5c",[871],{"text":872,"type":80},"Stand-alone",{"type":570,"attrs":874,"content":875},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[876],{"type":82,"attrs":877,"content":879},{"id":878},"p_s9p1reae",[880,882,885],{"text":881,"type":80},"Compatible with CVA and Reactive Forms via ",{"text":247,"type":80,"marks":883},[884],{"type":92},{"text":886,"type":80}," ✅",{"type":536,"content":888},[889,898,907],{"type":570,"attrs":890,"content":891},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[892],{"type":82,"attrs":893,"content":895},{"id":894},"p_t2nzi7fe",[896],{"text":897,"type":80},"When to keep",{"type":570,"attrs":899,"content":900},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[901],{"type":82,"attrs":902,"content":904},{"id":903},"p_xvs5m466",[905],{"text":906,"type":80},"Existing forms that work — don't migrate what isn't broken",{"type":570,"attrs":908,"content":909},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[910],{"type":82,"attrs":911,"content":913},{"id":912},"p_5p767c27",[914],{"text":915,"type":80},"All new forms starting today",{"type":177},{"type":74,"attrs":918,"content":920},{"id":919,"level":36},"h_0rz6dpau",[921],{"text":922,"type":80},"Migrating Without a Big Bang",{"type":82,"attrs":924,"content":926},{"id":925},"p_j1u429g9",[927,929,932,933,936],{"text":928,"type":80},"The right migration is incremental. Angular 22 guarantees ",{"text":153,"type":80,"marks":930},[931],{"type":92},{"text":100,"type":80},{"text":633,"type":80,"marks":934},[935],{"type":92},{"text":937,"type":80}," are mutually compatible — you can have both in the same form without a single workaround.",{"type":74,"attrs":939,"content":941},{"id":940,"level":193},"h_6gqw60ml",[942],{"text":943,"type":80},"Signal Forms evolution",{"type":531,"attrs":945,"content":947},{"id":946},"tbl_a9mnlzgt",[948,968,990],{"type":536,"content":949},[950,959],{"type":539,"attrs":951,"content":952},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[953],{"type":82,"attrs":954,"content":956},{"id":955},"p_n5c4msci",[957],{"text":958,"type":80},"Version",{"type":539,"attrs":960,"content":961},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[962],{"type":82,"attrs":963,"content":965},{"id":964},"p_nc1zqxeb",[966],{"text":967,"type":80},"Status",{"type":536,"content":969},[970,981],{"type":570,"attrs":971,"content":972},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[973],{"type":82,"attrs":974,"content":976},{"id":975},"p_m7r9q8cf",[977],{"text":978,"type":80,"marks":979},"v21",[980],{"type":149},{"type":570,"attrs":982,"content":983},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[984],{"type":82,"attrs":985,"content":987},{"id":986},"p_tzcrq2v4",[988],{"text":989,"type":80},"Experimental — API unstable between minor versions. Not recommended for production.",{"type":536,"content":991},[992,1003],{"type":570,"attrs":993,"content":994},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[995],{"type":82,"attrs":996,"content":998},{"id":997},"p_ikpkkntm",[999],{"text":1000,"type":80,"marks":1001},"v22",[1002],{"type":149},{"type":570,"attrs":1004,"content":1005},{"align":9,"colspan":541,"rowspan":541,"colwidth":9},[1006],{"type":82,"attrs":1007,"content":1009},{"id":1008},"p_t3pn6bca",[1010,1014,1015,1018,1019,1022,1023,1026,1027,1030,1031,1035,1037,1041],{"text":1011,"type":80,"marks":1012},"Stable — public API.",[1013],{"type":149},{"text":151,"type":80},{"text":196,"type":80,"marks":1016},[1017],{"type":92},{"text":288,"type":80},{"text":633,"type":80,"marks":1020},[1021],{"type":92},{"text":288,"type":80},{"text":247,"type":80,"marks":1024},[1025],{"type":92},{"text":288,"type":80},{"text":266,"type":80,"marks":1028},[1029],{"type":92},{"text":288,"type":80},{"text":1032,"type":80,"marks":1033},"ngNoCva",[1034],{"type":92},{"text":1036,"type":80},", and ",{"text":1038,"type":80,"marks":1039},"compatForm",[1040],{"type":92},{"text":1042,"type":80}," bridge are all stable.",{"type":74,"attrs":1044,"content":1046},{"id":1045,"level":193},"h_xrdc4tim",[1047],{"text":1048,"type":80},"The four-step migration playbook",{"type":82,"attrs":1050,"content":1052},{"id":1051},"p_t13trvf5",[1053,1057,1059,1062,1063,1066,1068,1071,1073,1076],{"text":1054,"type":80,"marks":1055},"Step 1 — Migrate custom controls first.",[1056],{"type":149},{"text":1058,"type":80}," Replace ",{"text":153,"type":80,"marks":1060},[1061],{"type":92},{"text":485,"type":80},{"text":633,"type":80,"marks":1064},[1065],{"type":92},{"text":1067,"type":80}," one control at a time. Angular 22 makes ",{"text":633,"type":80,"marks":1069},[1070],{"type":92},{"text":1072,"type":80}," components fully compatible with existing ",{"text":125,"type":80,"marks":1074},[1075],{"type":92},{"text":1077,"type":80}," parents — no parent changes needed.",{"type":1079,"attrs":1080},"speedCards",{"id":1081,"cards":1082,"title":1099,"caption":1100},"speed_x9v7ra57",[1083,1087,1091,1095],{"label":1084,"value":1085,"detail":1086},"Migrate Custom Controls","Step 1","Replace ControlValueAccessor with FormValueControl one control at a time without changing parent FormGroups.",{"label":1088,"value":1089,"detail":1090},"Write New Forms","Step 2","Develop new forms using Signal Forms to maximize migration ROI; leave working forms unchanged.",{"label":1092,"value":1093,"detail":1094},"Use SignalFormControl","Step 3","Insert signal-based controls into existing FormGroups to enable two-way validation error propagation.",{"label":1096,"value":1097,"detail":1098},"Add ngNoCva","Step 4","Apply ngNoCva directive when mixing legacy and Signal Forms APIs to avoid conflicts.","Four-Step Migration Playbook","A concise guide to incrementally migrate Angular forms using Signal Forms.",{"type":82,"attrs":1102,"content":1104},{"id":1103},"p_f0mb3ser",[1105,1109],{"text":1106,"type":80,"marks":1107},"Step 2 — Write all new forms with Signal Forms.",[1108],{"type":149},{"text":1110,"type":80}," Don't touch working forms. The ROI of migration is in new development — where Signal Forms eliminate setup entirely — not in rewriting forms that already work.",{"type":82,"attrs":1112,"content":1114},{"id":1113},"p_d8f4zk4g",[1115,1119,1122,1126,1128,1131],{"text":1116,"type":80,"marks":1117},"Step 3 — Use ",[1118],{"type":149},{"text":247,"type":80,"marks":1120},[1121],{"type":92},{"text":1123,"type":80,"marks":1124}," as a bridge.",[1125],{"type":149},{"text":1127,"type":80}," Drop a signal-based control inside an existing ",{"text":125,"type":80,"marks":1129},[1130],{"type":92},{"text":1132,"type":80},". Validation errors propagate both ways — no double bookkeeping.",{"type":319,"attrs":1134,"content":1137},{"id":1135,"language":57,"highlighted":1136},"code_hq3oa5ka","\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { SignalFormControl } \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fforms\u002Fsignals'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> { FormGroup }         \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\"> '@angular\u002Fforms'\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F Drop a signal-based control into an existing FormGroup — no rewrite needed\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">const\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\"> legacyForm\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> FormGroup\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  firstName: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> FormControl\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  lastName:  \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> FormControl\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">  \u002F\u002F Signal-based control coexists naturally\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  nickname: \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\"> SignalFormControl\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#032F62;--shiki-dark:#9ECBFF\">''\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">, (\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#E36209;--shiki-dark:#FFAB70\">field\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#D73A49;--shiki-dark:#F97583\">=>\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    required\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6F42C1;--shiki-dark:#B392F0\">    minLength\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">(field, \u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">  }),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#24292E;--shiki-dark:#E1E4E8\">});\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F legacyForm.getRawValue()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F → { firstName: '', lastName: '', nickname: '' }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"--shiki-light:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F nickname behaves exactly like a regular FormControl from the parent's perspective\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",[1138],{"text":1139,"type":80},"import { SignalFormControl } from '@angular\u002Fforms\u002Fsignals';\nimport { FormGroup }         from '@angular\u002Fforms';\n\n\u002F\u002F Drop a signal-based control into an existing FormGroup — no rewrite needed\nconst legacyForm = new FormGroup({\n  firstName: new FormControl(''),\n  lastName:  new FormControl(''),\n\n  \u002F\u002F Signal-based control coexists naturally\n  nickname: new SignalFormControl('', (field) => {\n    required(field);\n    minLength(field, 2);\n  }),\n});\n\n\u002F\u002F legacyForm.getRawValue()\n\u002F\u002F → { firstName: '', lastName: '', nickname: '' }\n\u002F\u002F nickname behaves exactly like a regular FormControl from the parent's perspective\n",{"type":82,"attrs":1141,"content":1143},{"id":1142},"p_nzg5k711",[1144,1148,1151,1155,1157,1160,1162,1166,1168,1171],{"text":1145,"type":80,"marks":1146},"Step 4 — Add ",[1147],{"type":149},{"text":1032,"type":80,"marks":1149},[1150],{"type":92},{"text":1152,"type":80,"marks":1153}," when mixing both APIs on the same element.",[1154],{"type":149},{"text":1156,"type":80}," When ",{"text":266,"type":80,"marks":1158},[1159],{"type":92},{"text":1161,"type":80}," is used on an input that is also inside a ",{"text":1163,"type":80,"marks":1164},"[formGroup]",[1165],{"type":92},{"text":1167,"type":80},", both APIs try to control the same element. ",{"text":1032,"type":80,"marks":1169},[1170],{"type":92},{"text":1172,"type":80}," tells Angular to skip the CVA and let Signal Forms take over.",{"type":137,"attrs":1174,"content":1176},{"id":1175},"quote_ssdgq4n2",[1177],{"type":82,"attrs":1178,"content":1180},{"id":1179},"p_6scl8ukh",[1181,1185,1188,1192,1194,1197,1198,1201,1202,1206,1208,1211,1213,1217,1219,1222,1224,1227],{"text":1182,"type":80,"marks":1183},"⚠️ The ",[1184],{"type":149},{"text":1032,"type":80,"marks":1186},[1187],{"type":92},{"text":1189,"type":80,"marks":1190}," directive:",[1191],{"type":149},{"text":1193,"type":80}," you only need this when mixing ",{"text":266,"type":80,"marks":1195},[1196],{"type":92},{"text":485,"type":80},{"text":1163,"type":80,"marks":1199},[1200],{"type":92},{"text":288,"type":80},{"text":1203,"type":80,"marks":1204},"formControlName",[1205],{"type":92},{"text":1207,"type":80},", or ",{"text":278,"type":80,"marks":1209},[1210],{"type":92},{"text":1212,"type":80}," on the ",{"text":1214,"type":80,"marks":1215},"same element",[1216],{"type":149},{"text":1218,"type":80},". If you're using ",{"text":266,"type":80,"marks":1220},[1221],{"type":92},{"text":1223,"type":80}," standalone or on a ",{"text":633,"type":80,"marks":1225},[1226],{"type":92},{"text":1228,"type":80}," component, there's no conflict.",{"type":177},{"type":74,"attrs":1231,"content":1233},{"id":1232,"level":36},"h_ksolhi0p",[1234],{"text":1235,"type":80},"When to Use Signal Forms — and When Not To",{"type":74,"attrs":1237,"content":1239},{"id":1238,"level":193},"h_s2ghdqwc",[1240],{"text":1241,"type":80},"✅ Reach for Signal Forms when",{"type":1243,"attrs":1244,"content":1247},"bulletList",{"id":1245,"tight":1246},"ul_dneyj8yt",true,[1248,1259,1269,1288,1298,1308,1329],{"type":1249,"attrs":1250,"content":1252},"listItem",{"id":1251},"li_iv44gji3",[1253],{"type":82,"attrs":1254,"content":1256},{"id":1255},"p_098ulick",[1257],{"text":1258,"type":80},"Writing any new form from scratch — this is the default now",{"type":1249,"attrs":1260,"content":1262},{"id":1261},"li_tddknqzc",[1263],{"type":82,"attrs":1264,"content":1266},{"id":1265},"p_4uwh9qos",[1267],{"text":1268,"type":80},"Building custom form controls — replaces CVA completely",{"type":1249,"attrs":1270,"content":1272},{"id":1271},"li_dp2vbo1q",[1273],{"type":82,"attrs":1274,"content":1276},{"id":1275},"p_86md8lex",[1277,1279,1282,1283,1286],{"text":1278,"type":80},"The form data comes from an API — ",{"text":206,"type":80,"marks":1280},[1281],{"type":92},{"text":806,"type":80},{"text":457,"type":80,"marks":1284},[1285],{"type":92},{"text":1287,"type":80}," compose naturally",{"type":1249,"attrs":1289,"content":1291},{"id":1290},"li_935w4hkz",[1292],{"type":82,"attrs":1293,"content":1295},{"id":1294},"p_0pqxceav",[1296],{"text":1297,"type":80},"Async validation is required — built-in debouncing, no Observable setup",{"type":1249,"attrs":1299,"content":1301},{"id":1300},"li_38f4o9lt",[1302],{"type":82,"attrs":1303,"content":1305},{"id":1304},"p_y9qcuym1",[1306],{"text":1307,"type":80},"Cross-field validation exists — read sibling signals directly in the schema",{"type":1249,"attrs":1309,"content":1311},{"id":1310},"li_m6re80sr",[1312],{"type":82,"attrs":1313,"content":1315},{"id":1314},"p_o9xg070e",[1316,1318,1322,1324,1327],{"text":1317,"type":80},"You want typed ",{"text":1319,"type":80,"marks":1320},".value()",[1321],{"type":92},{"text":1323,"type":80}," without ",{"text":352,"type":80,"marks":1325},[1326],{"type":92},{"text":1328,"type":80}," or null coercion",{"type":1249,"attrs":1330,"content":1332},{"id":1331},"li_50i6yvdl",[1333],{"type":82,"attrs":1334,"content":1336},{"id":1335},"p_jfjkg5us",[1337],{"text":1338,"type":80},"You're in a zoneless app — Signals are the right primitive end to end",{"type":1340,"attrs":1341,"content":1343},"compare",{"id":1342},"cmp_ob391y0e",[1344,1381],{"type":1345,"attrs":1346,"content":1350},"compareColumn",{"id":1347,"side":1348,"title":1349},"col_u61b89jv","left","When to Use Signal Forms",[1351,1357,1363,1369,1375],{"type":82,"attrs":1352,"content":1354},{"id":1353},"p_ca2qjpjw",[1355],{"text":1356,"type":80},"Writing any new form from scratch (default choice)",{"type":82,"attrs":1358,"content":1360},{"id":1359},"p_olrmhk72",[1361],{"text":1362,"type":80},"Building custom form controls (fully replaces CVA)",{"type":82,"attrs":1364,"content":1366},{"id":1365},"p_zui5th67",[1367],{"text":1368,"type":80},"Form data sourced from an API (natural with linkedSignal + httpResource)",{"type":82,"attrs":1370,"content":1372},{"id":1371},"p_jwxcv3eo",[1373],{"text":1374,"type":80},"Async validation needed (built-in debouncing, no Observable setup)",{"type":82,"attrs":1376,"content":1378},{"id":1377},"p_dxiyvlsx",[1379],{"text":1380,"type":80},"Cross-field validation supported via sibling signals in schema",{"type":1345,"attrs":1382,"content":1386},{"id":1383,"side":1384,"title":1385},"col_uxrifvjd","right","When to Keep Reactive Forms",[1387,1393,1399,1405,1411],{"type":82,"attrs":1388,"content":1390},{"id":1389},"p_c853n3ot",[1391],{"text":1392,"type":80},"Existing forms working without bugs",{"type":82,"attrs":1394,"content":1396},{"id":1395},"p_zdq936pd",[1397],{"text":1398,"type":80},"Complex FormGroup logic with custom status management",{"type":82,"attrs":1400,"content":1402},{"id":1401},"p_91c4sltf",[1403],{"text":1404,"type":80},"Third-party CVA libraries not yet updated",{"type":82,"attrs":1406,"content":1408},{"id":1407},"p_pqh7dc31",[1409],{"text":1410,"type":80},"Team requires time to gain intuition before migrating",{"type":82,"attrs":1412,"content":1414},{"id":1413},"p_eob21xa7",[1415],{"text":1416,"type":80},"Simple admin panels or settings screens with no issues",{"type":74,"attrs":1418,"content":1420},{"id":1419,"level":193},"h_j04fgowm",[1421],{"text":1422,"type":80},"→ Keep Reactive Forms when",{"type":1243,"attrs":1424,"content":1426},{"id":1425,"tight":1246},"ul_5szeo6r6",[1427,1437,1452,1462,1472],{"type":1249,"attrs":1428,"content":1430},{"id":1429},"li_zbigvrdk",[1431],{"type":82,"attrs":1432,"content":1434},{"id":1433},"p_ozkuaitd",[1435],{"text":1436,"type":80},"The form already works and there's no bug to fix",{"type":1249,"attrs":1438,"content":1440},{"id":1439},"li_h4oge9tm",[1441],{"type":82,"attrs":1442,"content":1444},{"id":1443},"p_3vicj8zj",[1445,1447,1450],{"text":1446,"type":80},"Heavily customized ",{"text":125,"type":80,"marks":1448},[1449],{"type":92},{"text":1451,"type":80}," logic with complex status management",{"type":1249,"attrs":1453,"content":1455},{"id":1454},"li_dvgey52q",[1456],{"type":82,"attrs":1457,"content":1459},{"id":1458},"p_im76w86b",[1460],{"text":1461,"type":80},"Third-party CVA libraries that haven't been updated yet",{"type":1249,"attrs":1463,"content":1465},{"id":1464},"li_58hql8wk",[1466],{"type":82,"attrs":1467,"content":1469},{"id":1468},"p_gs0veo3p",[1470],{"text":1471,"type":80},"Your team needs time to build intuition — don't rush a big-bang migration",{"type":1249,"attrs":1473,"content":1475},{"id":1474},"li_7lch4k51",[1476],{"type":82,"attrs":1477,"content":1479},{"id":1478},"p_d8xfcl30",[1480],{"text":1481,"type":80},"Admin panels and settings screens with simple logic and no issues",{"type":177},{"type":74,"attrs":1484,"content":1486},{"id":1485,"level":36},"h_hzc7ugy7",[1487],{"text":1488,"type":80},"The Bottom Line",{"type":82,"attrs":1490,"content":1492},{"id":1491},"p_uz1bjj15",[1493],{"text":1494,"type":80},"Signal Forms aren't a new way to think about forms. They're the same concepts — validation, state, binding, submission — built on the right primitive. Signals were always going to be the foundation of Angular's reactivity; forms were just the last major surface to catch up.",{"type":82,"attrs":1496,"content":1498},{"id":1497},"p_gbd91r3o",[1499],{"text":1500,"type":80},"The practical impact is immediate in two places:",{"type":82,"attrs":1502,"content":1504},{"id":1503},"p_bj6vvmok",[1505,1509],{"text":1506,"type":80,"marks":1507},"Custom controls:",[1508],{"type":149},{"text":1510,"type":80}," if you have a design system with 10 custom inputs, each one can lose 30–40 lines of boilerplate the next time you touch it.",{"type":82,"attrs":1512,"content":1514},{"id":1513},"p_4pd465yp",[1515,1519,1521,1524,1525,1528,1530,1533],{"text":1516,"type":80,"marks":1517},"Form-to-API integration:",[1518],{"type":149},{"text":1520,"type":80}," the ",{"text":457,"type":80,"marks":1522},[1523],{"type":92},{"text":806,"type":80},{"text":206,"type":80,"marks":1526},[1527],{"type":92},{"text":1529,"type":80}," pattern removes an entire category of manual ",{"text":487,"type":80,"marks":1531},[1532],{"type":92},{"text":1534,"type":80}," calls and subscription management that littered edit forms in every Angular codebase.",{"type":82,"attrs":1536,"content":1538},{"id":1537},"p_okrmsz4r",[1539,1541,1544,1546,1549],{"text":1540,"type":80},"The migration story is genuinely good. Angular 22 doesn't force your hand — ",{"text":153,"type":80,"marks":1542},[1543],{"type":92},{"text":1545,"type":80}," still works, ",{"text":125,"type":80,"marks":1547},[1548],{"type":92},{"text":1550,"type":80}," still works, and the two APIs can coexist in the same form. The right moment to switch is the next new form you write, not a scheduled migration sprint.",{"type":137,"attrs":1552,"content":1554},{"id":1553},"quote_h4fepmvw",[1555],{"type":82,"attrs":1556,"content":1558},{"id":1557},"p_ww6fh1up",[1559,1563,1564,1568,1570,1573,1574,1577,1578,1581,1583,1587,1589,1593],{"text":1560,"type":80,"marks":1561},"The one-sentence decision guide:",[1562],{"type":149},{"text":151,"type":80},{"text":1565,"type":80,"marks":1566},"New form?",[1567],{"type":149},{"text":1569,"type":80}," Use Signal Forms — ",{"text":196,"type":80,"marks":1571},[1572],{"type":92},{"text":288,"type":80},{"text":639,"type":80,"marks":1575},[1576],{"type":92},{"text":288,"type":80},{"text":633,"type":80,"marks":1579},[1580],{"type":92},{"text":1582,"type":80},". ",{"text":1584,"type":80,"marks":1585},"Existing form that works?",[1586],{"type":149},{"text":1588,"type":80}," Leave it. ",{"text":1590,"type":80,"marks":1591},"Existing form you're touching anyway?",[1592],{"type":149},{"text":1594,"type":80}," Migrate the custom controls first, then consider the form model if it's worth the diff.",{"type":82,"attrs":1596,"content":1598},{"id":1597},"p_kdxwf8ln",[1599],{"text":1600,"type":80,"marks":1601},"The best Angular forms don't feel like Angular forms. They feel like data.",[1602],{"type":149},{"type":177},{"type":74,"attrs":1605,"content":1607},{"id":1606,"level":36},"h_d18xuwfo",[1608],{"text":1609,"type":80},"Sources",{"type":1243,"attrs":1611,"content":1613},{"id":1612,"tight":1246},"ul_z7qxn7ey",[1614,1637,1656,1675,1723,1749,1770,1800,1814,1842],{"type":1249,"attrs":1615,"content":1617},{"id":1616},"li_viwmwfo0",[1618],{"type":82,"attrs":1619,"content":1621},{"id":1620},"p_xapf2lsh",[1622,1626,1628,1631,1632,1635],{"text":1623,"type":80,"marks":1624},"angular.dev\u002Fguide\u002Fforms\u002Fsignals\u002Foverview",[1625],{"type":149},{"text":1627,"type":80}," — Official Angular Signal Forms guide: ",{"text":196,"type":80,"marks":1629},[1630],{"type":92},{"text":288,"type":80},{"text":266,"type":80,"marks":1633},[1634],{"type":92},{"text":1636,"type":80},", validators, async validation, nested forms.",{"type":1249,"attrs":1638,"content":1640},{"id":1639},"li_acdc8tk9",[1641],{"type":82,"attrs":1642,"content":1644},{"id":1643},"p_yputn9o9",[1645,1649,1651,1654],{"text":1646,"type":80,"marks":1647},"angular.dev\u002Fessentials\u002Fsignal-forms",[1648],{"type":149},{"text":1650,"type":80}," — Essentials overview, ",{"text":633,"type":80,"marks":1652},[1653],{"type":92},{"text":1655,"type":80}," interface, custom controls without ControlValueAccessor.",{"type":1249,"attrs":1657,"content":1659},{"id":1658},"li_uwa5qmx7",[1660],{"type":82,"attrs":1661,"content":1663},{"id":1662},"p_vytkle6k",[1664,1668,1670,1673],{"text":1665,"type":80,"marks":1666},"angular.dev\u002Fguide\u002Fforms\u002Fsignals\u002Fmigration",[1667],{"type":149},{"text":1669,"type":80}," — Official migration guide from Reactive Forms, ",{"text":1038,"type":80,"marks":1671},[1672],{"type":92},{"text":1674,"type":80}," bridge, incremental strategy.",{"type":1249,"attrs":1676,"content":1678},{"id":1677},"li_rdytkm15",[1679],{"type":82,"attrs":1680,"content":1682},{"id":1681},"p_w2kxqnnc",[1683,1687,1698,1702,1704,1707,1708,1711,1712,1716,1718,1721],{"text":1684,"type":80,"marks":1685},"Santosh Yadav — ",[1686],{"type":149},{"text":1688,"type":80,"marks":1689},"santoshyadav.dev",[1690,1697],{"type":1691,"attrs":1692},"link",{"rel":1693,"href":1694,"class":1695,"title":9,"target":1696},"noopener noreferrer","http:\u002F\u002Fsantoshyadav.dev","editor-link","_blank",{"type":149},{"text":1699,"type":80,"marks":1700}," (Jun 16, 2026)",[1701],{"type":149},{"text":1703,"type":80}," — \"Angular 22: The Signals Are Strong\" — ",{"text":1032,"type":80,"marks":1705},[1706],{"type":92},{"text":288,"type":80},{"text":247,"type":80,"marks":1709},[1710],{"type":92},{"text":288,"type":80},{"text":1713,"type":80,"marks":1714},"OnPush",[1715],{"type":92},{"text":1717,"type":80}," as new default, ",{"text":206,"type":80,"marks":1719},[1720],{"type":92},{"text":1722,"type":80}," custom set option.",{"type":1249,"attrs":1724,"content":1726},{"id":1725},"li_i43om4rp",[1727],{"type":82,"attrs":1728,"content":1730},{"id":1729},"p_zbg69cxd",[1731,1738,1742,1744,1747],{"text":1732,"type":80,"marks":1733},"Angular.love",[1734,1737],{"type":1691,"attrs":1735},{"rel":1693,"href":1736,"class":1695,"title":9,"target":1696},"http:\u002F\u002FAngular.love",{"type":149},{"text":1739,"type":80,"marks":1740}," (May 26, 2026)",[1741],{"type":149},{"text":1743,"type":80}," — \"Signal Forms in Angular 21: Complete Guide\" — ",{"text":1038,"type":80,"marks":1745},[1746],{"type":92},{"text":1748,"type":80},", validation model, CVA→FormValueControl comparison.",{"type":1249,"attrs":1750,"content":1752},{"id":1751},"li_26f6mfiv",[1753],{"type":82,"attrs":1754,"content":1756},{"id":1755},"p_s42srrdd",[1757,1764,1768],{"text":1758,"type":80,"marks":1759},"ANGULARarchitects.io",[1760,1763],{"type":1691,"attrs":1761},{"rel":1693,"href":1762,"class":1695,"title":9,"target":1696},"http:\u002F\u002FANGULARarchitects.io",{"type":149},{"text":1765,"type":80,"marks":1766}," (Jun 2026)",[1767],{"type":149},{"text":1769,"type":80}," — \"All About Angular's New Signal Forms\" — custom validation, schemas, nested forms, form arrays, debouncing.",{"type":1249,"attrs":1771,"content":1773},{"id":1772},"li_otcrti5d",[1774],{"type":82,"attrs":1775,"content":1777},{"id":1776},"p_fq30vanf",[1778,1782,1789,1793,1795,1798],{"text":1779,"type":80,"marks":1780},"Brian Treese — ",[1781],{"type":149},{"text":1783,"type":80,"marks":1784},"briantree.se",[1785,1788],{"type":1691,"attrs":1786},{"rel":1693,"href":1787,"class":1695,"title":9,"target":1696},"http:\u002F\u002Fbriantree.se",{"type":149},{"text":1790,"type":80,"marks":1791}," (Mar 26, 2026)",[1792],{"type":149},{"text":1794,"type":80}," — \"Angular 22: Mix Signal Forms and Reactive Forms Seamlessly\" — ",{"text":633,"type":80,"marks":1796},[1797],{"type":92},{"text":1799,"type":80}," migration, incremental strategy.",{"type":1249,"attrs":1801,"content":1803},{"id":1802},"li_i8rikohw",[1804],{"type":82,"attrs":1805,"content":1807},{"id":1806},"p_yvjd824l",[1808,1812],{"text":1809,"type":80,"marks":1810},"LogRocket Blog (Mar 27, 2026)",[1811],{"type":149},{"text":1813,"type":80}," — \"Signal Forms: Angular's best quality of life update in years.\"",{"type":1249,"attrs":1815,"content":1817},{"id":1816},"li_k7q8eeb2",[1818],{"type":82,"attrs":1819,"content":1821},{"id":1820},"p_t4m4c5v0",[1822,1826,1828,1831,1833,1836,1837,1840],{"text":1823,"type":80,"marks":1824},"Rajat \u002F Substack (Jun 2026)",[1825],{"type":149},{"text":1827,"type":80}," — \"Signal Forms Are Finally Production-Ready in Angular 22\" — ",{"text":196,"type":80,"marks":1829},[1830],{"type":92},{"text":1832,"type":80}," API, ",{"text":457,"type":80,"marks":1834},[1835],{"type":92},{"text":806,"type":80},{"text":206,"type":80,"marks":1838},[1839],{"type":92},{"text":1841,"type":80}," pattern, async validation.",{"type":1249,"attrs":1843,"content":1845},{"id":1844},"li_11b78wl2",[1846],{"type":82,"attrs":1847,"content":1849},{"id":1848},"p_hmnim5zx",[1850,1854],{"text":1851,"type":80,"marks":1852},"Angular_with_Awais \u002F Medium (May 2026)",[1853],{"type":149},{"text":1855,"type":80}," — \"Angular v22 Signal Forms: Build Custom Controls Without ControlValueAccessor.\"",{"type":177},{"type":82,"attrs":1858,"content":1860},{"id":1859},"p_wrbve4wf",[1861,1866,1869],{"text":1862,"type":80,"marks":1863},"Code examples reflect the Angular 22 Signal Forms stable API as released June 3, 2026. ",[1864],{"type":1865},"italic",{"text":153,"type":80,"marks":1867},[1868],{"type":92},{"text":1870,"type":80,"marks":1871}," is not deprecated and continues to work in Angular 22. Always refer to angular.dev\u002Fguide\u002Fforms\u002Fsignals for the current stable API.",[1872],{"type":1865},"Learn how Angular 22 Signal Forms simplify reactive forms and custom controls, replacing complex ControlValueAccessor boilerplate with clean code.","2026-08-24T14:53:53.315534+00:00",[1876,1900,1916,1930],{"id":1877,"slug":1878,"title":1879,"excerpt":1880,"image":1881,"featured":1246,"groupId":1882,"publishedAt":1883,"readingTime":1884,"views":48,"author":1885,"category":1886,"tags":1887},"81f24e9a-fc6b-47d0-beb9-d00a6137ed99","framework-vs-sdk-who-calls-whom","Framework vs SDK: Who Calls Whom","Most developers memorize names, but few understand the mechanism. Here is the simple rule to tell a framework from an SDK and level up your debugging.","https:\u002F\u002Fmfgiomkkbihnlpfdxwrt.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fblog-assets\u002Fmedia\u002Ffeatured\u002F1787491947099-featured-ikba2e.jpg","0ccb21eb-6459-40ed-8110-227836e9bb74","2026-08-23T13:15:25.439194+00:00",10,{"name":50,"avatar":9},{"slug":13,"name":14,"color":15},[1888,1891,1894,1897],{"slug":1889,"name":1890},"javascript","JavaScript",{"slug":1892,"name":1893},"python","Python",{"slug":1895,"name":1896},"runtime","Runtime",{"slug":1898,"name":1899},"sdk","Sdk",{"id":1901,"slug":1902,"title":1903,"excerpt":1904,"image":1905,"featured":44,"groupId":1906,"publishedAt":1907,"readingTime":1908,"views":1909,"author":1910,"category":1911,"tags":1912},"c2d6b1ca-29bf-401f-8ab9-a1f828504d0b","microfrontends-with-angular-and-nx-what-nobody-tells-you","Microfrontends with Angular and Nx: what nobody tells you","Every tutorial teaches how to set up microfrontends in minutes, but few reveal the architectural chaos that emerges after months of production deploys.","https:\u002F\u002Fmfgiomkkbihnlpfdxwrt.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fblog-assets\u002Fmedia\u002Ffeatured\u002F1785206853570-featured-zoyhgn.webp","3368e1b2-b6de-4e94-a3d4-294fae8ade13","2026-08-03T01:42:48.969355+00:00",15,5,{"name":50,"avatar":9},{"slug":13,"name":14,"color":15},[1913,1914,1915],{"slug":54,"name":55},{"slug":1889,"name":1890},{"slug":57,"name":58},{"id":1917,"slug":1918,"title":1919,"excerpt":1920,"image":1921,"featured":44,"groupId":1922,"publishedAt":1923,"readingTime":1924,"views":1884,"author":1925,"category":1926,"tags":1927},"36440333-311c-4081-92e6-030a04a38fed","astro-island-architecture-performance-and-where-the-framework-truly-fits","Astro: Island Architecture, Performance, and Where the Framework Truly Fits","Astro challenges the modern web status quo by prioritizing static HTML and isolating interactivity into independent, lightweight code islands.","https:\u002F\u002Fmfgiomkkbihnlpfdxwrt.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fblog-assets\u002Fmedia\u002Ffeatured\u002F1785721161920-featured-a5vzkb.webp","cb62459e-7ace-43d3-a545-661c5695e925","2026-08-03T01:00:45.280114+00:00",38,{"name":50,"avatar":9},{"slug":13,"name":14,"color":15},[1928,1929],{"slug":1889,"name":1890},{"slug":57,"name":58},{"id":1931,"slug":1932,"title":1933,"excerpt":1934,"image":1935,"featured":44,"groupId":1936,"publishedAt":1937,"readingTime":1938,"views":48,"author":1939,"category":1940,"tags":1941},"633b6488-2a90-4632-86a5-9cff7ed52f08","javascript-event-loop-the-heart-of-non-blocking-concurrency-revisited-in-2025","JavaScript Event Loop: The Heart of Non-Blocking Concurrency — Revisited in 2025","The Event Loop is not just a simple loop, but a sophisticated coordination mechanism between Call Stack, queues, and runtime. Essential for JS concurrency, it enables thousands of asynchronous operations without blocking the UI. This guide explores its structure, differences between Node.js and browsers, common bugs, advanced techniques, and 2025 trends like Structured Concurrency and scheduler.yield().","https:\u002F\u002Fmfgiomkkbihnlpfdxwrt.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fblog-assets\u002Fmedia\u002Ffeatured\u002F1772847201637-featured-oreiro.jpeg","df6ca967-8130-4b2d-bcac-3532a150131e","2026-07-26T18:54:25.359533+00:00",6,{"name":50,"avatar":9},{"slug":13,"name":14,"color":15},[1942,1943],{"slug":1889,"name":1890},{"slug":1944,"name":1945},"nodejs","Node.js",{"en":40,"es":1947},"angular-22-reemplace-controlvalueaccessor-con-signal-forms"]