{"id":31926,"date":"2025-12-02T10:45:22","date_gmt":"2025-12-02T09:45:22","guid":{"rendered":"https:\/\/www.angulararchitects.io\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/"},"modified":"2025-12-09T10:11:02","modified_gmt":"2025-12-09T09:11:02","slug":"whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/","title":{"rendered":"What\u2019s new in Angular 21?"},"content":{"rendered":"<p>Angular 21, released at the end of 2025, brings several new features: Signal Forms as a reactive form API, Zone-less as the new standard model for change detection, vitest as a modern test environment, a set of ARIA directives for accessible components, and an enhanced MCP server for AI-supported development.<\/p>\n<p>This article provides an overview of these features using examples. <\/p>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/manfredsteyer\/angular21.git\">Source Code<\/a><\/p>\n<h2>Signal Forms<\/h2>\n<p>The currently experimental Signal Forms allow for efficient form design based on signals. This new API is similarly lightweight to template-driven forms, while offering the power of reactive forms. Furthermore, it enables the very simple creation of compound forms (&quot;Form Groups&quot;) and repeating groups (&quot;Form Arrays&quot;):<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/11\/signal-forms.png\" alt=\"Example application\" style=\"width:100%; max-width: 500px\"><\/p>\n<p>The implementation is based on a Signal with the desired form structure. For this signal, the <code>form<\/code> function generates a so-called <code>FieldTree<\/code>, which enables data binding to the form:<\/p>\n<pre><code class=\"language-ts\">@Component([\u2026])\nexport class FlightSearchComponent {\n  filter = signal({\n    from: &#039;Graz&#039;,\n    to: &#039;Hamburg&#039;,\n    details: {\n      maxLayovers: 0,\n      maxPrice: 200\n    },\n    layovers: [\n      { airport: &#039;&#039;, minDuration: 0}\n    ] as Layover[]\n  });\n\n  filterForm = form(this.filter, (path) =&gt; {\n    required(path.from);\n    minLength(path.from, 3);\n\n    required(path.to);\n    minLength(path.to, 3);\n\n    const allowed = [&#039;Graz&#039;, &#039;Hamburg&#039;, &#039;Paris&#039;];\n    validateAirport(path.from, allowed);\n  });\n\n  addLayover(): void {\n    this.filter.update(filter =&gt; ({\n      ...filter,\n      layovers: [\n        ...filter.layovers,\n        {\n          airport: &#039;&#039;,\n          minDuration: 0\n        }\n      ]\n    }));\n  }\n\n  search(): void {\n    const { from, to } = this.filterForm().value();\n\n    \/\/ Alternative\n    \/\/ const from = this.filterForm.from().value();\n    \/\/ const to = this.filterForm.to().value();\n    [\u2026]\n  }\n\n}<\/code><\/pre>\n<p>Technically, a <code>FieldTree<\/code> is a signal that represents the state of a form section -- an object, an array or a single property that is bound to a field. It manages information like <code>value<\/code>, <code>dirty<\/code>, and <code>errors<\/code>. For each nested property, such as <code>maxLayovers<\/code> and <code>maxPrice<\/code>, it contains another <code>FieldTree<\/code>.<\/p>\n<p>The directive <code>field<\/code> binds a <code>FieldTree<\/code> to an input field:<\/p>\n<pre><code class=\"language-html\">&lt;form&gt;\n    &lt;input [field]=&quot;filterForm.from&quot; \/&gt;\n    &lt;div&gt;{{ filterForm.from().errors() | json }}&lt;\/div&gt;\n\n    &lt;input [field]=&quot;filterForm.to&quot; \/&gt;\n    &lt;div&gt;{{ filterForm.to().errors() | json }}&lt;\/div&gt;\n\n    &lt;!-- &quot;Field Group&quot; --&gt;\n    &lt;input\n      [field]=&quot;filterForm.details.maxLayovers&quot;\n      type=&quot;number&quot;\n    \/&gt;\n    &lt;input\n      [field]=&quot;filterForm.details.maxPrice&quot;\n      type=&quot;number&quot;\n    \/&gt;\n\n    &lt;!-- &quot;Field Array&quot; --&gt;\n    @for (layover of filterForm.layovers; track $index) {\n        &lt;input [field]=&quot;layover.airport&quot; \/&gt;          \n        &lt;input\n            [field]=&quot;layover.minDuration&quot;\n            type=&quot;number&quot;\n        \/&gt;\n    }\n    [\u2026]\n&lt;\/form&gt;<\/code><\/pre>\n<p>For improved readability, the form markup shown has been reduced to the essential parts. The optional second parameter of <code>form<\/code> accepts a schema that primarily contains validation rules. Signal Forms comes with the usual rules such as <code>required<\/code> and <code>minLength<\/code>. Custom validation rules can be implemented using the <code>validate<\/code> function:<\/p>\n<pre><code class=\"language-ts\">function validateAirport(path: SchemaPath&lt;string&gt;, allowed: string[]) {\n  validate(path, (ctx) =&gt; {\n    if (allowed.includes(ctx.value())) {\n      return null;\n    }\n    return {\n      kind: &#039;airport_not_supported&#039;,\n      allowed,\n      actual: ctx.value(),\n    };\n  });\n}<\/code><\/pre>\n<p>In addition to validation rules, a schema can define debouncing or specify when certain fields should be deactivated.<\/p>\n<h2>More on Signal Forms<\/h2>\n<p>Please find more information on Signal Forms in my <a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/all-about-angulars-new-signal-forms\/\">article &quot;All About Angular's new Signal Forms&quot;<\/a>:<\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/all-about-angulars-new-signal-forms\/\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/09\/sujet-1-1024x536.png\" style=\"max-width:600px; width:100%\"><\/a><\/p>\n<h2>Zone-less by Default<\/h2>\n<p>From the very beginning, Zone.js has been the foundation for change detection in Angular. This library, included with the framework, uses monkey patching to modify existing browser objects such as <code>window<\/code>, <code>document<\/code>, and <code>Promise<\/code>. This allows it to determine when an event handler has been executed. After an event handler ran, Zone.js notifies the framework, which then checks the components for changes.<\/p>\n<p>Zone.js also has some disadvantages. These include the overhead of around 30 KB (compressed), which is usually negligible for business applications. More serious, however, is the intervention in existing browser objects, as this complicates troubleshooting and leads to less traceable stack traces. Furthermore, there is a risk that Zone.js will trigger change detection too frequently - it has no knowledge of whether the current event handler has actually modified bound data.<\/p>\n<p>The new zone-less change detection no longer requires Zone.js. To inform Angular about changes to bound properties, signals or observables must now be bound. The latter is done as usual via the <em>async<\/em> pipe. The objects managed by these reactive data structures should also be immutable so that Angular, in <em>OnPush<\/em> mode, can recognize which subcomponents need to be checked.<\/p>\n<p>Since Angular 21, Zone-less change detection is on by default. If you want to stick with Zone.js, add provideZoneChangeDetection to your providers when bootstrapping:<\/p>\n<pre><code class=\"language-ts\">\/\/ switching back to Zone-full CD\nbootstrapApplication(AppComponent, {\n  providers: [\n    provideZoneChangeDetection()\n  ],\n});<\/code><\/pre>\n<p>In this case, you also have to add <code>zone.js<\/code> to the polyfills in your <code>angular.json<\/code>.<\/p>\n<p>Generally, Zone-less should work wherever <code>OnPush<\/code> works. Nevertheless, it's advisable to thoroughly test the application in the new mode. Challenges might arise with third-party components that rely on the original behavior. In such cases, it's necessary to switch to a newer version (or wait for one).<\/p>\n<h2>Vitest: Migration and Fake Timer<\/h2>\n<p>The Angular team has been searching for a successor to the deprecated unit testing tool Karma for some time. They have now chosen the popular <a href=\"https:\/\/vitest.dev\/\">vitest<\/a>. New projects are automatically set up with the new, dedicated unit test builder (@angular\/build:unit-test).<\/p>\n<p>Karma support remains available for existing projects. An experimental schematic is available for automated migration:<\/p>\n<pre><code class=\"language-bash\">ng g @schematics\/angular:refactor-jasmine-vitest<\/code><\/pre>\n<p>The new vitest-based unit tests always run zone-less. Hence, ideally, the application should also be migrated to Zone-less when using vitest. Since most calls remain unchanged, existing knowledge about Angular testing can be transferred almost directly.<\/p>\n<p>However, a few things need to be changed. For example, the setup of spies changes slightly. <code>spyOn<\/code> is now, for instance, a method in the <code>vi<\/code> object:<\/p>\n<pre><code class=\"language-ts\">vi.spyOn(flightService, &#039;find&#039;);<\/code><\/pre>\n<p>Depending on the configuration, <code>vi<\/code> is either globally available or must be imported separately:<\/p>\n<pre><code class=\"language-ts\">import { vi } from &#039;vitest&#039;;<\/code><\/pre>\n<p>Spies in <code>vitest<\/code> delegate to the observed implementation by default - the call <code>.and.callThrough()<\/code> known from Jasmine is omitted. The object returned by <code>spyOn<\/code> also has a slightly different form, although the basic functionality remains unchanged. A mock implementation can thus be set up, for example, as follows:<\/p>\n<pre><code class=\"language-ts\">vi.spyOn(flightService, &#039;find&#039;)\n    .mockImplementation((_from, _to) =&gt; of([]));<\/code><\/pre>\n<p>In addition to these minor adjustments, vitest also brings a whole amount of new features familiar from Jest. These include snapshot testing, extensive import mocking capabilities, and parallel test execution.<\/p>\n<p>As Vitest always runs Zone-less in Angular, the notorious Zone.js testing utilities - including <code>fakeAsync<\/code> and <code>tick<\/code> cannot be used anymore. Affected tests must now be structured differently. For example, the test might replace asynchronous routines with synchronous mocks. This approach is also known from testing the <code>HttpClient<\/code>.<\/p>\n<p>Those who still want to &quot;fast-forward&quot; time can use the fake timers offered by vitest:<\/p>\n<pre><code class=\"language-ts\">import { TestBed } from &#039;@angular\/core\/testing&#039;;\nimport { debounceTime, Subject } from &#039;rxjs&#039;;\nimport { vi } from &#039;vitest&#039;;\nimport { toSignal } from &#039;@angular\/core\/rxjs-interop&#039;;\n\ndescribe(&#039;simulated input&#039;, () =&gt; {\n  beforeEach(() =&gt; {\n    vi.useFakeTimers();\n  });\n\n  it(&#039;is updated after debouncing&#039;, async () =&gt; {\n    await TestBed.runInInjectionContext(async () =&gt; {\n      const input = createInput();\n      input.set(&#039;Hallo&#039;);\n      await vi.runAllTimersAsync();\n      expect(input.value()).toBe(&#039;Hallo&#039;);\n    });\n  });\n});\n\n\/\/ Simulates debounced input\nfunction createInput() {\n  const input = new Subject&lt;string&gt;();\n  const inputSignal = toSignal(input.pipe(debounceTime(300)), {\n    initialValue: &#039;&#039;,\n  });\n  return {\n    value: inputSignal,\n    set(value: string) {\n      input.next(value);\n    },\n  };\n}<\/code><\/pre>\n<p>The function <code>beforeEach<\/code> function activates the fake timers for each test case in the suite. After the content is set, the timers for debouncing must be resolved before the test can read the new value. This is done using <code>runAllTimersAsync<\/code>.<\/p>\n<p>Unlike its synchronous alternative, <code>runAllTimers<\/code>, <code>runAllTimersAsync<\/code> not only resolves all timers but also waits for the microtasks they trigger. Examples of these are microtasks that arise from Promises in the timer callback.<\/p>\n<h2>Browser Mode in Vitest<\/h2>\n<p>Unlike Karma, vitest doesn't run in the browser by default, but directly in Node.js. The CLI integration simulates the DOM using the packages <code>happy-dom<\/code> or <code>jsdom<\/code>. One of these must be installed. If both are present, <code>happy-dom<\/code> takes precedence. While such tests are somewhat faster, they also run in an artificial environment.<\/p>\n<p>Therefore, vitest also offers a browser mode. In this mode, a provider is used for communication with the browser. The vitest team recommends the Playwright-based provider, especially since it accelerates execution through parallelization:<\/p>\n<pre><code class=\"language-bash\">npm install @vitest\/browser-playwright -D<\/code><\/pre>\n<p>To activate browser mode, the browsers to be used must be entered in the <code>angular.json<\/code> file:<\/p>\n<pre><code class=\"language-ts\">&quot;test&quot;: {\n  &quot;builder&quot;: &quot;@angular\/build:unit-test&quot;,\n  &quot;options&quot;: {\n    &quot;browsers&quot;: [&quot;Chromium&quot;]\n  },\n  &quot;configurations&quot;: {\n    &quot;ci&quot;: {\n      &quot;browsers&quot;: [&quot;ChromiumHeadless&quot;]\n    }\n  }\n}<\/code><\/pre>\n<p>The shown example is using Chromium. Additionally, for execution on the build server, it includes a <code>ci<\/code> configuration that uses the headless version of Chrome. To interact with the page, vitest provides a <code>page<\/code> object for browser mode:<\/p>\n<pre><code class=\"language-ts\">import { page } from &#039;vitest\/browser&#039;;\n[\u2026]\n\nit(&#039;should have a disabled search button w\/o params&#039;, async () =&gt; {\n\n  await page.getByLabelText(&#039;from&#039;).fill(&#039;&#039;);\n  await page.getByLabelText(&#039;to&#039;).fill(&#039;&#039;);\n\n  const button = page.getByRole(&#039;button&#039;, { name: &#039;search&#039; }).element() \n          as HTMLButtonElement;\n\n  const disabled = button.disabled;\n\n  expect(disabled).toBeTruthy();\n});<\/code><\/pre>\n<p>Aria properties are used to retrieve elements from the page. For example, the <code>getByLabelText(&#039;from&#039;)<\/code> method searches for an element with the attribute <code>aria-label=&quot;from&quot;<\/code>, and <code>getByRole(&#039;button&#039;, { name: &#039;search&#039; })<\/code> references a button with <code>aria-label=&quot;search&quot;<\/code>. The <code>name<\/code> parameter here refers to the <code>aria-label<\/code> attribute, not the <code>name<\/code> attribute.<\/p>\n<p>As the discussed example shows, the objects retrieved via <code>page<\/code> allow the simulation of user actions. They have methods such as <code>fill<\/code> for this purpose. Other methods not shown here include <code>clear<\/code> (clearing the field content), <code>click<\/code>, <code>dblClick<\/code>, <code>hover<\/code>, and <code>unhover<\/code>. Additional user actions are enabled by the <code>userEvent<\/code> object in the <code>vitest\/browser<\/code> package.<\/p>\n<p>When <em>ng test<\/em> is run in browser mode, the CLI opens the configured browser(s) and executes the tests within it:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/11\/vitest-browser-mode.png\" alt=\"Vitest Browser Mode\" \/><\/p>\n<p>The middle section displays the component being tested during test execution. Afterwards, the DOM in this area is reset so that subsequent tests can run without interference. However, when debugging, for example using a breakpoint in the browser's developer tools, the current state is visible there. This can be useful for troubleshooting.<\/p>\n<h2>More on Testing<\/h2>\n<p>Please find more information on Testing in our <a href=\"https:\/\/www.angulararchitects.io\/en\/training\/professional-angular-testing-playwright-edition\/\">Professional Angular Testing workshop<\/a>:<\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/training\/professional-angular-testing-playwright-edition\/\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/09\/sujet.png\" style=\"max-width:600px; width:100%\"><\/a><\/p>\n<h2>Angular Aria<\/h2>\n<p>The new @angular\/aria package introduces accessible Directives that implement <a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/patterns\/\">WAI-ARIA<\/a> patterns. Out of the box, they support keyboard navigation, ARIA attributes, focus management, and screen reader integration. These directives are not intended for direct use in applications, but rather as a foundation for custom component libraries with their own unique design.<\/p>\n<p>The following image shows an overview of the directives offered in version 21 on the left, and a tree view next to it. Since these directives are intentionally unstyled (headless), the styling from <em>angular.dev<\/em> has been applied to them:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/11\/aria-components.png\" alt=\"Overview of components\" \/><\/p>\n<p>The next listing, that has been slightly simplified and taken from the documentation,  implements a grid:<\/p>\n<pre><code class=\"language-html\">&lt;table ngGrid class=&quot;basic-data-table&quot;&gt;\n  &lt;thead&gt;\n    &lt;tr ngGridRow&gt;\n      &lt;th ngGridCell&gt;\n        &lt;input\n          ngGridCellWidget\n          aria-label=&quot;Select all rows&quot;\n          type=&quot;checkbox&quot;\n          [checked]=&quot;allSelected()&quot;\n          (change)=&quot;updateSelection($event)&quot;\n          #headerCheckbox\n        \/&gt;\n      &lt;\/th&gt;\n      &lt;th ngGridCell&gt;ID&lt;\/th&gt;\n      &lt;th ngGridCell&gt;Task&lt;\/th&gt;\n      &lt;th ngGridCell&gt;Priority&lt;\/th&gt;\n    &lt;\/tr&gt;\n  &lt;\/thead&gt;\n  &lt;tbody&gt;\n    @for (task of data(); track task.taskId) {\n    &lt;tr ngGridRow&gt;\n      &lt;td ngGridCell&gt;\n        &lt;input\n          ngGridCellWidget\n          aria-label=&quot;Select row {{$index + 1}}&quot;\n          type=&quot;checkbox&quot;\n          [(ngModel)]=&quot;task.selected&quot;\n        \/&gt;\n      &lt;\/td&gt;\n      &lt;td ngGridCell&gt;{{task.taskId}}&lt;\/td&gt;\n      &lt;td ngGridCell&gt;{{task.summary}}&lt;\/td&gt;\n      &lt;td ngGridCell&gt;{{task.priority}}&lt;\/td&gt;\n    &lt;\/tr&gt;\n    }\n  &lt;\/tbody&gt;\n&lt;\/table&gt;<\/code><\/pre>\n<p>Here is what this grid looks like:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/11\/aria-grid.png\" alt=\"Angular Aria Grid\" style=\"width:100%; max-width:300px\"><\/p>\n<p>The following directives work together to implement this grid: <em>Grid<\/em>, <em>GridRow<\/em>, <em>GridCell<\/em>, <em>GridCellWidget<\/em>. This rather long code example for a simple grid underscores that the directives provided are intended as flexible building blocks for your own reusable components.<\/p>\n<h2>Improved MCP server in Angular CLI<\/h2>\n<p>Version 21 also expands the MCP server integrated into the CLI. It now includes the following tools to assist with AI-assisted programming (Vibe Coding):<\/p>\n<ul>\n<li><strong>find_examples:<\/strong> Finds good code examples in a database curated by the Angular team.<\/li>\n<li><strong>get_best_practices:<\/strong> Delivers the Angular Best Practice Guide.<\/li>\n<li><strong>list_projects:<\/strong> Lists all projects in the current workspace.<\/li>\n<li><strong>onpush_zoneless_migration:<\/strong> Helps with migration to Zone-less.<\/li>\n<li><strong>search_documentation:<\/strong> Searches the official Angular documentation.<\/li>\n<li><strong>ai_tutor:<\/strong> Launches an interactive <a href=\"https:\/\/angular.dev\/ai\/ai-tutor\">AI tutor<\/a> to help you learn Angular. A prompt like &quot;Start AI Tutor&quot; in your Vibe coding tool of choice should be enough to get started.<\/li>\n<li><strong>modernize:<\/strong> Helps to migrate existing code to modern Angular. This tool is currently experimental.<\/li>\n<\/ul>\n<p>The MCP server can be started directly using <code>ng mcp<\/code>. To also activate experimental tools, the <code>--experimental-tool<\/code> switch, or <code>-E<\/code> for short, must be specified. When using code generation tools such as Cursor, Firebase Studio, or Gemini CLI, this command must be configured. Cursor, for example, requires a file named <code>.cursor\/mcp.json<\/code>:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;mcpServers&quot;: {\n    &quot;angular-cli&quot;: {\n      &quot;command&quot;: &quot;npx&quot;,\n      &quot;args&quot;: [&quot;-y&quot;, &quot;@angular\/cli&quot;, &quot;mcp&quot;]\n    }\n  }\n}<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>Version 21 continues the modernization of the framework: With Signal Forms and standard zone-less change detection, the focus on responsiveness, developer experience, and performance is strengthened. Vitest replaces the aging Karma and introduces modern testing features such as fake timers, snapshots, and parallelization.<\/p>\n<p>The new package @angular\/aria provides a solid foundation for accessible component libraries. Finally, the MCP server, integrated into the CLI, comes with new tools that support vibe coding.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Angular 21, released at the end of 2025, brings several new features: Signal Forms as a reactive form API, Zone-less as the new standard model for change detection, vitest as a modern test environment, a set of ARIA directives for accessible components, and an enhanced MCP server for AI-supported development. This article provides an overview [&hellip;]<\/p>\n","protected":false},"author":25,"featured_media":31921,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[18],"tags":[],"class_list":["post-31926","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>What\u2019s new in Angular 21? - ANGULARarchitects<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"What\u2019s new in Angular 21? - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"Angular 21, released at the end of 2025, brings several new features: Signal Forms as a reactive form API, Zone-less as the new standard model for change detection, vitest as a modern test environment, a set of ARIA directives for accessible components, and an enhanced MCP server for AI-supported development. This article provides an overview [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2025-12-02T09:45:22+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-12-09T09:11:02+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/Angular-21-1024x536.png\" \/>\n<meta name=\"author\" content=\"Manfred Steyer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/Angular-21-1024x536.png\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/\"},\"author\":{\"name\":\"Manfred Steyer\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\"},\"headline\":\"What\u2019s new in Angular 21?\",\"datePublished\":\"2025-12-02T09:45:22+00:00\",\"dateModified\":\"2025-12-09T09:11:02+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/\"},\"wordCount\":1609,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/\",\"name\":\"What\u2019s new in Angular 21? - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg\",\"datePublished\":\"2025-12-02T09:45:22+00:00\",\"dateModified\":\"2025-12-09T09:11:02+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg\",\"width\":1000,\"height\":683},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"What\u2019s new in Angular 21?\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\",\"name\":\"Manfred Steyer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"What\u2019s new in Angular 21? - ANGULARarchitects","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/","og_locale":"en_US","og_type":"article","og_title":"What\u2019s new in Angular 21? - ANGULARarchitects","og_description":"Angular 21, released at the end of 2025, brings several new features: Signal Forms as a reactive form API, Zone-less as the new standard model for change detection, vitest as a modern test environment, a set of ARIA directives for accessible components, and an enhanced MCP server for AI-supported development. This article provides an overview [&hellip;]","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/","og_site_name":"ANGULARarchitects","article_published_time":"2025-12-02T09:45:22+00:00","article_modified_time":"2025-12-09T09:11:02+00:00","og_image":[{"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/Angular-21-1024x536.png","type":"","width":"","height":""}],"author":"Manfred Steyer","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/Angular-21-1024x536.png","twitter_misc":{"Written by":"Manfred Steyer","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/"},"author":{"name":"Manfred Steyer","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a"},"headline":"What\u2019s new in Angular 21?","datePublished":"2025-12-02T09:45:22+00:00","dateModified":"2025-12-09T09:11:02+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/"},"wordCount":1609,"commentCount":0,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/","name":"What\u2019s new in Angular 21? - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg","datePublished":"2025-12-02T09:45:22+00:00","dateModified":"2025-12-09T09:11:02+00:00","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_2243296597.jpg","width":1000,"height":683},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/whats-new-in-angular-21-signal-forms-zone-less-vitest-angular-aria-cli-with-mcp-server\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"What\u2019s new in Angular 21?"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a","name":"Manfred Steyer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","caption":"Manfred Steyer"}}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/31926","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=31926"}],"version-history":[{"count":5,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/31926\/revisions"}],"predecessor-version":[{"id":31980,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/31926\/revisions\/31980"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/31921"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=31926"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=31926"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=31926"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}