<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/atom-premium.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0">
  <feedpress:locale>en</feedpress:locale>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs-web-angular-61850c76637b3.jpg</logo>
  <title type="text">Telerik Blogs | Web | Angular</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:c9587aa7-6488-4999-ab92-90eeabc12f99;id=2368</id>
  <updated>2026-10-22T19:10:29Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs/web-angular"/>
  <entry>
    <id>urn:uuid:6d348dbb-b643-4054-a00c-a61394e43d07</id>
    <title type="text">Moving to Angular Zoneless</title>
    <summary type="text">Instead of change detection after every asynchronous task, Zoneless Angular applications use Signals to detect relevant state changes. Here’s how to set that up greenfield and what to watch for as you migrate to Zoneless.</summary>
    <published>2026-10-21T19:54:21Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17502259/moving-angular-zoneless"/>
    <content type="text"><![CDATA[<p><span class="featured">Instead of change detection after every asynchronous task, Zoneless Angular applications use Signals to detect relevant state changes. Here’s how to set that up greenfield and what to watch for as you migrate to Zoneless.</span></p>
<p>If you have worked with Angular, you have probably seen components react and update the UI after a click, timer or HTTP request without calling <a target="_blank" href="https://angular.dev/api/core/ChangeDetectorRef#detectChanges"><code>detectChanges()</code></a>. Angular has provided this convenience for years, so you usually do not need to think about what makes it work.</p>
<p>ZoneJS and Angular’s <a target="_blank" href="https://angular.dev/api/core/NgZone">NgZone</a> make this possible, and we can make reactive apps easy, but NgZone makes Angular check more than necessary. As your app grows, you may start asking a simple question: can Angular update only the views that really changed?</p>
<p>Modern Angular gives you that option with Zoneless change detection. <a target="_blank" href="https://www.telerik.com/blogs/angular-22-evolution-modern-angular">Signals</a> and other Angular APIs can tell Angular when a view needs an update. This lets you build applications without depending on NgZone to watch every asynchronous task.</p>
<p>Today, let’s build together a small product dashboard with <a target="_blank" href="https://www.telerik.com/blogs/angular-22-evolution-modern-angular">Angular 22</a> and <a target="_blank" href="https://www.telerik.com/kendo-angular-ui">Progress Kendo UI for Angular</a>. First, we will learn how the old model works. Then we will use Signals and httpResource to build a Zoneless dashboard. Finally, we will review common problems that can appear during a Zoneless migration.</p>
<p>Let’s start with what made the old model work: NgZone and ZoneJS.</p>
<h2 id="what-are-ngzone-and-zonejs">What Are NgZone and ZoneJS?</h2>
<p>When we talk about NgZone, it’s a service that connects Angular to the ZoneJS library. ZoneJS patches many browser APIs, including timers, events and promises. When one of these operations finishes, Angular can use that notification to schedule change detection.</p>
<p>Think of ZoneJS as a central alarm. It does not know which component changed. As a result, Angular may check a large part of the component tree after the alarm rings. This is easy to use because developers do not always need to tell Angular that state changed.</p>
<p>For example, this code changes a normal property from a timer:</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">NotificationComponent</span> <span class="token punctuation">{</span>
  badgeCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

  <span class="token function">ngOnInit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>badgeCount<span class="token operator">++</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>With ZoneJS, the timer can trigger Angular change detection, and the badge updated property can appear in the template even though the component did not explicitly notify Angular. That convenience has a cost, especially in larger applications:</p>
<ul>
<li>Angular can perform checks that do not produce a visible change.</li>
<li>ZoneJS injects runtime code into the application.</li>
<li>Stack traces can include framework scheduling details that make debugging painful.</li>
</ul>
<p>The performance cost depends on the application. Zoneless does not promise that every application will become faster. It makes change detection more explicit and can reduce unnecessary work when the application exposes its state clearly.</p>
<p>We have seen why Angular used NgZone and what ZoneJS added. Now, let’s look at how Angular knows that a Zoneless view needs an update.</p>
<h2 id="how-does-zoneless-change-detection-work">How Does Zoneless Change Detection Work?</h2>
<p>Zoneless Angular does not need NgZone to watch browser tasks. Instead, Angular schedules change detection when one of its APIs sends a notification. Examples include a signal read by a template, a bound event listener, a component input and <a target="_blank" href="https://angular.dev/api/core/ChangeDetectorRef#markForCheck"><code>ChangeDetectorRef.markForCheck()</code></a>. Signals are the most common option. They store state and tell Angular which views use it.</p>
<p>The difference is simple. ZoneJS can trigger a broad check after a timer, event or promise, but Zoneless Angular reacts to an Angular notification and updates the views connected to that state. Not every value must become a <a target="_blank" href="https://angular.dev/api/core/signal"><code>signal()</code></a>. However, values used by a template must change through a mechanism that Angular can observe.</p>
<p>That is the idea we will use in the dashboard: a user event changes a signal, the resource loads new data and the template updates.</p>
<h2 id="build-a-zoneless-product-dashboard">Build a Zoneless Product Dashboard</h2>
<p>Let’s build a small product dashboard with a category filter and a Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid">Angular Grid</a>. Keeping the example small makes it easier to follow the change detection path.</p>
<h2 id="step-1-create-the-angular-project">Step 1: Create the Angular Project</h2>
<p>Create the project with Angular CLI. Run this command in a terminal:</p>
<pre class=" language-bash"><code class="prism  language-bash">npx @angular/cli@22 new zoneless-kendo-dashboard --standalone --style<span class="token operator">=</span>scss --ssr<span class="token operator">=</span>false
<span class="token function">cd</span> zoneless-kendo-dashboard
</code></pre>
<p>The first command creates an Angular 22 project. It includes standalone components and SCSS, and it disables server-side rendering. The second command moves into the project folder. You should now have files such as <code>src/app/app.ts</code>, <code>src/app/app.html, and</code>src/app/app.scss`.</p>
<p>Start the new application to check that the project works before continuing:</p>
<pre class=" language-bash"><code class="prism  language-bash">ng serve
</code></pre>
<p>Open <code>http://localhost:4200</code> in your browser. You should see the default Angular application. Stop the server with <code>Ctrl+C</code> before continuing.</p>
<p>New Angular projects use the defaults for that Angular version. Before using the next step in an existing project, check its Angular version and the generated <code>app.config.ts</code>.</p>
<p>Next, check the change detection configuration Angular generated.</p>
<h3 id="zoneless-in-angular-22">Zoneless in Angular 22</h3>
<p>In this example, you are using Angular 22, which uses zoneless change detection by default. The generated <code>app.config.ts</code> does not need a <code>provideZonelessChangeDetection()</code> call. The project also does not include <code>zone.js</code> as a dependency.</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> ApplicationConfig<span class="token punctuation">,</span> provideBrowserGlobalErrorListeners <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideHttpClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/router'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> routes <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.routes'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> appConfig<span class="token punctuation">:</span> ApplicationConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token function">provideBrowserGlobalErrorListeners</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function">provideRouter</span><span class="token punctuation">(</span>routes<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function">provideHttpClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<p>We add <a target="_blank" href="https://angular.dev/api/common/http/provideHttpClient"><code>provideHttpClient()</code></a> because this dashboard will use <code>httpResource</code> to call the Fake Store API. <code>provideHttpClient()</code> is an HTTP provider, not a Zoneless provider, but if your application does not make HTTP requests, you can leave it out.</p>
<h3 id="what-about-older-angular-versions">What About Older Angular Versions?</h3>
<p>This section is only for older versions like Angular 20, because not every project uses the latest Angular version. Angular 20 and later support opting into Zoneless with <a target="_blank" href="https://angular.dev/api/core/provideZonelessChangeDetection"><code>provideZonelessChangeDetection()</code></a>. New projects created with Angular 21 and later use Zoneless by default, but existing applications still need a migration review.</p>
<p>For an Angular 20 application that still uses ZoneJS, add the provider in <code>app.config.ts</code>, then review the project configuration. If you use an older Angular version, check its version-specific migration guide because the provider and setup can be different:</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> provideZonelessChangeDetection <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> appConfig<span class="token punctuation">:</span> ApplicationConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token function">provideZonelessChangeDetection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<p>After checking the application, tests and third-party libraries, remove <code>zone.js</code> and <code>zone.js/testing</code> from <code>angular.json</code> or the polyfills configuration. Only then remove the package:</p>
<pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> uninstall zone.js
</code></pre>
<p>Now you know how to set up Zoneless for the old version; now let’s continue with our project in Angular 22 and add Kendo UI!</p>
<h2 id="step-2-install-kendo-ui-components">Step 2: Install Kendo UI Components</h2>
<p>Now that the Angular project is ready, add the UI components that users will use. Kendo UI will provide the category selector and product grid, so install the packages from the project folder:</p>
<pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> @progress/kendo-angular-grid @progress/kendo-angular-dropdowns
<span class="token function">npm</span> <span class="token function">install</span> @progress/kendo-theme-default @angular/animations @angular/localize
</code></pre>
<p>Check Kendo UI for Angular compatibility documentation when choosing package versions. Kendo UI components require Angular’s animation and localization runtimes, so install <code>@angular/animations</code> and <code>@angular/localize</code> as part of the setup.</p>
<p>Because Kendo UI uses Angular localization internally, add the localize runtime as a build polyfill in <code>angular.json</code>:</p>
<pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>
  <span class="token string">"polyfills"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token string">"@angular/localize/init"</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Then load the Kendo UI theme in <code>src/styles.scss</code>:</p>
<pre class=" language-scss"><code class="prism  language-scss"><span class="token keyword">@import</span> <span class="token string">'@progress/kendo-theme-default/dist/all.css'</span><span class="token punctuation">;</span>
</code></pre>
<p>Kendo UI gives you ready-to-use components such as the <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/dropdownlist">Angular DropDownList</a> and Grid. Before you use all their features, review the Angular bundle budget. The application can compile successfully even when its bundle exceeds the default budget, so update <code>angular.json</code> after measuring the result:</p>
<pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>
  <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"initial"</span><span class="token punctuation">,</span>
  <span class="token string">"maximumWarning"</span><span class="token punctuation">:</span> <span class="token string">"5MB"</span><span class="token punctuation">,</span>
  <span class="token string">"maximumError"</span><span class="token punctuation">:</span> <span class="token string">"6MB"</span>
<span class="token punctuation">}</span>
</code></pre>
<p>This change gives the Kendo UI Grid and DropDownList enough space within the bundle budget. Do not increase the budget in production without first measuring the bundle. Review imports, themes and lazy loading as the application grows. In this project, the generated bundle is about 4.4 MB, so the default <code>500kB</code> warning and <code>1MB</code> error limits are too low for these components.</p>
<p>The project is ready for the dashboard UI. Next, connect the product request to a signal so a category change can load new data.</p>
<h2 id="step-3-fetch-products-with-httpresource">Step 3: Fetch Products with httpResource</h2>
<p>The UI dependencies are in place, but the grid still needs data. Use <a target="_blank" href="https://angular.dev/api/common/http/httpResource"><code>httpResource</code></a> to connect the HTTP request to Angular’s reactive APIs. If the signal read by the request function changes, the resource starts a new request and exposes loading, error and value state.</p>
<p>Create <code>src/app/product.service.ts</code>. Run this command from the project folder to generate the file:</p>
<pre class=" language-bash"><code class="prism  language-bash">ng generate <span class="token function">service</span> product
</code></pre>
<p>Replace the generated service with this code:</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> httpResource <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Service<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Product</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  price<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  description<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Service</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ProductService</span> <span class="token punctuation">{</span>
  readonly selectedCategory <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token string">'all'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly productsResource <span class="token operator">=</span> httpResource<span class="token operator">&lt;</span>Product<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> category <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">selectedCategory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> category <span class="token operator">===</span> <span class="token string">'all'</span>
      <span class="token operator">?</span> <span class="token string">'https://fakestoreapi.com/products'</span>
      <span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`https://fakestoreapi.com/products/category/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>category<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>The service does not inject <a target="_blank" href="https://angular.dev/api/common/http/HttpClient"><code>HttpClient</code></a> because <code>httpResource</code> handles the request. The signal is read inside the resource function, so changing the selected category changes the resource URL. This example uses <a target="_blank" href="https://angular.dev/api/core/Service"><code>@Service()</code></a> because it is a root-level service that uses <code>inject()</code>.</p>
<p>The service now owns the reactive product request. That gives the component one clear source for its server state.</p>
<h2 id="step-4-connect-kendo-ui-components-with-signals">Step 4: Connect Kendo UI Components with Signals</h2>
<p>The service is ready. Now we connect the Kendo UI <strong>DropDownList</strong> (category filter) and <strong>Grid</strong> (product list) to the signals from <code>ProductService</code>. The template reads signals directly—<code>[value]="selectedCategory()"</code> and <code>[data]="products()"</code>—so when a signal changes, Kendo UI updates without NgZone.</p>
<p>The <strong>Kendo UI Grid</strong> gives you virtual scrolling, sorting, filtering and column resizing out of the box, all reactive to signal changes. The <strong>DropDownList</strong> handles keyboard navigation, filtering and accessibility without extra code.</p>
<p>Think of it like a direct wire: the signal is the switch, the Kendo UI component is the light. When you flip the switch, the light turns on. No central alarm (ZoneJS) needed.</p>
<p>The current Angular CLI creates <code>src/app/app.ts</code>, <code>src/app/app.html</code>, and <code>src/app/app.scss</code>. Update these files separately. This matches the structure the CLI generates and shows where each part of the component belongs.</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span>
  Component<span class="token punctuation">,</span>
  computed<span class="token punctuation">,</span>
  effect<span class="token punctuation">,</span>
  inject<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DropDownListModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-dropdowns'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> GridModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-grid'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ProductService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./product.service'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-root'</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>DropDownListModule<span class="token punctuation">,</span> GridModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">'./app.html'</span><span class="token punctuation">,</span>
  styleUrl<span class="token punctuation">:</span> <span class="token string">'./app.scss'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly productService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ProductService<span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly categories <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token string">'all'</span><span class="token punctuation">,</span>
    <span class="token string">'electronics'</span><span class="token punctuation">,</span>
    <span class="token string">'jewelery'</span><span class="token punctuation">,</span>
    <span class="token string">"men's clothing"</span><span class="token punctuation">,</span>
    <span class="token string">"women's clothing"</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  readonly selectedCategory <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>productService<span class="token punctuation">.</span>selectedCategory<span class="token punctuation">;</span>
  readonly productsResource <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>productService<span class="token punctuation">.</span>productsResource<span class="token punctuation">;</span>
  readonly products <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>productsResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      document<span class="token punctuation">.</span>title <span class="token operator">=</span> <span class="token template-string"><span class="token string">`Products - </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">selectedCategory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">onCategoryChange</span><span class="token punctuation">(</span>category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>productService<span class="token punctuation">.</span>selectedCategory<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>category<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Now replace the content of <code>src/app/app.html</code> with the dashboard template:</p>
<pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Angular Zoneless Product Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Signals, httpResource, and Kendo UI working together.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>controls<span class="token punctuation">"</span></span> <span class="token attr-name">aria-labelledby</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>filter-title<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>filter-title<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Filter products<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-dropdownlist</span>
      <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Filter by category<span class="token punctuation">"</span></span>
      <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>categories<span class="token punctuation">"</span></span>
      <span class="token attr-name">[value]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>selectedCategory()<span class="token punctuation">"</span></span>
      <span class="token attr-name">(valueChange)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onCategoryChange($event)<span class="token punctuation">"</span></span>
    <span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-dropdownlist</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>category-summary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    Available categories:
    @for (category of categories; track category) {
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>{{ category }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    }
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

  @if (productsResource.isLoading()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>status<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Loading products...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  } @else if (productsResource.error()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>status error<span class="token punctuation">"</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      The products could not be loaded. Try again later.
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  } @else {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span> <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>products()<span class="token punctuation">"</span></span> <span class="token attr-name">[height]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>400<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ID<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>title<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Product<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>category<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Category<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>160<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>price<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Price<span class="token punctuation">"</span></span> <span class="token attr-name">format</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{0:c}<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>120<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
}
}
</code></pre>
<p>Let’s take a look at the HTML code. The DropDownList reads <code>selectedCategory()</code> with <code>[value]</code>. The Grid reads <code>products()</code> with <code>[data]</code>. And the <code>(valueChange)</code> event writes back to the signal. That is the whole loop. Read the signal, update UI, write signal, done!</p>
<p>The <code>effect()</code> syncs the category to the browser title. A side effect outside the template, but still driven by a signal.</p>
<p>Next, replace the content of <code>src/app/app.scss</code> with the dashboard styles:</p>
<pre class=" language-scss"><code class="prism  language-scss"><span class="token selector">.container </span><span class="token punctuation">{</span>
  <span class="token property">max-width</span><span class="token punctuation">:</span> <span class="token number">900</span>px<span class="token punctuation">;</span>
  <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">2</span>rem auto<span class="token punctuation">;</span>
  <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
  <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui, sans-serif<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">header </span><span class="token punctuation">{</span>
  <span class="token property">border-bottom</span><span class="token punctuation">:</span> <span class="token number">2</span>px solid <span class="token hexcode">#d9e2ec</span><span class="token punctuation">;</span>
  <span class="token property">margin-bottom</span><span class="token punctuation">:</span> <span class="token number">2</span>rem<span class="token punctuation">;</span>
  <span class="token property">padding-bottom</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.controls </span><span class="token punctuation">{</span>
  <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  <span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
  <span class="token property">margin-bottom</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.category-summary span </span><span class="token punctuation">{</span>
  <span class="token property">margin-right</span><span class="token punctuation">:</span> <span class="token number">0.75</span>rem<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.status </span><span class="token punctuation">{</span>
  <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">2</span>rem<span class="token punctuation">;</span>
  <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.error </span><span class="token punctuation">{</span>
  <span class="token property">color</span><span class="token punctuation">:</span> <span class="token hexcode">#a61b1b</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>The <code>@if</code> block reacts to resource state, while <code>@for</code> renders the category summary. The key point is that the template reads Signals. When <code>selectedCategory</code> changes, the resource and the affected view receive Angular notifications without relying on NgZone.</p>
<p>Run the application by running the following command:</p>
<pre class=" language-bash"><code class="prism  language-bash">ng serve
</code></pre>
<p>Open <code>http://localhost:4200</code>. You should first see the loading message, followed by the product grid. Select <code>all</code> in the dropdown and confirm that the loading message appears again before the filtered products are displayed.</p>
<p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/angular-zoneless-dashboard.gif?sfvrsn=47455500_2" alt="Angular Zoneless Product Dashboard filtering"></p>
<p>Done!! Our flow is working from the signal to the resource and then to the template. But my next question is: how do we test this flow without leaving an HTTP request pending? </p>
<h2 id="test-zoneless-components">Test Zoneless Components</h2>
<p>In a Zoneless test, you need to wait for Angular’s scheduled work and control asynchronous dependencies. A pending HTTP request is not stable work, so flush it through <a target="_blank" href="https://angular.dev/api/common/http/testing/HttpTestingController"><code>HttpTestingController</code></a>. This lets you test the same signal-to-view path without depending on the real API.</p>
<p>Here is a focused Vitest test for the initial product request in <code>src/app/app.spec.ts</code>. The test uses <a target="_blank" href="https://angular.dev/api/common/http/testing/provideHttpClientTesting"><code>provideHttpClientTesting()</code></a> to control the request.</p>
<p>The Zoneless provider is included in TestBed to make the setup explicit, even though Angular 22 enables it by default in a new application:</p>
<p>The following snippet copy and replace the app.spec.ts file:</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> provideHttpClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>
  HttpTestingController<span class="token punctuation">,</span>
  provideHttpClientTesting<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http/testing'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideZonelessChangeDetection <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ComponentFixture<span class="token punctuation">,</span> TestBed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core/testing'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideAnimationsAsync <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/platform-browser/animations/async'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> App <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app'</span><span class="token punctuation">;</span>

<span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'App in a zoneless application'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> fixture<span class="token punctuation">:</span> ComponentFixture<span class="token operator">&lt;</span>App<span class="token operator">&gt;</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> httpTesting<span class="token punctuation">:</span> HttpTestingController<span class="token punctuation">;</span>

  <span class="token function">beforeEach</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> TestBed<span class="token punctuation">.</span><span class="token function">configureTestingModule</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>App<span class="token punctuation">]</span><span class="token punctuation">,</span>
      providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token function">provideZonelessChangeDetection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token function">provideAnimationsAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token function">provideHttpClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token function">provideHttpClientTesting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">compileComponents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    fixture <span class="token operator">=</span> TestBed<span class="token punctuation">.</span><span class="token function">createComponent</span><span class="token punctuation">(</span>App<span class="token punctuation">)</span><span class="token punctuation">;</span>
    httpTesting <span class="token operator">=</span> TestBed<span class="token punctuation">.</span><span class="token function">inject</span><span class="token punctuation">(</span>HttpTestingController<span class="token punctuation">)</span><span class="token punctuation">;</span>
    fixture<span class="token punctuation">.</span><span class="token function">detectChanges</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">afterEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    httpTesting<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'renders products after the HTTP request settles'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> request <span class="token operator">=</span> httpTesting<span class="token punctuation">.</span><span class="token function">expectOne</span><span class="token punctuation">(</span>
      <span class="token string">'https://fakestoreapi.com/products'</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    request<span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
        title<span class="token punctuation">:</span> <span class="token string">'Test product'</span><span class="token punctuation">,</span>
        price<span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">,</span>
        category<span class="token punctuation">:</span> <span class="token string">'electronics'</span><span class="token punctuation">,</span>
        description<span class="token punctuation">:</span> <span class="token string">'A product for the test'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> fixture<span class="token punctuation">.</span><span class="token function">whenStable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">expect</span><span class="token punctuation">(</span>fixture<span class="token punctuation">.</span>nativeElement<span class="token punctuation">.</span>textContent<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toContain</span><span class="token punctuation">(</span><span class="token string">'Test product'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'loads products when the category changes'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> initialRequest <span class="token operator">=</span> httpTesting<span class="token punctuation">.</span><span class="token function">expectOne</span><span class="token punctuation">(</span>
      <span class="token string">'https://fakestoreapi.com/products'</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
    initialRequest<span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> fixture<span class="token punctuation">.</span><span class="token function">whenStable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    fixture<span class="token punctuation">.</span>componentInstance<span class="token punctuation">.</span><span class="token function">onCategoryChange</span><span class="token punctuation">(</span><span class="token string">"men's clothing"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    fixture<span class="token punctuation">.</span><span class="token function">detectChanges</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> request <span class="token operator">=</span> httpTesting<span class="token punctuation">.</span><span class="token function">expectOne</span><span class="token punctuation">(</span>
      <span class="token string">"https://fakestoreapi.com/products/category/men's%20clothing"</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    request<span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        id<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
        title<span class="token punctuation">:</span> <span class="token string">'Test clothing product'</span><span class="token punctuation">,</span>
        price<span class="token punctuation">:</span> <span class="token number">20</span><span class="token punctuation">,</span>
        category<span class="token punctuation">:</span> <span class="token string">"men's clothing"</span><span class="token punctuation">,</span>
        description<span class="token punctuation">:</span> <span class="token string">'A clothing product for the test'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> fixture<span class="token punctuation">.</span><span class="token function">whenStable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">expect</span><span class="token punctuation">(</span>fixture<span class="token punctuation">.</span>nativeElement<span class="token punctuation">.</span>textContent<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toContain</span><span class="token punctuation">(</span><span class="token string">'Test clothing product'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>What does this code do? Well,<br>
<a target="_blank" href="https://angular.dev/api/core/testing/ComponentFixture#detectChanges"><code>fixture.detectChanges()</code></a> starts the initial render and request. <code>flush()</code> completes the controlled request. And <a target="_blank" href="https://angular.dev/api/core/testing/ComponentFixture#whenStable"><code>whenStable()</code></a> waits for Angular to finish the resulting render work. In other words, call <code>whenStable()</code> after you resolve the HTTP request instead of leaving it pending.</p>
<p>To run our test, use <code>npm run test</code>:</p>
<p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/angular-zoneless-test.png?sfvrsn=9149ef08_2" alt="Code screen zoneless-kendo-dashboard test run"></p>
<p>Yeah!! Our test now covers the initial request and a category change. So we have our app with Zoneless and testing, but this was a greenfield. Let me share with you the common migration problems I have encountered most often.</p>
<h2 id="common-migration-issues">Common Migration Issues</h2>
<p>Before we finish, let me share some of the most common problems I have found while migrating real Angular applications to Zoneless. This is not a complete list of every possible problem. These are the cases I’ve seen in real projects, and they are a good place to start when you review your own application.</p>
<p>These examples cover different migration boundaries. The practical fix depends on where the state enters the application.</p>
<h3 id="a-timer-changes-state-but-the-ui-does-not-update">A Timer Changes State But the UI Does Not Update</h3>
<p>A normal property changed by <code>setInterval()</code> does not notify Angular in a Zoneless application. Use a signal for state read by the template, and clean up the timer when the component is destroyed:</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> DestroyRef<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-notification'</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token string">'{{ badgeCount() }}'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">NotificationComponent</span> <span class="token punctuation">{</span>
  readonly badgeCount <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly destroyRef <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>DestroyRef<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> timer <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>badgeCount<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">this</span><span class="token punctuation">.</span>destroyRef<span class="token punctuation">.</span><span class="token function">onDestroy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">clearInterval</span><span class="token punctuation">(</span>timer<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>For RxJS streams, use <code>takeUntilDestroyed()</code> when a manual subscription must be tied to the component lifecycle.</p>
<p>The timer example is the simplest case: template state needs an Angular notification.</p>
<h3 id="a-manual-rxjs-subscription-leaves-a-stale-view">A Manual RxJS Subscription Leaves a Stale View</h3>
<p>An Observable emission does not automatically make a normal component property reactive. Prefer <a target="_blank" href="https://angular.dev/ecosystem/rxjs-interop/to-signal"><code>toSignal()</code></a> when the value belongs in a template:</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> Service<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> toSignal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core/rxjs-interop'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> interval<span class="token punctuation">,</span> map <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs'</span><span class="token punctuation">;</span>

@<span class="token function">Service</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">WeatherService</span> <span class="token punctuation">{</span>
  readonly temperature$ <span class="token operator">=</span> <span class="token function">interval</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token number">72</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-weather'</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token string">'&lt;p&gt;{{ temperature() }} F&lt;/p&gt;'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">WeatherComponent</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly weatherService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>WeatherService<span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly temperature <span class="token operator">=</span> <span class="token function">toSignal</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>weatherService<span class="token punctuation">.</span>temperature$<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    initialValue<span class="token punctuation">:</span> <span class="token number">72</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p><code>toSignal()</code> subscribes to the Observable, exposes the latest value as a signal, and connects the value to the component lifecycle. If you need a manual subscription to perform an action in code, use <a target="_blank" href="https://angular.dev/ecosystem/rxjs-interop/take-until-destroyed"><code>takeUntilDestroyed()</code></a> and update a signal inside the callback.</p>
<p>This keeps the stream connected to the template without calling manual change detection. It is a useful pattern when an existing application already uses RxJS.</p>
<h3 id="a-third-party-widget-does-not-notify-angular">A Third-Party Widget Does Not Notify Angular</h3>
<p>Maps, charts, editors and older wrappers may emit callbacks outside Angular’s reactive model. First, check whether the package has a current Angular integration. If it does not, isolate the integration and update a signal at the boundary:</p>
<pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-map'</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token string">'&lt;p&gt;Selected location: {{ selectedLocation() }}&lt;/p&gt;'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MapComponent</span> <span class="token punctuation">{</span>
  readonly selectedLocation <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">onMapSelectionChanged</span><span class="token punctuation">(</span>selection<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>selectedLocation<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>selection<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Because the template reads <code>selectedLocation()</code>, updating the signal already notifies Angular in a Zoneless application. If an external callback updates ordinary component state instead, use <code>markForCheck()</code> at that boundary. I use <code>detectChanges()</code> only for a narrow integration boundary, because it runs change detection immediately and should not become the default solution throughout the application.</p>
<p>These are the boundaries I have encountered most often. They are not the only cases, but they are a useful starting point for a Zoneless migration.</p>
<h2 id="a-practical-migration-path">A Practical Migration Path</h2>
<p>When moving an existing application from ZoneJS to Zoneless, first check the Angular version and read the migration guide for that version. This shows whether the application needs <code>provideZonelessChangeDetection()</code> explicitly. Before removing anything, search the project for NgZone, <code>zone.js</code>, <code>zone.js/testing</code> and code that depends on <code>onStable</code> or <code>onMicrotaskEmpty</code>.</p>
<p>Next, follow the state that each feature exposes to its template. Please pay special attention to values changed by timers, external callbacks and manual subscriptions. If a normal property is doing that work, move it to <code>signal()</code>, <code>computed()</code>, <code>toSignal()</code> or another Angular notification mechanism. Also check third-party libraries such as maps, charts, dialogs, date pickers and editors, because their callbacks may need an explicit integration boundary.</p>
<p>Keep the migration small enough to test as you go. Once the application and its dependencies no longer rely on ZoneJS, remove it from the project configuration and uninstall the package. Finally, measure the result with the browser performance tools. Do not assume that Zoneless alone will improve every metric.</p>
<h2 id="recap">Recap</h2>
<p>The main idea is this: Zoneless applications do not depend on NgZone and ZoneJS to schedule change detection after every asynchronous task. Instead, the application exposes template state through Signals and other Angular APIs that can tell Angular when relevant state changes.</p>
<p>In my experience, an incremental migration is the most practical approach. Move one feature, test it, inspect its integrations and then continue to the next feature. This makes the application’s real change detection dependencies easier to see without turning the whole migration into one large debugging session.</p>
<p>Now it is your turn to try the same model with a small piece of client-side state.</p>
<h2 id="open-challenge">Open Challenge</h2>
<p>Add a search input to the dashboard. Keep the query in a <code>signal()</code>, and use <a target="_blank" href="https://angular.dev/api/core/computed"><code>computed()</code></a> to show the matching products. Leave the category request as it is, so the resource manages server state while Signals manage local UI state.</p>
<p>Happy coding!</p>
<p>Feel free to read extra docs to have a full overview. :)</p>
<ul>
<li><a target="_blank" href="https://angular.dev/guide/zoneless">Angular Zoneless Guide</a></li>
<li><a target="_blank" href="https://angular.dev/guide/signals">Angular Signals Guide</a></li>
<li><a target="_blank" href="https://angular.dev/guide/di/creating-and-using-services">Angular Services Guide</a></li>
<li><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/">Kendo UI for Angular Grid Documentation</a></li>
<li><a target="_blank" href="https://github.com/danywalls/zoneless-kendo-dashboard">Source code</a></li>
</ul>
<p>And remember, Kendo UI for Angular, one of the <a target="_blank" href="https://www.telerik.com/blogs/10-best-angular-ui-libraries">best Angular libraries</a> comes with a free 30-day trial. So go on:</p>
<p><a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/23055/17502259.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:a5564819-4b16-44c3-a9c0-748fe79d20d5</id>
    <title type="text">Using Kendo UI with Svelte</title>
    <summary type="text">Even though there isn’t a native Svelte Kendo UI library, you can still use the Kendo UI for jQuery library of components with Svelte.</summary>
    <published>2026-10-12T16:13:00Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17494691/using-kendo-ui-svelte"/>
    <content type="text"><![CDATA[<p><span class="featured">Even though there isn&rsquo;t a native Svelte Kendo UI library, you can still use one of the Kendo UI component libraries with Svelte.</span></p><p>Progress Kendo UI does not have a Svelte version by default. It specializes in Angular, Vue, React and jQuery. However, that does not mean you Svelte fans out there are out of luck! JavaScript is just JavaScript, and there are work arounds to meet different needs.</p><h2 id="tldr">TL;DR</h2><p>You can use <a target="_blank" href="https://www.telerik.com/kendo-jquery-ui">Kendo UI for jQuery</a> in any JavaScript framework, or you can use <a target="_blank" href="https://astro.build/">Astro</a> to mix components from multiple frameworks (React, Vue, Angular). The jQuery version is not perfect for server-side rendering, but it can work in pure Svelte, while Astro can work wonders with any framework component.</p><h2 id="svelte-kendo-ui-for-jquery">✔️ Svelte with Kendo UI for jQuery</h2><p>Svelte allows you to import any JS you want. It is just JavaScript.</p><h3 id="create-a-new-sveltekit-project">Create a New SvelteKit Project</h3><pre class=" language-bash"><code class="prism  language-bash">npx sv create my-app
</code></pre><p>Install Tailwind and any other <a target="_blank" href="https://svelte.dev/docs/kit/creating-a-project">SvelteKit options</a> you need.</p><h3 id="install-packages">Install Packages</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i @progress/kendo-theme-meridian @progress/kendo-ui @progress/kendo-data-query @progress/kendo-date-math @progress/kendo-licensing @progress/kendo-popup-common jquery
</code></pre><p>with types:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> -D @types/jquery @types/node
</code></pre><h3 id="activate-kendo-ui">Activate Kendo UI</h3><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><p>Get your key ready and follow any other <a target="_blank" href="https://www.telerik.com/kendo-jquery-ui/documentation/intro/installation/licensing/using-license-file">activation instructions</a>.</p><h3 id="create-a-jquery-declaration-file">Create a jQuery Declaration File</h3><p>Put it in <code>lib/types/kendo-ui-module.d.ts</code> or wherever makes sense.</p><pre class=" language-tsx"><code class="prism  language-tsx">/// &lt;reference types="@progress/kendo-ui" /&gt;

declare module '@progress/kendo-ui' {
export {};
}

declare module '@progress/kendo-ui/kendo.all.js' {
export {};
}

declare global {
interface Window {
jQuery: JQueryStatic;
$: JQueryStatic;
}
}

export {};
</code></pre><ul><li><code>jQuery</code> types do not work as expected on the client without it.</li></ul><h3 id="dynamically-import-jquery-on-the-browser">Dynamically Import jQuery on the Browser</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">export</span> async <span class="token keyword">function</span> loadKendoUi<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
// @ts-expect-error Kendo UI exposes this runtime entry but ships global-only typings.
await import<span class="token punctuation">(</span><span class="token string">'@progress/kendo-ui/kendo.all.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>This allows you to load Kendo UI dynamically. Kendo UI for jQuery cannot run on the server, as it needs <code>window</code> objects, etc.</p><h3 id="handle-css">Handle CSS</h3><pre class=" language-tsx"><code class="prism  language-tsx">@import 'tailwindcss';
@import '@progress/kendo-theme-meridian/dist/meridian-main.css';
</code></pre><p>Make sure your global <code>layout.css</code> or <code>app.css</code> file contains tailwind and the correct Progress theme CSS.</p><h3 id="load-a-grid-element">Load a Grid Element</h3><pre class=" language-tsx"><code class="prism  language-tsx">&lt;script lang="ts"&gt;
import { loadKendoUi } from '$lib/kendo-ui';
import { onMount, tick } from 'svelte';
import ProductGridSkeleton from './ProductGridSkeleton.svelte';

let gridEl = $state&lt;HTMLDivElement&gt;();
let isGridLoading = $state(true);

const products = [
{ ProductName: 'Chai', UnitPrice: 18, UnitsInStock: 39, Discontinued: false },
{ ProductName: 'Chang', UnitPrice: 19, UnitsInStock: 17, Discontinued: false },
{ ProductName: 'Aniseed Syrup', UnitPrice: 10, UnitsInStock: 13, Discontinued: false }
];

onMount(() =&gt; {
let mounted = true;
let jquery: JQueryStatic | undefined;

(async () =&gt; {
const jqueryModule = await import('jquery');

jquery = jqueryModule.default;

window.jQuery = jquery;
window.$ = jquery;

await loadKendoUi();

if (!mounted) return;

isGridLoading = false;
await tick();

if (!mounted || !gridEl) return;

jquery(gridEl).kendoGrid({
dataSource: {
data: products,
pageSize: 20
},
height: 550,
scrollable: true,
sortable: true,
filterable: true,
pageable: {
input: true,
numeric: false
},
columns: [
{ field: 'ProductName' },
{ field: 'UnitPrice', title: 'Unit Price', format: '{0:c}', width: 130 },
{ field: 'UnitsInStock', title: 'Units In Stock', width: 130 },
{ field: 'Discontinued', width: 130 }
]
});
})();

return () =&gt; {
mounted = false;

if (!jquery || !gridEl) return;

jquery(gridEl).data('kendoGrid')?.destroy();
jquery(gridEl).empty();
};
});
&lt;/script&gt;

&lt;main class="flex items-center justify-center p-6"&gt;
&lt;section class="w-full max-w-5xl overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm"&gt;
{#if isGridLoading}
&lt;ProductGridSkeleton /&gt;
{:else}
&lt;div bind:this={gridEl}&gt;&lt;/div&gt;
{/if}
&lt;/section&gt;
&lt;/main&gt;
</code></pre><ul><li>We load Kendo UI in <code>onMount</code> so that it won&rsquo;t have problems loading on the server.</li><li>We use a <code>$state</code> signal to show a loading skeleton.</li><li><code>jQuery</code> and <code>kendoGrid</code> are dynamically loaded, and then the products are added to the grid.</li></ul><h3 id="create-a-loader">Create a Loader</h3><p>Since the component takes a second to load, we want to show a loading skeleton in the interim. Notice we use <code>kendo</code> style classes to match our grid.</p><pre class=" language-tsx"><code class="prism  language-tsx">&lt;script lang="ts"&gt;
const skeletonRows = Array.from({ length: 8 }, (_, index) =&gt; index);
&lt;/script&gt;

&lt;div class="k-grid k-grid-md k-grid-display-block h-137.5" aria-label="Loading product grid"&gt;
&lt;div class="k-grid-header"&gt;
&lt;div class="k-grid-header-wrap"&gt;
&lt;table class="k-grid-header-table k-table k-table-md"&gt;
&lt;colgroup&gt;
&lt;col /&gt;
&lt;col class="w-32.5" /&gt;
&lt;col class="w-32.5" /&gt;
&lt;col class="w-32.5" /&gt;
&lt;/colgroup&gt;
&lt;thead class="k-table-thead"&gt;
&lt;tr class="k-table-row"&gt;
&lt;th class="k-header k-table-th" scope="col"&gt;
&lt;span class="k-cell-inner"&gt;
&lt;span class="k-link"&gt;
&lt;span class="k-column-title"&gt;ProductName&lt;/span&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;/th&gt;
&lt;th class="k-header k-table-th" scope="col"&gt;
&lt;span class="k-cell-inner"&gt;
&lt;span class="k-link"&gt;
&lt;span class="k-column-title"&gt;Unit Price&lt;/span&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;/th&gt;
&lt;th class="k-header k-table-th" scope="col"&gt;
&lt;span class="k-cell-inner"&gt;
&lt;span class="k-link"&gt;
&lt;span class="k-column-title"&gt;Units In Stock&lt;/span&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;/th&gt;
&lt;th class="k-header k-table-th" scope="col"&gt;
&lt;span class="k-cell-inner"&gt;
&lt;span class="k-link"&gt;
&lt;span class="k-column-title"&gt;Discontinued&lt;/span&gt;
&lt;/span&gt;
&lt;/span&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div class="k-grid-container"&gt;
&lt;div class="k-grid-content k-auto-scrollable"&gt;
&lt;table class="k-grid-table k-table k-table-md"&gt;
&lt;colgroup&gt;
&lt;col /&gt;
&lt;col class="w-32.5" /&gt;
&lt;col class="w-32.5" /&gt;
&lt;col class="w-32.5" /&gt;
&lt;/colgroup&gt;
&lt;tbody class="k-table-tbody"&gt;
{#each skeletonRows as row (row)}
&lt;tr class="k-table-row"&gt;
&lt;td class="k-table-td"&gt;
&lt;span class="block h-4 w-64 max-w-full animate-pulse rounded bg-gray-300 text-transparent"&gt;
Loading product
&lt;/span&gt;
&lt;/td&gt;
&lt;td class="k-table-td"&gt;
&lt;span class="block h-4 w-24 animate-pulse rounded bg-gray-300 text-transparent"&gt;
0.00
&lt;/span&gt;
&lt;/td&gt;
&lt;td class="k-table-td"&gt;
&lt;span class="block h-4 w-24 animate-pulse rounded bg-gray-300 text-transparent"&gt;
000
&lt;/span&gt;
&lt;/td&gt;
&lt;td class="k-table-td"&gt;
&lt;span class="block h-4 w-20 animate-pulse rounded bg-gray-300 text-transparent"&gt;
False
&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
{/each}
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</code></pre><p>We could equally have used a spinner.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/chrome_vzeabbkhqo.gif?sfvrsn=137ca374_2" alt="Kendo UI for jQuery with Svelte in a grid" /></p><h2 id="kendo-ui-in-astro">✔️ Kendo UI in Astro</h2><p>Using <a target="_blank" href="https://astro.build/">Astro</a> is by far the best approach. You are not building in Svelte, but you can use any other framework with Astro, and have everything else built with Svelte.</p><h3 id="install-astro">Install Astro</h3><p>Create a new Astro project.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> create astro@latest
</code></pre><ul><li>Don&rsquo;t forget to <a target="_blank" href="https://tailwindcss.com/docs/installation/framework-guides/astro">add Tailwind</a>.</li></ul><h3 id="choose-your-kendo-ui-framework">Choose Your Kendo UI Framework</h3><ul><li><a target="_blank" href="https://www.telerik.com/kendo-react-ui">KendoReact</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-vue-ui">Kendo UI for Vue</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-angular-ui">Kendo UI for Angular</a></li></ul><p>Install the Svelte Astro Plugin:</p><ul><li><a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/svelte/">Svelte</a></li></ul><p>Install the proper Astro plugin:</p><ul><li><a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/react/">React</a></li><li><a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/vue/">Vue</a></li><li><a target="_blank" href="https://analogjs.org/docs/packages/astro-angular/overview">Angular</a> (unofficial)</li></ul><p>For this demo, I chose Vue and I copied all the <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/grid">Kendo UI for Vue</a> grid components in Astro.</p><h3 id="install-dependencies">Install Dependencies</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i @progress/kendo-data-query @progress/kendo-drawing @progress/kendo-licensing @progress/kendo-svg-icons @progress/kendo-theme-default @progress/kendo-vue-animation @progress/kendo-vue-buttons @progress/kendo-vue-charts @progress/kendo-vue-data-tools @progress/kendo-vue-dateinputs @progress/kendo-vue-dropdowns @progress/kendo-vue-grid @progress/kendo-vue-excel-export @progress/kendo-vue-inputs @progress/kendo-vue-intl @progress/kendo-vue-pdf @progress/kendo-vue-popup @progress/kendo-vue-treeview
</code></pre><h3 id="configure-css">Configure CSS</h3><pre class=" language-bash"><code class="prism  language-bash">@import <span class="token string">"tailwindcss"</span><span class="token punctuation">;</span>
@import <span class="token string">'@progress/kendo-theme-default/dist/all.css'</span><span class="token punctuation">;</span>
</code></pre><p>Put in <code>styles/global.css</code>.</p><h3 id="activate-your-kendo-ui-license">Activate Your Kendo UI License</h3><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><ul><li><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/my-license">KendoReact Setup</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/my-license">Kendo UI for Vue Setup</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/licensing">Kendo UI for Angular Setup</a></li></ul><p> You can also use more than one or all of them with Astro! (Progress offers a <a target="_blank" href="https://www.telerik.com/devcraft">DevCraft bundle</a>, if you code in more than one framework.)</p><h3 id="create-svelte-component">Create Svelte Component</h3><p>We want to mix our components, so we create a Svelte Component that can accept any other component as a child.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token operator">&lt;</span>script lang<span class="token operator">=</span><span class="token string">"ts"</span><span class="token operator">&gt;</span>
  <span class="token keyword">let</span> <span class="token punctuation">{</span> children <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token variable">$props</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token variable">$state</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">&lt;</span>/script<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>section
  class<span class="token operator">=</span><span class="token string">"mx-auto mt-5 w-full max-w-4xl rounded-3xl border border-pink-200 bg-linear-to-br from-pink-50 via-yellow-50 to-sky-50 p-6 shadow-[0_20px_60px_rgba(244,114,182,0.18)] ring-1 ring-white/70 sm:p-8"</span>
<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"mb-5 flex items-center justify-between gap-3"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"flex items-center gap-3"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>div
        class<span class="token operator">=</span><span class="token string">"flex h-12 w-12 items-center justify-center rounded-2xl bg-linear-to-br from-pink-500 to-rose-400 text-2xl shadow-lg shadow-pink-200/60"</span>
      <span class="token operator">&gt;</span>
        ⭐
      <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>div<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>p
          class<span class="token operator">=</span><span class="token string">"text-xs font-semibold uppercase tracking-[0.32em] text-pink-500"</span>
        <span class="token operator">&gt;</span>
          For kids
        <span class="token operator">&lt;</span>/p<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>h2 class<span class="token operator">=</span><span class="token string">"text-2xl font-bold text-slate-800 sm:text-3xl"</span><span class="token operator">&gt;</span>
          Fun learning card
        <span class="token operator">&lt;</span>/h2<span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>button
      type<span class="token operator">=</span><span class="token string">"button"</span>
      class<span class="token operator">=</span><span class="token string">"inline-flex items-center gap-2 rounded-full bg-sky-500 px-4 py-2 text-sm font-semibold text-white shadow-md shadow-sky-200 transition hover:-translate-y-0.5 hover:bg-sky-600 active:translate-y-0"</span>
      onclick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">(</span>count +<span class="token operator">=</span> 1<span class="token punctuation">)</span><span class="token punctuation">}</span>
      aria-label<span class="token operator">=</span><span class="token string">"Increase counter"</span>
    <span class="token operator">&gt;</span>
      Count <span class="token operator">&lt;</span>span class<span class="token operator">=</span><span class="token string">"min-w-5 text-center"</span><span class="token operator">&gt;</span><span class="token punctuation">{</span>count<span class="token punctuation">}</span><span class="token operator">&lt;</span>/span<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/button<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

  <span class="token operator">&lt;</span>div
    class<span class="token operator">=</span><span class="token string">"rounded-2xl bg-white/80 p-4 shadow-inner shadow-white/60 ring-1 ring-pink-100 sm:p-6"</span>
  <span class="token operator">&gt;</span>
    <span class="token punctuation">{</span>@render children?.<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
  <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

  <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"mt-5 flex items-center justify-between text-sm text-slate-500"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>span<span class="token operator">&gt;</span>Made <span class="token keyword">for</span> bright ideas and happy moments.<span class="token operator">&lt;</span>/span<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>span
      class<span class="token operator">=</span><span class="token string">"rounded-full bg-yellow-200 px-3 py-1 font-medium text-yellow-900"</span>
      <span class="token operator">&gt;</span>New<span class="token operator">&lt;</span>/span
    <span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>/section<span class="token operator">&gt;</span>
</code></pre><p>This component has a simple button counter that uses Runes.</p><h3 id="create-our-grid-component">Create our Grid Component</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token operator">&lt;</span>script setup<span class="token operator">&gt;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> ref, computed <span class="token punctuation">}</span> from <span class="token string">'vue'</span><span class="token punctuation">;</span>
<span class="token function">import</span> Grid from <span class="token string">'./grid/Grid.vue'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span>
    load,
    LocalizationProvider,
    IntlProvider,
    loadMessages,
<span class="token punctuation">}</span> from <span class="token string">'@progress/kendo-vue-intl'</span><span class="token punctuation">;</span>

<span class="token function">import</span> <span class="token punctuation">{</span> enComponentMessages, enCustomMessages <span class="token punctuation">}</span> from <span class="token string">'./grid/en-US'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> esComponentMessages, esCustomMessages <span class="token punctuation">}</span> from <span class="token string">'./grid/es'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> frComponentMessages, frCustomMessages <span class="token punctuation">}</span> from <span class="token string">'./grid/fr'</span><span class="token punctuation">;</span>

<span class="token function">import</span> likelySubtags from <span class="token string">'cldr-core/supplemental/likelySubtags.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> currencyData from <span class="token string">'cldr-core/supplemental/currencyData.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> weekData from <span class="token string">'cldr-core/supplemental/weekData.json'</span><span class="token punctuation">;</span>

<span class="token function">import</span> numbers from <span class="token string">'cldr-numbers-full/main/es/numbers.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> caGregorian from <span class="token string">'cldr-dates-full/main/es/ca-gregorian.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> dateFields from <span class="token string">'cldr-dates-full/main/es/dateFields.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> timeZoneNames from <span class="token string">'cldr-dates-full/main/es/timeZoneNames.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> esNumbers from <span class="token string">'cldr-numbers-full/main/es/numbers.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> esCurrencies from <span class="token string">'cldr-numbers-full/main/es/currencies.json'</span><span class="token punctuation">;</span>

<span class="token function">import</span> frNumbers from <span class="token string">'cldr-numbers-full/main/fr/numbers.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> frCaGregorian from <span class="token string">'cldr-dates-full/main/fr/ca-gregorian.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> frDateFields from <span class="token string">'cldr-dates-full/main/fr/dateFields.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> frTimeZoneNames from <span class="token string">'cldr-dates-full/main/fr/timeZoneNames.json'</span><span class="token punctuation">;</span>
<span class="token function">import</span> frCurrencies from <span class="token string">'cldr-numbers-full/main/fr/currencies.json'</span><span class="token punctuation">;</span>

load<span class="token punctuation">(</span>
    likelySubtags,
    currencyData,
    weekData,
    numbers,
    caGregorian,
    dateFields,
    timeZoneNames,
    esNumbers,
    esCurrencies,
    frNumbers,
    frCaGregorian,
    frDateFields,
    frTimeZoneNames,
    frCurrencies
<span class="token punctuation">)</span><span class="token punctuation">;</span>

loadMessages<span class="token punctuation">(</span>enCustomMessages, <span class="token string">'English'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
loadMessages<span class="token punctuation">(</span>enComponentMessages, <span class="token string">'English'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
loadMessages<span class="token punctuation">(</span>esCustomMessages, <span class="token string">'Spanish'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
loadMessages<span class="token punctuation">(</span>esComponentMessages, <span class="token string">'Spanish'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
loadMessages<span class="token punctuation">(</span>frCustomMessages, <span class="token string">'French'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
loadMessages<span class="token punctuation">(</span>frComponentMessages, <span class="token string">'French'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

const localizationLanguage <span class="token operator">=</span> ref<span class="token punctuation">(</span><span class="token string">'English'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
const locale <span class="token operator">=</span> computed<span class="token punctuation">((</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>localizationLanguage.value <span class="token operator">==</span><span class="token operator">=</span> <span class="token string">'Spanish'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">'es'</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>localizationLanguage.value <span class="token operator">==</span><span class="token operator">=</span> <span class="token string">'French'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">'fr'</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token string">'en'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

const onLocalizationLanguageChange <span class="token operator">=</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    localizationLanguage.value <span class="token operator">=</span> e<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token operator">&lt;</span>/script<span class="token operator">&gt;</span>

<span class="token operator">&lt;</span>template<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>div<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>LocalizationProvider :language<span class="token operator">=</span><span class="token string">"localizationLanguage"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>IntlProvider :locale<span class="token operator">=</span><span class="token string">"locale"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>Grid @localizationLanguageChange<span class="token operator">=</span><span class="token string">"onLocalizationLanguageChange"</span> /<span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>/IntlProvider<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/LocalizationProvider<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>/template<span class="token operator">&gt;</span>
</code></pre><p>The Grid component is broken up in several files and is copied directly from the <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/grid#kendo-ui-for-vue-native-grid-demo">Grid Demo</a>.</p><h3 id="load-vue-with-svelte">Load Vue with Svelte</h3><p>We create our Astro page under <code>pages/index.astro</code>.</p><pre class=" language-bash"><code class="prism  language-bash">---
<span class="token function">import</span> Panel from <span class="token string">"../components/Panel.svelte"</span><span class="token punctuation">;</span>
<span class="token function">import</span> Grid from <span class="token string">"../components/kendo-grid.vue"</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token string">"../styles/global.css"</span><span class="token punctuation">;</span>
---

<span class="token operator">&lt;</span>html lang<span class="token operator">=</span><span class="token string">"en"</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>head<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>meta charset<span class="token operator">=</span><span class="token string">"utf-8"</span> /<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>link rel<span class="token operator">=</span><span class="token string">"icon"</span> type<span class="token operator">=</span><span class="token string">"image/svg+xml"</span> href<span class="token operator">=</span><span class="token string">"/favicon.svg"</span> /<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>link rel<span class="token operator">=</span><span class="token string">"icon"</span> href<span class="token operator">=</span><span class="token string">"/favicon.ico"</span> /<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>meta name<span class="token operator">=</span><span class="token string">"viewport"</span> content<span class="token operator">=</span><span class="token string">"width=device-width"</span> /<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>meta name<span class="token operator">=</span><span class="token string">"generator"</span> content<span class="token operator">=</span><span class="token punctuation">{</span>Astro.generator<span class="token punctuation">}</span> /<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>title<span class="token operator">&gt;</span>Astro<span class="token operator">&lt;</span>/title<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/head<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>body<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>Panel client:load<span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>Grid client:load /<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/Panel<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/body<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>/html<span class="token operator">&gt;</span>
</code></pre><ul><li>Notice we are importing both a <code>.svelte</code> and a <code>.vue</code> component in the same file!</li><li>We use <code>client:load</code> to show that the component is dynamic.</li><li><code>Grid</code> is a child of <code>Panel</code>.</li></ul><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/chrome_ic0nsd7axj.gif?sfvrsn=7ed7ef64_2" height="1031" style="max-width:100%;height:auto;" title="Kendo UI with Svelte" width="1920" alt="Kendo UI with Svelte - Fun learning card" sf-size="339356" /></p><p>And we have Kendo UI for Vue and a Svelte Component in one!</p><h2 id="resources">Resources</h2><ul><li><a target="_blank" href="https://github.com/jdgamble555/svelte-kendo-ui">Svelte Repo on GitHub</a></li><li><a target="_blank" href="https://svelte-kendo-ui.vercel.app/">Svelte Demo on Vercel Serverless</a></li><li><a target="_blank" href="https://github.com/jdgamble555/astro-kendo">Astro Repo on GitHub</a></li><li><a target="_blank" href="https://astro-kendo.vercel.app/">Astro Demo on GitHub</a></li></ul><hr /><h2>Try Kendo UI</h2><p>All the Progress Kendo UI component libraries come with a free 30-day trial so you can explore them yourself.</p><p><a target="_blank" href="https://astro-kendo.vercel.app/" data-sf-ec-immutable="https://www.telerik.com/try/kendo-ui" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/23055/17494691.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:aed0c1e8-362e-4e5f-bc59-28516e012c02</id>
    <title type="text">Ready with WebMCP: Building Agent-Ready Apps with Telerik and Kendo UI</title>
    <summary type="text">WebMCP has great potential to help make our apps super compatible with agents. Let’s learn more about this proposed standard and see how to start using a preview tool in Telerik and Kendo UI.</summary>
    <published>2026-10-06T16:55:06Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17490592/ready-webmcp-building-agent-ready-apps-telerik-kendo-ui"/>
    <content type="text"><![CDATA[<p><span class="featured">WebMCP has great potential to help make our apps super compatible with agents. Let&rsquo;s learn more about this proposed standard and see how to start using a preview tool in Telerik and Kendo UI.</span></p><p>When I started working with AI agents, they were mostly good for writing code or running tasks on my local machine. But today, they do much more. AI agents can now open a browser, navigate webpages and interact with online applications just like we do.</p><p>The way agents interact with webpages today can be slow and fragile. Many browser agents act like a human looking at a screen, first taking screenshots, reading text from images or scanning thousands of lines of raw HTML, next trying to guess which button to click or which input to use.</p><p>What happens when an agent tries to interact with a data grid? Features like virtual scrolling, custom templates and dropdown menus make the HTML very complex. Rows disappear as you scroll. CSS classes change. When this happens, the agent gets lost.</p><p>What if our web applications could give AI agents a clear, direct API right inside the browser?</p><p>This is where Web Model Context Protocol (WebMCP) can help! We&rsquo;re going to learn what WebMCP is and how Progress Telerik and Kendo UI libraries bring this proposed standard to your apps.</p><p>We will start with our existing <strong>NBA Players Scout Dashboard</strong> built with <a target="_blank" href="https://angular.dev">Angular</a> and <a target="_blank" href="https://www.telerik.com/kendo-angular-ui">Kendo UI for Angular</a> and see why an AI agent might struggle with it. Then we will make it agent-ready in just a few minutes using <code>@progress/kendo-angular-webmcp</code>.</p><blockquote><p><strong>Important note:</strong> WebMCP is still a <em>proposed</em> web standard that only works behind dev feature flags in certain browsers. This post and its demo should be treated as a preview.</p></blockquote><p>Let&rsquo;s make it work in a real project, but first let&rsquo;s understand what WebMCP is.</p><h2 id="what-is-webmcp">What Is WebMCP?</h2><p>You might already know Anthropic&rsquo;s <a target="_blank" href="https://modelcontextprotocol.io/"><strong>Model Context Protocol (MCP)</strong></a>. MCP is an open standard. It connects AI assistants to external databases, files and developer tools. It usually runs on a server or a local machine.</p><p>WebMCP takes that idea and brings it directly inside your web browser.</p><p>Proposed by the <a target="_blank" href="https://github.com/webmachinelearning/webmcp?tab=readme-ov-file"><strong>W3C Web Machine Learning Community Group</strong></a>, WebMCP adds a new browser API: <code>navigator.modelContext</code>.</p><p>Think of WebMCP as an API menu for your webpage. Instead of asking an AI agent to read complex HTML elements, WebMCP lets your application register JavaScript functions as structured <strong>tools</strong>. Each tool has a clear name, a description and typed parameters.</p><p>When an AI agent visits your page, it does not need to guess how your UI works. It reads the list of tools, understands the parameters and runs the action directly.</p><p>But how do we actually create these tools without spending hours writing JSON schemas? That&rsquo;s where Progress Telerik and Kendo UI steps in.</p><blockquote><p>For a deeper understanding of WebMCP, check out the post: <a target="_blank" href="https://www.telerik.com/blogs/ai-crash-course-webmcp">AI Crash Course: WebMCP</a>.</p></blockquote><h2 id="using-telerik-and-kendo-ui-ai-tools-for-webmcp">Using Telerik and Kendo UI AI Tools for WebMCP</h2><p>Creating custom tools by hand takes a lot of time. You have to write code and sync it with your UI state. This is where a component library like Kendo UI for Angular helps. And now, with native preview WebMCP support through the <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-tools/web-mcp"><code>@progress/kendo-angular-webmcp</code></a>, Kendo UI components are ready for AI agents immediately.</p><p>You do not need to write custom JSON schemas for sorting or filtering. You just add a directive to your component:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span> <span class="token attr-name">[kendoWebMcp]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{ dataName: <span class="token punctuation">'</span>players<span class="token punctuation">'</span> }<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token comment">&lt;!-- Grid columns --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>When you add the <code>kendoWebMcp</code> directive to a component like the Kendo UI Grid:</p><ol><li>The directive checks active features, such as <code>[sortable]="true"</code> or <code>[filterable]="true"</code>.</li><li>It registers tools into the browser context, such as <code>players-sort-column</code> or <code>players-filter</code>.</li><li>When an AI agent calls one of these tools, the directive calls the internal Grid methods. Angular reacts, updates the state and refreshes the view.</li></ol><p>To test these tools, Progress provides the <a target="_blank" href="https://chromewebstore.google.com/detail/telerik-kendo-ui/bikfklddeekcicbafiejfbbpdjnaaiid"><strong>Telerik &amp; Kendo UI WebMCP Browser Extension</strong></a>. It gives you a chat panel right in your browser. Before we write any code, we need to make sure our browser can actually understand these new WebMCP commands.</p><blockquote><p>Progress also offers preview <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/web-mcp" title="https://www.telerik.com/kendo-react-ui/components/ai-tools/web-mcp">React WebMCP tools</a> and <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/ai/web-mcp/overview" title="https://www.telerik.com/blazor-ui/documentation/ai/web-mcp/overview">Blazor WebMCP tools</a>. Progress WebMCP integration tools are preview features because WebMCP is still a <em>proposed</em> web standard that only works in limited situations. In a quickly evolving ecosystem, support may also be limited.</p></blockquote><h2 id="preparing-your-browser">Preparing Your Browser</h2><p>Because WebMCP is a newly proposed web standard, we need two quick setup steps before we start:</p><h3 id="enable-the-browser-flag">1. Enable the Browser Flag</h3><p>WebMCP currently runs behind a flag in Chromium browsers (Google Chrome or Microsoft Edge).</p><ol><li>Open your browser and go to: <code>chrome://flags/#enable-webmcp-testing</code></li><li>Set the flag to <strong>Enabled</strong>.</li><li>Click <strong>Relaunch</strong> at the bottom of the window.</li></ol><h3 id="install-and-configure-the-telerik-webmcp-extension">2. Install and Configure the Telerik WebMCP Extension</h3><ol><li>Install the <a target="_blank" href="https://chromewebstore.google.com/detail/telerik-kendo-ui/bikfklddeekcicbafiejfbbpdjnaaiid"><strong>Telerik &amp; Kendo UI WebMCP Extension</strong></a> from the Chrome Web Store.</li></ol><p><img width="400" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/telerik-kendo-browser-webmcp-extension.png?sfvrsn=7656eb34_2" alt="Telerik & Kendo UI WebMCP Extension chat - how can I help" /></p><ol start="2"><li><p>The extension acts as a bridge between the browser and an LLM (Large Language Model) to interpret natural language commands. You need to configure it with an API key from an AI provider (such as Anthropic, Google Gemini or OpenAI).</p></li><li><p>Open the extension settings (the gear icon), choose your preferred provider and paste your API key.</p></li><li><p><strong>(Recommended):</strong> In the same settings menu under &ldquo;Behavior,&rdquo; turn on <strong>Auto-approve tool calls</strong>. This makes the chat experience much smoother because the AI won&rsquo;t ask for permission every time it wants to update the Grid.</p></li></ol><p>Once configured, you can open the chat panel, select your desired model and start typing prompts!</p><blockquote><p><strong>Warning:</strong> WebMCP and this extension are currently experimental. If you get an API error, just switch to Anthropic or OpenAI in the settings.</p></blockquote><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/telerik-kendo-browser-webmcp-extension-settings.png?sfvrsn=79c797f2_2" alt="Telerik & Kendo UI WebMCP Extension Preference Settings" /></p><p>Now your browser is fully ready! With the environment configured, we are ready to dive into the code and see this in action.</p><h2 id="the-starting-point-our-existing-nba-scout-dashboard">The Starting Point: Our Existing NBA Scout Dashboard</h2><p>In the real world, we rarely build applications from scratch just to support AI agents. Usually, we already have production applications.</p><p>Let&rsquo;s look at our existing application: an <strong>NBA Scout Dashboard</strong>. Basketball scouts use it to review player statistics.</p><blockquote><p><strong>Follow:</strong> The complete source code is available on <a target="_blank" href="https://github.com/danywalls/webmcp-kendo-angular">GitHub</a>. You can clone the <code>starter</code> branch to follow this tutorial step by step, or switch to the <code>main</code> branch to see the final code.</p></blockquote><p>To follow along, open your terminal and clone the <code>starter</code> branch of the repository:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">git</span> clone -b starter https://github.com/danywalls/webmcp-kendo-angular.git
<span class="token function">cd</span> webmcp-kendo-angular
<span class="token function">npm</span> <span class="token function">install</span>
</code></pre><p>The data model in <code>src/app/player.model.ts</code> looks like this:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Player</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  name<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  team<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  conference<span class="token punctuation">:</span> <span class="token string">'East'</span> <span class="token operator">|</span> <span class="token string">'West'</span><span class="token punctuation">;</span>
  position<span class="token punctuation">:</span> <span class="token string">'PG'</span> <span class="token operator">|</span> <span class="token string">'SG'</span> <span class="token operator">|</span> <span class="token string">'SF'</span> <span class="token operator">|</span> <span class="token string">'PF'</span> <span class="token operator">|</span> <span class="token string">'C'</span><span class="token punctuation">;</span>
  ppg<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  rpg<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  apg<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  status<span class="token punctuation">:</span> <span class="token string">'Active'</span> <span class="token operator">|</span> <span class="token string">'Day-to-Day'</span> <span class="token operator">|</span> <span class="token string">'Injured'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> NBA_PLAYERS<span class="token punctuation">:</span> Player<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Luka Doncic'</span><span class="token punctuation">,</span> team<span class="token punctuation">:</span> <span class="token string">'Mavericks'</span><span class="token punctuation">,</span> conference<span class="token punctuation">:</span> <span class="token string">'West'</span><span class="token punctuation">,</span> position<span class="token punctuation">:</span> <span class="token string">'PG'</span><span class="token punctuation">,</span> ppg<span class="token punctuation">:</span> <span class="token number">33.9</span><span class="token punctuation">,</span> rpg<span class="token punctuation">:</span> <span class="token number">9.2</span><span class="token punctuation">,</span> apg<span class="token punctuation">:</span> <span class="token number">9.8</span><span class="token punctuation">,</span> status<span class="token punctuation">:</span> <span class="token string">'Active'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token comment">// ... more players</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre><p>The component in <code>src/app/app.ts</code> is a modern standalone Angular component using <code>KENDO_GRID</code>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-grid'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Player<span class="token punctuation">,</span> NBA_PLAYERS <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./player.model'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-root'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">'./app.html'</span><span class="token punctuation">,</span>
  styleUrl<span class="token punctuation">:</span> <span class="token string">'./app.css'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  players <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Player<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>NBA_PLAYERS<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>And here is the template in <code>src/app/app.html</code>:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dashboard-container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dashboard-header<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>NBA Scout Intelligence Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Powered by Angular and Kendo UI<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>grid-wrapper<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span>
      <span class="token attr-name">[kendoGridBinding]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>players()<span class="token punctuation">"</span></span>
      <span class="token attr-name">[sortable]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
      <span class="token attr-name">[filterable]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
    <span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Player Name<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>200<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>team<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Team<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>conference<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Conf<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>90<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>position<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Pos<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>90<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ppg<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>PPG<span class="token punctuation">"</span></span> <span class="token attr-name">filter</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>numeric<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>rpg<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>RPG<span class="token punctuation">"</span></span> <span class="token attr-name">filter</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>numeric<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>apg<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>APG<span class="token punctuation">"</span></span> <span class="token attr-name">filter</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>numeric<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>100<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>status<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Status<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>130<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ng-template</span> <span class="token attr-name">kendoGridCellTemplate</span> <span class="token attr-name">let-dataItem</span><span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">[class]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>badge badge-<span class="token punctuation">'</span> + dataItem.status.toLowerCase().replace(<span class="token punctuation">'</span> <span class="token punctuation">'</span>, <span class="token punctuation">'</span>-<span class="token punctuation">'</span>)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            {{ dataItem.status }}
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ng-template</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Start the application by running this command:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> start
</code></pre><h3 id="testing-the-app-as-a-human-user">Testing the App as a Human User</h3><p>If you open <code>http://localhost:4200</code> in your browser right now, you&rsquo;ll see a fully functional grid. Click the column headers to sort the data, or open the filter menus to find specific players. For us humans, the dashboard works perfectly and is ready to use. But what happens when an AI agent tries to do the exact same thing? Let&rsquo;s find out.</p><h3 id="the-problem-how-an-ai-agent-sees-our-app-today">The Problem: How an AI Agent Sees Our App Today</h3><p>Now, open the <strong>Telerik &amp; Kendo UI WebMCP Extension</strong> in your browser toolbar and click the <strong>Tools</strong> tab.</p><p>You will see: <strong>No tools available</strong>.</p><p>Because we did not register any tools, the AI agent has no direct way to interact with the grid. If you ask the agent to <em>&ldquo;Show me point guards with more than 25 PPG&rdquo;</em>, it has to guess the HTML structure. If the grid layout changes, the agent fails.</p><p>What problem does this solve for us as developers? We want our existing application to tell the agent exactly what it can do, without rewriting all our code.</p><p>So, how do we bridge this gap and give the AI the exact tools it needs? Let&rsquo;s make this app agent-ready in just three simple steps.</p><h2 id="making-the-existing-application-agent-ready">Making the Existing Application Agent-Ready</h2><p>So far, we have tested our dashboard and seen the problem. Now, let&rsquo;s fix it in three simple steps.</p><h3 id="install-progresskendo-angular-webmcp">Install @progress/kendo-angular-webmcp</h3><p>Stop your development server and run this command:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> @progress/kendo-angular-webmcp
</code></pre><p>This installs the Kendo UI WebMCP package.</p><h3 id="step-2-import-kendo_webmcp-in-the-component">Step 2: Import KENDO_WEBMCP in the Component</h3><p>Open <code>src/app/app.ts</code>. Import <code>KENDO_WEBMCP</code> and add it to the <code>imports</code> array:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// Update src/app/app.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-grid'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_WEBMCP <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-webmcp'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Player<span class="token punctuation">,</span> NBA_PLAYERS <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./player.model'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-root'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">,</span> KENDO_WEBMCP<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">'./app.html'</span><span class="token punctuation">,</span>
  styleUrl<span class="token punctuation">:</span> <span class="token string">'./app.css'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  players <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Player<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>NBA_PLAYERS<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="add-the-kendowebmcp-directive-to-the-grid">Add the kendoWebMcp Directive to the Grid</h3><p>Open <code>src/app/app.html</code>. Update the <code>&lt;kendo-grid&gt;</code> tag to include <code>[kendoWebMcp]</code>:</p><pre class=" language-html"><code class="prism  language-html"><span class="token comment">&lt;!-- Update src/app/app.html --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span>
  <span class="token attr-name">[kendoGridBinding]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>players()<span class="token punctuation">"</span></span>
  <span class="token attr-name">[sortable]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
  <span class="token attr-name">[filterable]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
  <span class="token attr-name">[kendoWebMcp]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{ dataName: <span class="token punctuation">'</span>players<span class="token punctuation">'</span> }<span class="token punctuation">"</span></span>
<span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="what-just-happened-here">What Just Happened Here?</h3><p>With just <code>[kendoWebMcp]="{ dataName: 'players' }"</code>, Kendo UI checks your grid features. Because <code>[sortable]="true"</code> and <code>[filterable]="true"</code> are on, it automatically creates a list of tools like:</p><ul><li><code>players-sort-column</code>: Lets the agent sort any column.</li><li><code>players-filter</code>: Lets the agent filter columns (e.g., greater than, equal to).</li><li><code>players-highlight</code>: Lets the agent visually highlight specific rows.</li></ul><p>Notice what we did <strong>not</strong> do. We did not write complex JSON schemas. We did not change our Angular signals. It just works. Now that our grid is exposing these tools natively, it is time to put the AI agent to the test.</p><h2 id="interacting-with-the-ai-agent">Interacting with the AI Agent</h2><p>Let&rsquo;s see the result. Start your application again (<code>npm start</code>).</p><h3 id="verifying-the-tools">Verifying the Tools</h3><ul><li><strong>URL</strong>: <code>http://localhost:4200</code></li><li><strong>Action</strong>: Refresh the page. Open the <strong>Telerik WebMCP Extension</strong> and check the <strong>Tools</strong> tab.</li><li><strong>Expected Result</strong>: You see <code>players-sort-column</code>, <code>players-filter</code> and <code>players-highlight</code> listed.</li></ul><p><img width="400" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/telerik-kendo-browser-webmcp-extension-tools.png?sfvrsn=a783abfa_2" alt="Telerik & Kendo UI WebMCP Extension Tools - 15 selected" /></p><p>Let&rsquo;s test a prompt with a multi-condition filtering. In the extension chat, type:</p><blockquote><p>&ldquo;Show me all Western Conference PG averaging more than 25 points per game.&rdquo;</p></blockquote><p>Instead of clicking menus, the AI calls the <code>players-filter</code> tool using this JSON:</p><pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>
  <span class="token string">"filters"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span> <span class="token string">"field"</span><span class="token punctuation">:</span> <span class="token string">"conference"</span><span class="token punctuation">,</span> <span class="token string">"operator"</span><span class="token punctuation">:</span> <span class="token string">"eq"</span><span class="token punctuation">,</span> <span class="token string">"value"</span><span class="token punctuation">:</span> <span class="token string">"West"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> <span class="token string">"field"</span><span class="token punctuation">:</span> <span class="token string">"position"</span><span class="token punctuation">,</span> <span class="token string">"operator"</span><span class="token punctuation">:</span> <span class="token string">"eq"</span><span class="token punctuation">,</span> <span class="token string">"value"</span><span class="token punctuation">:</span> <span class="token string">"PG"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> <span class="token string">"field"</span><span class="token punctuation">:</span> <span class="token string">"ppg"</span><span class="token punctuation">,</span> <span class="token string">"operator"</span><span class="token punctuation">:</span> <span class="token string">"gt"</span><span class="token punctuation">,</span> <span class="token string">"value"</span><span class="token punctuation">:</span> <span class="token number">25</span> <span class="token punctuation">}</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span>
</code></pre><p>The Kendo UI Grid applies the filter immediately! Luka Doncic, Shai Gilgeous-Alexander and Stephen Curry remain visible.</p><p>Next, clear the filter and try sorting and highlighting this two-step prompt:</p><blockquote><p>&ldquo;Clear the filter, sort players by PPG descending, and highlight any player who is currently injured.&rdquo;</p></blockquote><p>The agent calls two tools automatically:</p><ol><li><code>players-sort-column</code> with <code>{ field: "ppg", dir: "desc" }</code>.</li><li><code>players-highlight</code> matching rows where <code>status == "Injured"</code>.</li></ol><p>Joel Embiid and Jimmy Butler are highlighted. There is no guessing HTML structure. It is fast and reliable.</p><p>Play with it and see how the AI manipulates the UI like magic!</p><p><a target="_blank" href="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/telerik-kendo-browser-webmcp-extension-ai-chat.gif?sfvrsn=26eb0b76_2"><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-10/telerik-kendo-browser-webmcp-extension-ai-chat.gif?sfvrsn=26eb0b76_2" alt="User asks chat questions that the AI can answer and respond to neatly" /></a></p><p>But what does this mean for us architecturally?</p><p>This update to WebMCP brings a few key points to improve our app&rsquo;s interactions with agents:</p><ul><li>Instead of an agent taking heavy screenshots or a scraping agent reading massive HTML blocks, WebMCP only uses small JSON payloads. This saves money, time and tokens!</li><li>WebMCP helps provide stable interactions. UI designs and CSS classes can change often, so if an agent relies on HTML tags like <code>.k-grid-table &gt; tr</code>, it will break. WebMCP creates a clear contract. You can change your CSS without breaking the AI agent.</li><li>The best part is the native Angular support, because Kendo UI for Angular components are created natively with Angular. When the AI agent calls a tool, Kendo UI updates the grid properly.</li></ul><p>This all sounds great, but of course not everything in preview is perfect yet, and WebMCP brings some trade-offs to keep in mind:</p><ul><li><strong>Experimental specification</strong>: WebMCP is still an experimental draft. The API might change before becoming a final standard.</li><li><strong>Browser setup required</strong>: Users currently need to enable a browser flag and install an extension. In the future, browsers will support this natively.</li><li><strong>Client-side security</strong>: Tools run in the browser session. Always validate inputs and check permissions on your backend server.</li></ul><p>Despite these early-stage trade-offs, the future of AI-driven web apps is incredibly and amazing.</p><h2 id="recap">Recap</h2><p>WebMCP gives AI agents a more reliable way to interact with your web applications. In this guide, we explored what WebMCP is and why standard web apps are hard for AI agents to use.</p><p>Then, we solved this problem using <code>@progress/kendo-angular-webmcp</code>. With a simple directive, we made our app agent-ready and watched an AI agent sort, filter and highlight our data using natural language.</p><p>You can find the final code <code>main</code> branch of our <a target="_blank" href="https://github.com/danywalls/webmcp-kendo-angular">GitHub repository</a>.</p><p>Happy coding!</p><hr /><p>Remember, Telerik and Kendo UI components come with a free trial. So, go on and explore the ever-evolving <a target="_blank" href="https://www.telerik.com/ai-components">AI-readiness of these well-known components</a> for yourself.</p><p><a class="Btn" target="_blank" href="https://www.telerik.com/download">Try Now</a></p><img src="https://feeds.telerik.com/link/23055/17490592.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:5dafde89-fa35-47c2-8e9a-d56da2fc6438</id>
    <title type="text">The 10 Best Angular UI Chart Libraries</title>
    <summary type="text">TL;DR Which Angular charts library is best? With the native build, 18+ styles of charts, accessibility and AI integrations, Kendo UI for Angular is hard to top.</summary>
    <published>2026-09-22T20:34:30Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17463589/10-best-angular-ui-chart-libraries"/>
    <content type="text"><![CDATA[<p><span class="featured">TL;DR Which Angular charts library is best? With the native build, 18+ styles of charts, accessibility and AI integrations, Kendo UI for Angular is hard to top.</span></p><p>It does not matter if you are building a static page, a dynamic dashboard, a landing page to show NBA results or an AI-powered application. The web needs charts. Data visualization is everywhere, and choosing the right chart library is a decision that affects every Angular project.</p><p>During these years, I have created charts with many libraries. I started with <a target="_blank" href="https://jquery.com/">jQuery plugins</a>, then moved to <a target="_blank" href="https://d3js.org/">D3.js</a>, <a target="_blank" href="https://www.chartjs.org/">Chart.js</a>, <a target="_blank" href="https://www.highcharts.com/">Highcharts</a> and many others. Each project brought a new challenge and a new library to learn. Some were easy to start with but hard to maintain. Others were powerful but difficult to integrate with Angular. Today, my go-to choice is Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts">Angular Charts</a> because it solves most of the problems I found along the way.</p><p>Today, the options are better than ever. But the question remains the same: <strong>"Which chart library should I use?"</strong></p><p>In this article, we are going to compare the <strong>10 best Angular chart libraries</strong> available in 2026. For each one, I will share its strengths, its weaknesses and my honest recommendation. By the end, you will know exactly which library fits your next project.</p><h2 id="the-scenario">The Scenario</h2><p>Imagine you are building a financial dashboard for a fintech startup. The app needs real-time stock charts, interactive bar charts for monthly revenue, pie charts for portfolio distribution and heat maps for risk analysis. The deadline is in two weeks. Your team has three Angular developers and one designer who works in Figma.</p><p>In this situation, your chart library is not just about rendering bars and lines. It needs to integrate cleanly with Angular, support real-time data, look consistent with your brand and be easy to maintain over time. You also need your AI coding agents to understand the component API so they can help you build faster.</p><blockquote><p>I have seen teams spend weeks trying to make a chart library work with Angular, only to switch to another one and start over. Making the right choice from the beginning saves you that pain.</p></blockquote><p>Let&rsquo;s look at the options.</p><h2 id="kendo-ui-for-angular-charts">1. Kendo UI for Angular Charts</h2><p>The <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts">Kendo UI for Angular Charts</a> are, in my experience, the most complete charting solution for Angular applications in 2026.</p><p>They are not just a wrapper around a JavaScript library. They are <strong>native Angular components</strong> built from the ground up for the framework. These charts use Angular&rsquo;s template system, dependency injection and change detection natively. This means better performance and a developer experience that feels natural.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-09/angular-charts.png?sfvrsn=c344dc72_2" title="angular-charts" alt="Angular Charts" /></p><h3 id="why-kendo-ui-charts-is-the-best-choice-imo">Why Kendo UI Charts Is the Best Choice IMO</h3><p>What makes Kendo UI charts stand out is the combination of completeness and Angular-native design. You get <strong>18+ chart types</strong> across four categories: Categorical (<a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/line">Line</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/bar">Bar</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/ohlc-candlestick">OHLC/Candlestick</a>), Circular (<a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/pie">Pie</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/donut">Donut</a>), Freeform (<a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/funnel">Funnel</a>) and Scatter (<a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/bubble">Bubble</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types/scatter">Scatter Plot</a>). Plus dedicated components like <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/sparkline">Sparkline</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/stock-chart">StockChart</a> and <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/sankey">Sankey</a>, and <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/series-types">many more</a>. Every one of these is a first-class Angular component with full TypeScript support.</p><p>And each type of chart comes with a wide range of functionality and customization:</p><ul><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/configuration">Dual configuration modes</a>:</strong> You can configure charts using <strong>TypeScript objects</strong> (great for loading settings from an API) or <strong>Angular template components</strong> (great for IDE support and type checking). You can even mix both approaches in the same chart, using objects for global settings and components for series-specific configuration.</li><li><strong>Real-time data support:</strong> The <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/stock-chart">StockChart</a> component handles live data feeds with smooth animations. If you are building financial dashboards or monitoring tools, this is critical.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/drill-down">Drilldown functionality</a>:</strong> Explore data in a view hierarchy directly within your charts. Users can click to see details without leaving the component.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/pan-zoom">Panning and Zooming</a>:</strong> Simple interactions allow users to explore data by moving across categories or zooming into specific ranges. You can enable or disable these features per chart.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/export-options">Export options</a>:</strong> Built-in export to <strong>PNG, PDF, SVG</strong> and the Kendo UI Drawing format for further processing. No extra libraries needed.</li><li><strong>Rendering modes:</strong> Choose between <strong>SVG</strong> (vector, default) and <strong>Canvas</strong> (bitmap) depending on your performance needs. Switching is a single configuration option.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/charts/accessibility/aria-support_chart">WCAG 2.2 AA accessibility</a>:</strong> Full compliance with Section 508 and WAI-ARIA best practices. Tested against screen readers including JAWS and NVDA.</li><li><strong>Context Menu integration:</strong> Right-click on data points to trigger custom actions. Works well with the Kendo UI ContextMenu component.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/styling/theme-builder">ThemeBuilder</a> and Figma kits:</strong> Your designer creates the look in Figma, you apply it with ThemeBuilder. The new <a target="_blank" href="https://www.telerik.com/design-system/docs/themes/kendo-themes/meridian/">Meridian theme</a> joins Bootstrap, Material and other popular options. No CSS customization problems.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-tools/web-mcp">WebMCP support</a> (2026 Q2):</strong> Kendo UI for Angular components are now <strong>agent-ready out of the box</strong>. AI agents can interact with your charts by calling structured tools directly, triggering actions like filtering data or updating values through natural language. This is the future of UI development.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-tools/agentic-ui-generator/getting-started">Agentic UI Generator</a>:</strong> Build chart components and layouts using <strong>natural language prompts</strong> directly inside AI-powered IDEs like VS Code and Cursor. Get intelligent assistance powered by Kendo UI documentation and APIs.</li><li><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/assisted-migration">Codemods CLI</a>:</strong> Upgrade to the latest Kendo UI version faster with <strong>AI-assisted migration</strong>. The tool automates code transformations, shows changes with diff-style output and provides a detailed migration report.</li><li><strong>Professional support:</strong> When your StockChart breaks at 2 AM before a demo, you have a team of engineers who built the component ready to help you. Not a forum post from 2019.</li></ul><p><strong>Pro tip:</strong> You can <a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui">try Kendo UI for Angular Charts free for 30 days</a>, no credit card required. The fastest way to prove to your team that this is the right choice is to build a small prototype and see how fast you can go.</p><p>But what if you prefer a simpler, open-source approach? That brings us to our next option.</p><h2 id="chart.js-with-ng2-charts">2. Chart.js with ng2-charts</h2><p><a target="_blank" href="https://www.chartjs.org/">Chart.js</a> with <a target="_blank" href="https://www.npmjs.com/package/ng2-charts">ng2-charts</a> is the most popular lightweight option for Angular developers who need quick, simple charts without a difficult learning process.</p><ul><li><strong>The Good:</strong> Very easy to set up. You can have a bar chart running in minutes. The library is lightweight and performs well for simple use cases. It supports the most common chart types: bar, line, pie, doughnut, radar, scatter and bubble.</li><li><strong>The Cons:</strong> Limited customization compared to enterprise libraries. The Angular wrapper (ng2-charts) is a community project, not an official Angular integration. When you need advanced features like real-time streaming, custom tooltips or complex animations, you will reach its limits quickly.</li><li><strong>My Feedback:</strong> Chart.js is perfect for a quick prototype or a small internal tool. But for a production dashboard with real-time data and brand customization, you will need more than it can offer. The Kendo UI library gives you the same simplicity for basic charts but grows with enterprise needs without switching libraries.</li></ul><p>If you want something more powerful than Chart.js but still open-source, keep reading.</p><h2 id="apache-echarts">3. Apache ECharts</h2><p><a target="_blank" href="https://echarts.apache.org/">Apache ECharts</a> is one of the most powerful open-source charting libraries available. It offers advanced visualization capabilities and strong performance with large datasets.</p><ul><li><strong>The Good:</strong> 20+ built-in chart types with advanced components like heat maps, treemaps, Sankey diagrams and candlestick charts. It handles large datasets well and offers rich interactivity out of the box. The library is free under the Apache 2.0 license.</li><li><strong>The Cons:</strong> The Angular integration requires wrappers like <code>ngx-echarts</code>, which are community-maintained. The API can feel overwhelming for developers coming from simpler libraries. The documentation is comprehensive but not always easy to navigate.</li><li><strong>My Feedback:</strong> ECharts is a solid open-source choice, especially if budget is a concern. However, the Angular integration is not native. If you are building a production app where support and consistency matter, the Kendo UI library&rsquo;s native Angular components and professional support make it a safer investment.</li></ul><p>If your project has a lot of data and you need something more focused on dashboards, check out the next option.</p><h2 id="highcharts-angular">4. Highcharts Angular</h2><p><a target="_blank" href="https://www.highcharts.com/">Highcharts</a> is one of the most mature and well-known charting libraries in the market. It has been around since 2009 and offers an official Angular wrapper.</p><ul><li><strong>The Good:</strong> Excellent documentation and a polished API. Highcharts offers specialized modules for stock charts, maps and Gantt charts. The visual quality is high and the library handles accessibility well with WCAG compliance.</li><li><strong>The Cons:</strong> Commercial license required for business use. The Angular wrapper is not a native Angular component, which means you lose some of Angular&rsquo;s template features and type safety. Customization beyond the built-in options can be complex.</li><li><strong>My Feedback:</strong> Highcharts is a solid library, but the licensing model and the wrapper-based Angular integration are limitations. The Kendo UI library offers a similar level of visual quality with native Angular components, a more flexible licensing model and the added benefit of ThemeBuilder for design consistency.</li></ul><p>For teams that need a large chart collection with commercial support, the next option is worth considering.</p><h2 id="fusioncharts-angular">5. FusionCharts Angular</h2><p><a target="_blank" href="https://www.fusioncharts.com/">FusionCharts</a> is an enterprise-focused charting library with over 95 chart types, 1,400+ maps and 20+ dashboard components.</p><ul><li><strong>The Good:</strong> The variety of chart types is impressive, including specialized visualizations like funnel charts, gauges and Gantt charts. It handles medium to large datasets well and offers commercial support.</li><li><strong>The Cons:</strong> Commercial license required. The Angular integration uses a wrapper approach. The API can feel heavy for simple use cases. The learning curve is steeper than simpler alternatives.</li><li><strong>My Feedback:</strong> FusionCharts is powerful, but it can be too much for many projects. If you only need 5 or 6 chart types, you are paying for complexity you will never use. The Kendo UI library gives you a similar variety of chart types with a lighter API and native Angular integration.</li></ul><p>If you want a modern, lightweight option with great interactivity, the next library might interest you.</p><h2 id="apexcharts-angular">6. ApexCharts Angular</h2><p><a target="_blank" href="https://apexcharts.com/">ApexCharts</a> is a modern, open-source charting library that focuses on beautiful, interactive charts with minimal configuration.</p><ul><li><strong>The Good:</strong> Clean, modern design out of the box. Built-in interactivity like zooming, panning and tooltips without extra configuration. The Angular wrapper (<code>ng-apexcharts</code>) is straightforward to use. Supports 20+ chart types.</li><li><strong>The Cons:</strong> The Angular wrapper is community-maintained. Limited chart variety compared to enterprise libraries. No built-in maps or Gantt charts. The commercial license applies for business use.</li><li><strong>My Feedback:</strong> ApexCharts is great for modern dashboards where visual appeal matters. However, the community Angular wrapper means you depend on external maintainers. If your project needs long-term stability and professional support, the Kendo UI library&rsquo;s native Angular components are a more reliable choice.</li></ul><p>For developers who want maximum flexibility and are willing to write more code, the next option is worth exploring.</p><h2 id="d3.js-with-angular">7. D3.js with Angular</h2><p><a target="_blank" href="https://d3js.org/">D3.js</a> is the most powerful and flexible data visualization library available. It gives you complete control over every pixel of your charts.</p><ul><li><strong>The Good:</strong> Maximum flexibility. You can create any visualization you can imagine, from simple bar charts to complex custom interactive dashboards. The data-binding capabilities are unmatched.</li><li><strong>The Cons:</strong> Difficult learning process. D3 is not a charting library, it is a visualization toolkit. Even simple charts require significant code. There is no Angular-specific integration, so you need to handle all the Angular-D3 setup yourself.</li><li><strong>My Feedback:</strong> D3.js is incredible for custom visualizations that no other library can create. But for a business dashboard with standard chart types, it is too much work. You will spend weeks building what Kendo UI gives you in hours. Use D3 for the 5% of charts that need custom work, and use Kendo UI for the other 95%.</li></ul><p>If your focus is on Angular-native solutions, the next option is worth a look.</p><h2 id="ngx-charts">8. ngx-charts</h2><p><a target="_blank" href="https://swimlane.github.io/ngx-charts/">ngx-charts</a> is a charting library built specifically for Angular, using D3.js internally.</p><ul><li><strong>The Good:</strong> Angular-native design. The library uses Angular components and supports features like animation and interactivity. It covers the most common chart types: bar, line, pie, area, polar and heat maps.</li><li><strong>The Cons:</strong> The project has not been actively maintained in recent years. The chart variety is limited compared to enterprise libraries. Performance can decrease with large datasets.</li><li><strong>My Feedback:</strong> ngx-charts was a good option when it was actively maintained, but the lack of recent updates is a concern for production apps. If you want Angular-native charts with active development and professional support, Kendo UI for Angular is the clear choice.</li></ul><p>For teams already invested in the Syncfusion ecosystem, the next option might be familiar.</p><h2 id="syncfusion-angular-charts">9. Syncfusion Angular Charts</h2><p><a target="_blank" href="https://www.syncfusion.com/angular-ui-components">Syncfusion</a> offers a comprehensive suite of Angular components including a powerful charting library.</p><ul><li><strong>The Good:</strong> Wide variety of chart types. Good performance with large datasets. The components are well-integrated with other Syncfusion components.</li><li><strong>The Cons:</strong> The API does not always feel like standard Angular, which makes it harder to learn. Customizing charts to match a specific Figma design can be challenging. The styling system can feel overwhelming.</li><li><strong>My Feedback:</strong> Syncfusion charts are powerful, but the developer experience can feel heavy. If you want charts that integrate cleanly with Angular&rsquo;s template system and offer ThemeBuilder for design consistency, the Kendo UI library provides a simpler experience for both developers and designers.</li></ul><p>Finally, for developers who want a modern, modular approach, the last option is worth considering.</p><h2 id="plotly.js">10. Plotly.js</h2><p><a target="_blank" href="https://plotly.com/javascript/">Plotly.js</a> is an open-source graphing library that supports over 20 chart types and offers an Angular wrapper.</p><ul><li><p><strong>The Good:</strong> Good for scientific and statistical visualizations. Supports 3D charts, which is rare among charting libraries. The library is open-source and well-documented.</p></li><li><p><strong>The Cons:</strong> The Angular wrapper is not <a target="_blank" href="https://github.com/plotly/angular-plotly.js">officially maintained by the Plotly team</a>. The library can be heavy for simple use cases. The visual style is functional but not as polished as commercial alternatives.</p></li><li><p><strong>My Feedback:</strong> Plotly.js is a good choice for scientific or research applications where 3D visualization is needed. For business dashboards and enterprise applications, the Kendo UI library offers a more polished look, better Angular integration and the consistency of a complete component suite.</p></li></ul><h2 id="recap">Recap</h2><p>Choosing the right Angular chart library in 2026 is about matching your project&rsquo;s needs with the right tool. If you are building a serious Angular application in 2026, <strong>Kendo UI for Angular Charts</strong> is the strongest choice.</p><p>You get production-ready native Angular components, 18+ chart types, real-time data, drilldown functionality, of course WCAG 2.2 AA accessibility, ThemeBuilder integration and AI-ready tools through WebMCP and the Agentic UI Generator. All backed by professional support from the team that built the software (so you never feel alone).</p><p>The best chart library is the one that lets you focus on your data, not on fighting with the tool. Kendo UI for Angular gives you that freedom.</p><h3 id="do-you-want-to-try-it">Do You Want to Try It?</h3><p>The Kendo UI for Angular Charts, plus 120 other components are all available to try free for 30 days.</p><p><a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Download Free Trial</a></p><img src="https://feeds.telerik.com/link/23055/17463589.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:714cae2f-b0e8-417b-93f1-8954ae57fa7a</id>
    <title type="text">Angular State Management: From Local Signals to NgRx SignalStore</title>
    <summary type="text">Last time, we looked at which state management method to choose for your Angular app. This time, we’ll explore this in a real app so you can see why different solutions work better for different cases.</summary>
    <published>2026-09-16T20:28:49Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17463884/angular-state-management-local-signals-ngrx-signalstore"/>
    <content type="text"><![CDATA[<p><span class="featured">Last time, we looked at which state management method to choose for your Angular app. This time, we&rsquo;ll explore this in a real app so you can see why different solutions work better for different cases.</span></p><p>It doesn&rsquo;t matter if you are working with Angular 4, 15, 20 or the latest version 22. Every time I start a new project, I face the same big question: <strong>&ldquo;Where should I put this data?&rdquo;</strong></p><p>I learned this through many challenges. Often you start small and think a few variables are enough. But the app grows fast, and suddenly you are struggling with too many inputs, outputs and &ldquo;prop drilling.&rdquo; Based on my experience, state management is about setting the rules for how data flows in your app.</p><p>Whether it is a tiny prototype or a large enterprise dashboard, the choices you make today will make your life easier when requirements change later. The tools have evolved, from complex RxJS streams to the simplicity of Signals, but the goal is the same: keeping your UI and data in sync without making it too difficult.</p><p>Think of this as a carpenter&rsquo;s workshop. Different jobs need different tools. We explored this decision in detail in our guide: <a target="_blank" href="https://www.telerik.com/blogs/decision-guide-angular-state-management-signals-services-signalstore-ngrx">Decision Guide for Angular State Management: Signals, Services, SignalStore or NgRx</a>.</p><p>Today, we are going to learn how to build a real accounting dashboard using the modern patterns of Angular 22 with UI components from the Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui">Angular library</a>. We will start simple and add complexity only when the logic needs it. By the end, you will have a clear plan to decide what goes where, so you can build apps that stay clean as they grow.</p><p>Let&rsquo;s make this work in a real project.</p><blockquote><p><strong>Follow along:</strong> All the code for this dashboard is available on <a target="_blank" href="https://github.com/danywalls/accounting-dashboard-ngrx">GitHub</a>. Feel free to clone it and experiment while you read!</p></blockquote><h2 id="setting-up-the-project">Setting Up the Project</h2><p>First, let&rsquo;s create our accounting dashboard app. Open your terminal and run:</p><pre class=" language-bash"><code class="prism  language-bash">ng new accounting-dashboard --standalone --style<span class="token operator">=</span>scss
</code></pre><p>Add the libraries we will use across the article. Since Kendo UI components need internationalization and animations, we will also include those packages:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> @progress/kendo-angular-grid @progress/kendo-angular-dropdowns @progress/kendo-angular-dateinputs @progress/kendo-angular-buttons @progress/kendo-angular-inputs @progress/kendo-theme-default
<span class="token function">npm</span> <span class="token function">install</span> @ngrx/signals @ngrx/store @ngrx/effects @ngrx/entity @ngrx/operators @ngrx/signals/events
<span class="token function">npm</span> <span class="token function">install</span> @angular/localize @angular/animations
</code></pre><p>Import the Kendo UI theme in <code>src/styles.scss</code>:</p><pre class=" language-scss"><code class="prism  language-scss"><span class="token keyword">@import</span> <span class="token string">"@progress/kendo-theme-default/dist/all.css"</span><span class="token punctuation">;</span>
</code></pre><h3 id="configuring-polyfills">Configuring Polyfills</h3><p>Kendo UI relies on Angular&rsquo;s localization. Open your <code>angular.json</code> and add <code>@angular/localize/init</code> to the polyfills array of your project:</p><pre class=" language-json"><code class="prism  language-json"><span class="token string">"polyfills"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"@angular/localize/init"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
</code></pre><p>Now we have a project ready to go. Each section below builds on the same accounting theme: journal entries, chart of accounts and financial periods.</p><h3 id="project-structure">Project Structure</h3><p>Here is the folder layout we will build. Create each file as we go through the sections:</p><pre><code>src/
  app/
    journal-list/
      journal-list.component.ts
    journal-entry/
      journal-entry.component.ts
    ledger-view/
      ledger-view.component.ts
    account.service.ts
    journal.store.ts
    ledger.state.ts
    period.actions.ts
    period.effects.ts
    app.component.ts
    app.config.ts
</code></pre><p>We need to configure the app to support HTTP, animations, and modern features. In Angular 22, <code>provideHttpClient()</code> uses the Fetch API by default. Update <code>app.config.ts</code>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> ApplicationConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideHttpClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideAnimations <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/platform-browser/animations'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> appConfig<span class="token punctuation">:</span> ApplicationConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token function">provideHttpClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function">provideAnimations</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>The <code>app.component.ts</code> connects all pieces. We will update it as we add each section:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> JournalListComponent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./journal-list/journal-list.component'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> JournalEntryComponent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./journal-entry/journal-entry.component'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> LedgerViewComponent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./ledger-view/ledger-view.component'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-root'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>JournalListComponent<span class="token punctuation">,</span> JournalEntryComponent<span class="token punctuation">,</span> LedgerViewComponent<span class="token punctuation">]</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;h1&gt;Accounting Dashboard&lt;/h1&gt;
    &lt;div style="display: flex; gap: 20px;"&gt;
      &lt;app-journal-entry /&gt;
      &lt;app-journal-list /&gt;
      &lt;app-ledger-view /&gt;
    &lt;/div&gt;
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppComponent</span> <span class="token punctuation">{</span>
  title <span class="token operator">=</span> <span class="token string">'Accounting Dashboard'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>So far we created the project. Now let&rsquo;s understand what we are solving.</p><h2 id="what-problem-does-state-management-solve">What Problem Does State Management Solve?</h2><p>Think of your app as a whiteboard. Many components write and read data on it. Without clear rules, one part of the app might delete data that another part needs. Or, two parts might try to change the exact same data at the same time. Using state management is simply the set of rules for who can read, who can write and how everyone stays updated.</p><p>As we talked about in our previous <a target="_blank" href="https://www.telerik.com/blogs/decision-guide-angular-state-management-signals-services-signalstore-ngrx">Decision Guide for Angular State Management</a>, an app has different types of state. Some state is very simple and only belongs to one component, like a search box. Other state is global and shared across the whole app, like opening or closing a financial month in our accounting app.</p><p>Because these types of data are so different, we cannot manage them all the same way. We need to pick the right tool for each job.</p><p>Let&rsquo;s start with the simplest one.</p><h2 id="local-state-with-signals">Local State with Signals</h2><p>The first rule of Angular state management: start with the simplest solution.</p><p>What problem does this solve for us? When data only lives in one component, why add a complex library?</p><p>In Angular 22, <code>OnPush</code> is the default way components check for changes. This means components only update when a signal changes or a new input arrives. This makes <code>signal()</code> a great choice for local state: it is fast, simple and updates only the parts of the app that changed.</p><p>In our accounting dashboard, imagine a list with a search filter. The search text belongs to this component alone. No other component needs it.</p><blockquote><p><strong>Note:</strong> To keep this guide practical and visual, we will include some sample data in our examples. This allows you to see the Kendo UI components in action immediately without needing a backend server.</p></blockquote><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> signal<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> CurrencyPipe <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_TEXTBOX <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-inputs'</span><span class="token punctuation">;</span>

<span class="token keyword">interface</span> <span class="token class-name">JournalEntry</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  date<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  description<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  total<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-journal-list'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CurrencyPipe<span class="token punctuation">,</span> KENDO_TEXTBOX<span class="token punctuation">]</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;kendo-textbox
      [value]="searchTerm()"
      (valueChange)="searchTerm.set($event)"
      placeholder="Search entries..."
      [clearButton]="true"
    /&gt;

    &lt;p&gt;Showing {{ filteredCount() }} of {{ entries().length }} entries&lt;/p&gt;

    @for (entry of filteredEntries(); track entry.id) {
      &lt;div&gt;
        {{ entry.date }} - {{ entry.description }}: {{ entry.total | currency }}
      &lt;/div&gt;
    } @empty {
      &lt;p&gt;No entries found.&lt;/p&gt;
    }
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">JournalListComponent</span> <span class="token punctuation">{</span>
  readonly entries <span class="token operator">=</span> signal<span class="token operator">&lt;</span>JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
    <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-07-01'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Monthly Subscription Sale'</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token number">150.00</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-07-02'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Office Supplies'</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token number">45.50</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-07-05'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Consulting Service'</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token number">1200.00</span> <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  readonly searchTerm <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly filteredEntries <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>e <span class="token operator">=&gt;</span>
      e<span class="token punctuation">.</span>description<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">searchTerm</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
  readonly filteredCount <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">filteredEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Notice the <code>@for</code> control flow syntax and the <code>@empty</code> block. No <code>*ngFor</code> here&mdash;this is modern Angular.</p><p>Also notice: no imports from NgRx, no actions, no reducers. Just two <code>signal()</code> calls and a <code>computed()</code>. That is it.</p><p>The <code>&lt;kendo-textbox&gt;</code> comes from Kendo UI and gives us a styled input with zero CSS work.</p><blockquote><p><strong>Tip:</strong> If you want to avoid filtering on every keystroke, Angular 22 offers a new (experimental) <code>debounced()</code> function. It creates a debounced version of a signal that you can consume in your <code>computed()</code> logic.</p></blockquote><p><strong>When to use this approach:</strong> State that only one component and its children need. Examples: search filters, toggle visibility, form input values, selected tab index.</p><p>So far we have local state, but what happens when two unrelated components need the same data? Let&rsquo;s move to shared state.</p><h2 id="shared-state-with-services">Shared State with Services</h2><p>When many components need the same data, a service with Signals is the right tool.</p><p>You could pass data through inputs and outputs. But that gets difficult when components are in different parts of the app.</p><p>Angular 22 introduces the <code>@Service</code> decorator. It is a new, shorter way to create services that are available everywhere in your app. It makes it easy to use the <code>inject()</code> function, which keeps our code clean and reactive. This is a great but often forgotten pattern in Angular. Seriously.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Service<span class="token punctuation">,</span> computed<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> httpResource <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Account</span> <span class="token punctuation">{</span>
  code<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  name<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'asset'</span> <span class="token operator">|</span> <span class="token string">'liability'</span> <span class="token operator">|</span> <span class="token string">'equity'</span> <span class="token operator">|</span> <span class="token string">'revenue'</span> <span class="token operator">|</span> <span class="token string">'expense'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Service</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AccountService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly _accountsRes <span class="token operator">=</span> httpResource<span class="token operator">&lt;</span>Account<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token string">'/api/accounts'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly _localAccounts <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Account<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'101'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Cash'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'asset'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'102'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Accounts Receivable'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'asset'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'401'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Sales Revenue'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'revenue'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'501'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Operating Expenses'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'expense'</span> <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly _selectedCode <span class="token operator">=</span> signal<span class="token operator">&lt;</span><span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly accounts <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span>
    <span class="token operator">...</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_accountsRes<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">_localAccounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  readonly isLoading <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_accountsRes<span class="token punctuation">.</span>isLoading<span class="token punctuation">;</span>
  readonly selectedCode <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_selectedCode<span class="token punctuation">.</span><span class="token function">asReadonly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly selectedAccount <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">accounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>a <span class="token operator">=&gt;</span> a<span class="token punctuation">.</span>code <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">_selectedCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly revenueAccounts <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">accounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>a <span class="token operator">=&gt;</span> a<span class="token punctuation">.</span><span class="token keyword">type</span> <span class="token operator">===</span> <span class="token string">'revenue'</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">selectAccount</span><span class="token punctuation">(</span>code<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_selectedCode<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">addAccount</span><span class="token punctuation">(</span>account<span class="token punctuation">:</span> Account<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_localAccounts<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>list <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>list<span class="token punctuation">,</span> account<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now any component can inject <code>AccountService</code> and get the same data. The journal entry form uses a Kendo UI DropDownList to select accounts:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_DROPDOWNLIST <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-dropdowns'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AccountService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../account.service'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-journal-entry'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_DROPDOWNLIST<span class="token punctuation">]</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;h3&gt;New Journal Entry&lt;/h3&gt;

    &lt;label&gt;Revenue Account:&lt;/label&gt;
    &lt;kendo-dropdownlist
      [data]="revenueAccounts()"
      textField="name"
      valueField="code"
      [valuePrimitive]="true"
      [defaultItem]="{ code: '', name: 'Select account...' }"
      (valueChange)="onSelectAccount($event)"
    &gt;
    &lt;/kendo-dropdownlist&gt;

    @if (selectedAccount(); as acct) {
      &lt;p&gt;Selected: {{ acct.name }} - {{ acct.type }}&lt;/p&gt;
    }
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">JournalEntryComponent</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly accountService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>AccountService<span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly revenueAccounts <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>accountService<span class="token punctuation">.</span>revenueAccounts<span class="token punctuation">;</span>
  readonly selectedAccount <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>accountService<span class="token punctuation">.</span>selectedAccount<span class="token punctuation">;</span>

  <span class="token function">onSelectAccount</span><span class="token punctuation">(</span>code<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>accountService<span class="token punctuation">.</span><span class="token function">selectAccount</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>&lt;kendo-dropdownlist&gt;</code> handles the UI: search within the dropdown, keyboard navigation, disabled state. We get this for free.</p><blockquote><p><strong>Tip:</strong> For heavy services that are only needed occasionally (like generating a PDF report), use <code>injectAsync()</code>. It allows you to lazy-load the service instance only when it is actually called, reducing your initial bundle size.</p></blockquote><p><strong>When to use this approach:</strong> State shared by 2 to 10 components within the same feature. Examples: chart of accounts, user preferences for a module, selected filters shared between list and detail views.</p><p><strong>When to escalate:</strong> You need strict conventions, computed state with complex rules or DevTools for debugging. That is SignalStore territory.</p><h3 id="signals-or-behaviorsubject">Signals or BehaviorSubject?</h3><p>I often ask myself: should I use <code>signal()</code> or <code>BehaviorSubject</code>?</p><p>In the <a target="_blank" href="https://www.telerik.com/blogs/decision-guide-angular-state-management-signals-services-signalstore-ngrx">Decision Guide for Angular State Management</a> post, I compared both options. My advice is simple. For new code, Signals are the best choice. They are very easy to read, and they work perfectly with Angular. I only use RxJS (<code>BehaviorSubject</code>) for complex tasks. For example, I use it to delay a search (debounce) or to stop a web request. For normal data, I just use Signals.</p><p>So far, we have shared data using a simple service. Now, let&rsquo;s add some rules and structure.</p><h2 id="ngrx-signalstore">NgRx SignalStore</h2><p>SignalStore gives you structure without all the extra code of classic NgRx. It is the recommended state management solution for Angular apps (NgRx team, v19+).</p><p>A simple service works well for basic state. But as your feature grows, you want rules: computed values, loading states and a consistent way to change data. Without these, team members might name methods differently or change data in confusing ways.</p><p>Think of SignalStore as the middle ground between a simple service and a complex Redux store.</p><p>In our accounting app, journal entries have rules: debits and credits must balance. A <code>JournalStore</code> can check this automatically with computed values.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> signalStore<span class="token punctuation">,</span> withState<span class="token punctuation">,</span> withComputed<span class="token punctuation">,</span> withMethods<span class="token punctuation">,</span> patchState<span class="token punctuation">,</span> withHooks <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> computed<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> HttpClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> rxMethod <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals/rxjs-interop'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> tapResponse <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/operators'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> pipe<span class="token punctuation">,</span> switchMap<span class="token punctuation">,</span> tap <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">JournalLine</span> <span class="token punctuation">{</span>
  accountCode<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  debit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  credit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">JournalEntry</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  date<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  description<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  lines<span class="token punctuation">:</span> JournalLine<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">type</span> JournalState <span class="token operator">=</span> <span class="token punctuation">{</span>
  entries<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  isLoading<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> initialState<span class="token punctuation">:</span> JournalState <span class="token operator">=</span> <span class="token punctuation">{</span>
  entries<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
      date<span class="token punctuation">:</span> <span class="token string">'2026-07-01'</span><span class="token punctuation">,</span>
      description<span class="token punctuation">:</span> <span class="token string">'Initial Opening Balance'</span><span class="token punctuation">,</span>
      lines<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'101'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">5000</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'301'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">5000</span> <span class="token punctuation">}</span>
      <span class="token punctuation">]</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      id<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
      date<span class="token punctuation">:</span> <span class="token string">'2026-07-08'</span><span class="token punctuation">,</span>
      description<span class="token punctuation">:</span> <span class="token string">'Service Revenue Recognition'</span><span class="token punctuation">,</span>
      lines<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'102'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">1200</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'401'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">1200</span> <span class="token punctuation">}</span>
      <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
  isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">totalDebits</span><span class="token punctuation">(</span>entries<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> entries<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    sum <span class="token operator">+</span> e<span class="token punctuation">.</span>lines<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> l<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> s <span class="token operator">+</span> l<span class="token punctuation">.</span>debit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">totalCredits</span><span class="token punctuation">(</span>entries<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> entries<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    sum <span class="token operator">+</span> e<span class="token punctuation">.</span>lines<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> l<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> s <span class="token operator">+</span> l<span class="token punctuation">.</span>credit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> JournalStore <span class="token operator">=</span> <span class="token function">signalStore</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">'root'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token function">withState</span><span class="token punctuation">(</span>initialState<span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token function">withComputed</span><span class="token punctuation">(</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    entryCount<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span>
    totalDebits<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">totalDebits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    totalCredits<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">totalCredits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    isBalanced<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">totalDebits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token function">totalCredits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token function">withMethods</span><span class="token punctuation">(</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> http <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>HttpClient<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    loadEntries<span class="token punctuation">:</span> rxMethod<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>
      <span class="token function">pipe</span><span class="token punctuation">(</span>
        <span class="token function">tap</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token function">switchMap</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
          http<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token operator">&lt;</span>JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'/api/journal-entries'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>
            <span class="token function">tapResponse</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
              next<span class="token punctuation">:</span> <span class="token punctuation">(</span>entries<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> entries<span class="token punctuation">,</span> isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
              error<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
          <span class="token punctuation">)</span>
        <span class="token punctuation">)</span>
      <span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">,</span>

    <span class="token function">addEntry</span><span class="token punctuation">(</span>entry<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> entries<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> entry<span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token function">withHooks</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function">onInit</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      store<span class="token punctuation">.</span><span class="token function">loadEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p><strong>Tip:</strong> For simple data loading, prefer <code>async/await</code> with <code>patchState</code> or Angular&rsquo;s <code>httpResource</code> (v19+). Reserve <code>rxMethod</code> for complex reactive flows like cancellation or dependent requests and with <code>httpResource</code>, the loading state is managed automatically:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> httpResource <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>

<span class="token comment">// in a component or service:</span>
readonly entriesResource <span class="token operator">=</span> httpResource<span class="token operator">&lt;</span>JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token string">'/api/journal-entries'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
readonly entries <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>entriesResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
readonly isLoading <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>entriesResource<span class="token punctuation">.</span>isLoading<span class="token punctuation">;</span>
</code></pre><p>Now a component can use the store and display the results in a Kendo UI for Angular Grid:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> CurrencyPipe <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-grid'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> JournalStore<span class="token punctuation">,</span> JournalLine <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../journal.store'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-ledger-view'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CurrencyPipe<span class="token punctuation">,</span> KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;h3&gt;General Ledger&lt;/h3&gt;

    @if (store.isLoading()) {
      &lt;p&gt;Loading entries...&lt;/p&gt;
    } @else {
      &lt;kendo-grid [data]="store.entries()"&gt;
        &lt;kendo-grid-column field="date" title="Date" [width]="120"&gt;
        &lt;/kendo-grid-column&gt;
        &lt;kendo-grid-column field="description" title="Description" [width]="200"&gt;
        &lt;/kendo-grid-column&gt;
        &lt;kendo-grid-column title="Debit" [width]="120"&gt;
          &lt;ng-template kendoGridCellTemplate let-dataItem&gt;
            {{ getDebitTotal(dataItem.lines) | currency }}
          &lt;/ng-template&gt;
        &lt;/kendo-grid-column&gt;
        &lt;kendo-grid-column title="Credit" [width]="120"&gt;
          &lt;ng-template kendoGridCellTemplate let-dataItem&gt;
            {{ getCreditTotal(dataItem.lines) | currency }}
          &lt;/ng-template&gt;
        &lt;/kendo-grid-column&gt;
      &lt;/kendo-grid&gt;
    }

    @if (!store.isBalanced()) {
      &lt;div style="color: red; margin-top: 8px;"&gt;
        Warning: Journal entries are not balanced!
      &lt;/div&gt;
    }
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LedgerViewComponent</span> <span class="token punctuation">{</span>
  readonly store <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>JournalStore<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">getDebitTotal</span><span class="token punctuation">(</span>lines<span class="token punctuation">:</span> JournalLine<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> lines<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> l<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> s <span class="token operator">+</span> l<span class="token punctuation">.</span>debit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">getCreditTotal</span><span class="token punctuation">(</span>lines<span class="token punctuation">:</span> JournalLine<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> lines<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> l<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> s <span class="token operator">+</span> l<span class="token punctuation">.</span>credit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>&lt;kendo-grid&gt;</code> gives us sorting, paging, and column resize out of the box. And the <code>isBalanced()</code> computed signal shows a warning instantly when numbers do not match.</p><h3 id="adding-more-signalstore-features">Adding More SignalStore Features</h3><p>SignalStore has more tools to help you write code faster without leaving the signal model.</p><p><strong><code>withProps</code></strong> adds injected services or static properties to the store. In our case, we can inject the <code>AccountService</code> from the previous section:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> withProps <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AccountService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./account.service'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> JournalStore <span class="token operator">=</span> <span class="token function">signalStore</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">'root'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">withState</span><span class="token punctuation">(</span>initialState<span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withComputed</span><span class="token punctuation">(</span><span class="token comment">/* ... */</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withProps</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    accountService<span class="token punctuation">:</span> <span class="token function">inject</span><span class="token punctuation">(</span>AccountService<span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withMethods</span><span class="token punctuation">(</span><span class="token comment">/* ... */</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p><strong><code>withHooks</code></strong> provides <code>onInit</code> and <code>onDestroy</code> lifecycle hooks. As shown in our main example, this is the modern way to load data when the store is first used, keeping your components clean and free of lifecycle interfaces:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> withHooks <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> JournalStore <span class="token operator">=</span> <span class="token function">signalStore</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">'root'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">withState</span><span class="token punctuation">(</span>initialState<span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withComputed</span><span class="token punctuation">(</span><span class="token comment">/* ... */</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withMethods</span><span class="token punctuation">(</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    loadEntries<span class="token punctuation">:</span> rxMethod<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token comment">/* ... */</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withHooks</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function">onInit</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      store<span class="token punctuation">.</span><span class="token function">loadEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p><strong>Entity Management</strong> with <code>@ngrx/signals/entities</code> gives us <code>withEntities()</code>, <code>setAllEntities</code>, <code>addEntity</code> and more. For our accounts, this enables efficient lookups by code:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> withEntities<span class="token punctuation">,</span> setAllEntities<span class="token punctuation">,</span> addEntity<span class="token punctuation">,</span> entityConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals/entities'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> signalStore<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Account <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./account.service'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> accountConfig <span class="token operator">=</span> <span class="token function">entityConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  entity<span class="token punctuation">:</span> <span class="token keyword">type</span><span class="token operator">&lt;</span>Account<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  collection<span class="token punctuation">:</span> <span class="token string">'accounts'</span><span class="token punctuation">,</span>
  selectId<span class="token punctuation">:</span> <span class="token punctuation">(</span>account<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> account<span class="token punctuation">.</span>code<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> AccountStore <span class="token operator">=</span> <span class="token function">signalStore</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">'root'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">withEntities</span><span class="token punctuation">(</span>accountConfig<span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withMethods</span><span class="token punctuation">(</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function">loadAccounts</span><span class="token punctuation">(</span>accounts<span class="token punctuation">:</span> Account<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token function">setAllEntities</span><span class="token punctuation">(</span>accounts<span class="token punctuation">,</span> accountConfig<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function">addAccount</span><span class="token punctuation">(</span>account<span class="token punctuation">:</span> Account<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token function">addEntity</span><span class="token punctuation">(</span>account<span class="token punctuation">,</span> accountConfig<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="events-plugin-communication-between-stores">Events Plugin: Communication Between Stores</h3><p>What happens when two stores need to talk to each other? For example, when you close a month, both the journal store and the ledger store need to stop allowing changes.</p><p>The Events Plugin (<code>@ngrx/signals/events</code>) solves this without making the stores depend on each other. Think of it as a communication system for your stores.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> eventGroup <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals/events'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> accountingEvents <span class="token operator">=</span> <span class="token function">eventGroup</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  source<span class="token punctuation">:</span> <span class="token string">'Accounting'</span><span class="token punctuation">,</span>
  events<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    entryPosted<span class="token punctuation">:</span> <span class="token punctuation">(</span>entryId<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> entryId<span class="token punctuation">,</span> total <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    periodClosed<span class="token punctuation">:</span> <span class="token punctuation">(</span>periodId<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> periodId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    periodReopened<span class="token punctuation">:</span> <span class="token punctuation">(</span>periodId<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> periodId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Any store can react to these events using <code>on</code> and <code>withReducer</code>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> on<span class="token punctuation">,</span> withReducer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals/events'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> JournalStore <span class="token operator">=</span> <span class="token function">signalStore</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">'root'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">withState</span><span class="token punctuation">(</span>initialState<span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">withReducer</span><span class="token punctuation">(</span>
    <span class="token function">on</span><span class="token punctuation">(</span>accountingEvents<span class="token punctuation">.</span>periodClosed<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token operator">...</span>state<span class="token punctuation">,</span>
      isReadOnly<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token function">on</span><span class="token punctuation">(</span>accountingEvents<span class="token punctuation">.</span>periodReopened<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token operator">...</span>state<span class="token punctuation">,</span>
      isReadOnly<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This approach is great when multiple features need to stay updated without importing each other&rsquo;s stores.</p><p>So far we covered SignalStore. Now let&rsquo;s look at the classic NgRx Store and when it still makes sense.</p><h2 id="classic-ngrx-store">Classic NgRx Store</h2><p>The classic NgRx Store (actions, reducers, selectors, effects) is best for very complex data. SignalStore works for most features, but some situations need the full Redux pattern.</p><p>This approach is great when:</p><ul><li>Many features share a large amount of connected data.</li><li>Your team needs Redux DevTools for advanced debugging.</li><li>You need a clear record of every state change.</li><li>Complex async workflows involve multiple services.</li></ul><p>In our accounting app, managing monthly periods fits here. Opening and closing a month affects entries, the ledger and reports. Getting this wrong can cause data errors.</p><h3 id="entities-with-ngrxentity">Entities with @ngrx/entity</h3><p>For normalized data like the general ledger, <code>@ngrx/entity</code> provides <code>createEntityAdapter</code> with built-in CRUD:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createEntityAdapter<span class="token punctuation">,</span> EntityState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/entity'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">LedgerEntry</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  accountCode<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  debit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  credit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  periodId<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> ledgerAdapter <span class="token operator">=</span> createEntityAdapter<span class="token operator">&lt;</span>LedgerEntry<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selectId<span class="token punctuation">:</span> <span class="token punctuation">(</span>entry<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> entry<span class="token punctuation">.</span>id<span class="token punctuation">,</span>
  sortComparer<span class="token punctuation">:</span> <span class="token punctuation">(</span>a<span class="token punctuation">,</span> b<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> a<span class="token punctuation">.</span>id <span class="token operator">-</span> b<span class="token punctuation">.</span>id<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">LedgerState</span> <span class="token keyword">extends</span> <span class="token class-name">EntityState</span><span class="token operator">&lt;</span>LedgerEntry<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
  isLoading<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
  selectedPeriodId<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> initialState<span class="token punctuation">:</span> LedgerState <span class="token operator">=</span> ledgerAdapter<span class="token punctuation">.</span><span class="token function">getInitialState</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
  selectedPeriodId<span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The reducer uses adapter methods directly:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> ledgerReducer <span class="token operator">=</span> <span class="token function">createReducer</span><span class="token punctuation">(</span>
  initialState<span class="token punctuation">,</span>
  <span class="token function">on</span><span class="token punctuation">(</span>LedgerActions<span class="token punctuation">.</span>loadSuccess<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">,</span> <span class="token punctuation">{</span> entries <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    ledgerAdapter<span class="token punctuation">.</span><span class="token function">setAll</span><span class="token punctuation">(</span>entries<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">on</span><span class="token punctuation">(</span>LedgerActions<span class="token punctuation">.</span>addEntry<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">,</span> <span class="token punctuation">{</span> entry <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    ledgerAdapter<span class="token punctuation">.</span><span class="token function">addOne</span><span class="token punctuation">(</span>entry<span class="token punctuation">,</span> state<span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="actions-and-effects-for-period-management">Actions and Effects for Period Management</h3><p>For financial periods, each action is a named event with a clear meaning:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createActionGroup<span class="token punctuation">,</span> props <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/store'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> PeriodActions <span class="token operator">=</span> <span class="token function">createActionGroup</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  source<span class="token punctuation">:</span> <span class="token string">'Financial Period'</span><span class="token punctuation">,</span>
  events<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token string">'Open Period'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> periodId<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string">'Close Period'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> periodId<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string">'Period Closed'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> periodId<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span> closedAt<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string">'Period Error'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Effects handle the async workflow:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createEffect<span class="token punctuation">,</span> Actions<span class="token punctuation">,</span> ofType <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/effects'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> switchMap<span class="token punctuation">,</span> map<span class="token punctuation">,</span> catchError <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs/operators'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token keyword">of</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> closePeriod$ <span class="token operator">=</span> <span class="token function">createEffect</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span>actions$ <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>Actions<span class="token punctuation">)</span><span class="token punctuation">,</span> ledgerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>LedgerService<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    actions$<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>
      <span class="token function">ofType</span><span class="token punctuation">(</span>PeriodActions<span class="token punctuation">.</span>closePeriod<span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token function">switchMap</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">{</span> periodId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
        ledgerService<span class="token punctuation">.</span><span class="token function">closePeriod</span><span class="token punctuation">(</span>periodId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>
          <span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>closedAt<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> PeriodActions<span class="token punctuation">.</span><span class="token function">periodClosed</span><span class="token punctuation">(</span><span class="token punctuation">{</span> periodId<span class="token punctuation">,</span> closedAt <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          <span class="token function">catchError</span><span class="token punctuation">(</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">of</span><span class="token punctuation">(</span>PeriodActions<span class="token punctuation">.</span><span class="token function">periodError</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> err<span class="token punctuation">.</span>message <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">)</span>
      <span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span> functional<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This is more code than SignalStore. And it should be. The benefit is having a clear history: every time a month is closed, it is recorded as an action with a timestamp. DevTools can show you exactly what happened.</p><p><strong>Every tool has pros and cons.</strong> Classic NgRx is powerful but needs more code. Use it only for very complex features. For most other things, SignalStore is enough.</p><p>Now that we have seen all the options, how do you decide which one to use?</p><h2 id="how-to-choose-the-right-approach">How to Choose the Right Approach</h2><p>Now that we have seen all the options, how do you decide which one to use? As explained in the Decision Guide, there is no single answer for every case.</p><p>The best choice depends on how much you share your data and how complex it is.</p><p>Let&rsquo;s apply those rules to our accounting app. The answer for each part is clear:</p><ul><li>Search filter: Local Signals. Just a <code>signal('')</code> in the component.</li><li>Account list: Service with Signals. An <code>AccountService</code> shared between the form and the report.</li><li>Journal entries: SignalStore. A <code>JournalStore</code> to check balance rules.</li><li>Closing a month: Classic Store. Full Redux with actions, effects and DevTools.</li></ul><p>A team building this app would use all four tools in the same project. This is completely fine. It is just about using the right tool for each job.</p><p>A great state management strategy is only complete if you can test it easily.</p><h2 id="testing-each-approach">Testing Each Approach</h2><p>Testing state management is simpler with Signals. Here is how each approach works:</p><h3 id="testing-local-signals">Testing Local Signals</h3><p>Signals are synchronous. Set a value, read it, assert:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'filters entries by description'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> fixture <span class="token operator">=</span> TestBed<span class="token punctuation">.</span><span class="token function">createComponent</span><span class="token punctuation">(</span>JournalListComponent<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> component <span class="token operator">=</span> fixture<span class="token punctuation">.</span>componentInstance<span class="token punctuation">;</span>
  component<span class="token punctuation">.</span>entries<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-01-01'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Sale'</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  component<span class="token punctuation">.</span>searchTerm<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token string">'sale'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">expect</span><span class="token punctuation">(</span>component<span class="token punctuation">.</span><span class="token function">filteredCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="testing-with-service-with-signals">Testing with Service with Signals</h3><p>Test the service as an injectable:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'adds an account to the chart of accounts'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> service <span class="token operator">=</span> TestBed<span class="token punctuation">.</span><span class="token function">inject</span><span class="token punctuation">(</span>AccountService<span class="token punctuation">)</span><span class="token punctuation">;</span>
  service<span class="token punctuation">.</span><span class="token function">addAccount</span><span class="token punctuation">(</span><span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'101'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Cash'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'asset'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">expect</span><span class="token punctuation">(</span>service<span class="token punctuation">.</span><span class="token function">accounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="testing-with-signalstore">Testing with SignalStore</h3><p>Inject the store like a service:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'computes total debits correctly'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> store <span class="token operator">=</span> TestBed<span class="token punctuation">.</span><span class="token function">inject</span><span class="token punctuation">(</span>JournalStore<span class="token punctuation">)</span><span class="token punctuation">;</span>
  store<span class="token punctuation">.</span><span class="token function">addEntry</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-01-01'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Test'</span><span class="token punctuation">,</span> lines<span class="token punctuation">:</span> <span class="token punctuation">[</span>
      <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'101'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">100</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'401'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">expect</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">totalDebits</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">expect</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">totalCredits</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">expect</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">isBalanced</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBeTrue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="testing-with-classic-store">Testing with Classic Store</h3><p>Use <code>provideMockStore</code> and <code>provideMockActions</code> for isolated tests:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'closes a period via reducer'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">periodReducer</span><span class="token punctuation">(</span>initialState<span class="token punctuation">,</span> PeriodActions<span class="token punctuation">.</span><span class="token function">closePeriod</span><span class="token punctuation">(</span><span class="token punctuation">{</span> periodId<span class="token punctuation">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">expect</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>periods<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>p <span class="token operator">=&gt;</span> p<span class="token punctuation">.</span>id <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span>status<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token string">'closed'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The trend across all approaches is toward simpler tests. Signals let you assert synchronously without <code>fakeAsync</code> or <code>async</code> pipes.</p><p>With our testing strategy ready, here is a quick summary.</p><blockquote><p>In modern Angular, I prefer to start simple and use local signals first. If things becomes complex, then I move data to a service only when I need to share it. Signals and services are now the standard choice for most of our data. I only pick complex libraries, like classic NgRx, for hard problems. For example, I use them if I need to track exactly how data changes over time.</p></blockquote><p>But remember, state management is not about strict rules. It is simply about choosing the best tool for your project and your team.</p><h2 id="try-kendo-ui-for-angular">Try Kendo UI for Angular</h2><p>You can download a free 30-day trial to try out everythign we&rsquo;ve explored in better detail.</p><p><a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Try Now</a></p><h3 id="open-challenge">Open Challenge</h3><p>Try these ideas to practice what we learned:</p><ul><li>Add a Kendo UI Chart that shows the balance trend using <code>computed()</code> signals.</li><li>Use <code>httpResource</code> in the <code>JournalStore</code> to load data.</li></ul><p>Try building one of these. You will see how each approach handles the task differently.</p><h3 id="references">References</h3><ul><li><a target="_blank" href="https://github.com/danywalls/accounting-dashboard-ngrx">Project Source Code (GitHub)</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components">Kendo UI for Angular Documentation</a></li></ul><img src="https://feeds.telerik.com/link/23055/17463884.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:902e921a-cf3c-4c89-9dfb-3020b23f59ae</id>
    <title type="text">Decision Guide for Angular State Management: Signals, Services, SignalStore or NgRx?</title>
    <summary type="text">When and why should you use Signals, Services, SignalStore or NgRx to manage state in your Angular app?</summary>
    <published>2026-09-09T17:20:44Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17443644/decision-guide-angular-state-management-signals-services-signalstore-ngrx"/>
    <content type="text"><![CDATA[<p><span class="featured">When and why should you use Signals, Services, SignalStore or NgRx to manage state in your Angular app?</span></p><p>This is the guide I wish I had before starting my first Angular project with state management.</p><p>Every developer needs to decide where data lives, how it changes and how different parts of the UI stay in sync. Get this wrong, and your app could become a mess of scattered data, broken UIs, and hours of debugging. Get it right, and your code stays clean, predictable and easy to change.</p><p>Most articles are outdated. They still push NgRx Store as the default answer or ignore Signals entirely. This guide is different. It covers the modern landscape: Signals, service stores, SignalStore and classic NgRx. It shows when each option makes sense and when to keep things simple.</p><p>We use examples from an accounting dashboard to make each concept concrete, so you can see how these patterns work in a real application.</p><blockquote><p><strong>Follow along:</strong> All the code for this dashboard is available on <a target="_blank" href="https://github.com/danywalls/accounting-dashboard-ngrx">GitHub</a>. Clone it and experiment while you read.</p></blockquote><h2 id="what-is-state-management-in-angular">What Is State Management in Angular?</h2><p>State management in practical terms means deciding how your app handles different types of data:</p><ul><li><strong>UI state</strong> &ndash; The search text a user types in a list. Only one component needs it.</li><li><strong>Server state</strong> &ndash; Data fetched from an API, like a list of bank accounts.</li><li><strong>Form state</strong> &ndash; User input before it is submitted.</li><li><strong>Shared application state</strong> &ndash; Data many parts of the app need, like the current user or an open financial period.</li></ul><p>Why does this matter more as Angular apps grow? More components will mean more async flows, more shared data and more chances for inconsistency. Without clear rules, one component might delete data another component needs, or two components might try to change the same data at once.</p><p>Consider concrete examples: authentication state controls what every component shows. Shopping cart state is shared across a product list, a cart view and a checkout form. Selected filters affect both a list and a detail view. Loading and error states need coordination across features. Each of these needs a different level of state management.</p><h3 id="why-angular-state-management-feels-different-nowadays">Why Angular State Management Feels Different Nowadays</h3><p><a target="_blank" href="https://angular.dev/guide/signals">Signals</a> changed things by making reactivity part of Angular itself. Before Signals, you had to pick between <a target="_blank" href="https://rxjs.dev/api/index/class/BehaviorSubject"><code>BehaviorSubject</code></a> in a service or use NgRx. Now you have <a target="_blank" href="https://angular.dev/guide/signals"><code>signal()</code></a>, <a target="_blank" href="https://angular.dev/guide/signals"><code>computed()</code></a> and <a target="_blank" href="https://angular.dev/guide/signals"><code>effect()</code></a> as built-in tools.</p><p>Modern Angular advice is less about libraries than before. In the past the answer was often &ldquo;just use NgRx.&rdquo; Now the community says: start simple and add complexity only when you need it. Most developers on Reddit and forums are practical. They use services with Signals or RxJS for shared state. They use NgRx only for large apps with complex async workflows.</p><p>This guide follows the current Angular and NgRx direction. Signals, service stores and SignalStore are the new normal.</p><h2 id="the-tiered-approach-to-state-management">The Tiered Approach to State Management</h2><p>Think of state in three levels: local component state, shared feature state and global application state.</p><p>The best approach is to start small. Move to a bigger solution only when you need to share data or coordinate across components. Different parts of the same app can use different approaches.</p><h3 id="local-component-state-with-signals">Local Component State with Signals</h3><p>When data lives in only one component, use <a target="_blank" href="https://angular.dev/guide/signals"><code>signal()</code></a>. Angular&rsquo;s change detection works with Signals by default. Components only update when a signal changes or a new input arrives. This makes <code>signal()</code> a good fit for local state: fast, simple and direct.</p><p>In our accounting dashboard, a journal list with a search filter is a good example. The search text belongs to this component alone. The <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/inputs/textbox/"><code>&lt;kendo-textbox&gt;</code></a> gives us a styled search input with no CSS work.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> signal<span class="token punctuation">,</span> computed <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> CurrencyPipe <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_TEXTBOX <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-inputs'</span><span class="token punctuation">;</span>

<span class="token keyword">interface</span> <span class="token class-name">JournalEntry</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  date<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  description<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  total<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-journal-list'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CurrencyPipe<span class="token punctuation">,</span> KENDO_TEXTBOX<span class="token punctuation">]</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;kendo-textbox
      [value]="searchTerm()"
      (valueChange)="searchTerm.set($event)"
      placeholder="Search entries..."
      [clearButton]="true"
    /&gt;

    &lt;p&gt;Showing {{ filteredCount() }} of {{ entries().length }} entries&lt;/p&gt;

    @for (entry of filteredEntries(); track entry.id) {
      &lt;div&gt;
        {{ entry.date }} - {{ entry.description }}: {{ entry.total | currency }}
      &lt;/div&gt;
    } @empty {
      &lt;p&gt;No entries found.&lt;/p&gt;
    }
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">JournalListComponent</span> <span class="token punctuation">{</span>
  readonly entries <span class="token operator">=</span> signal<span class="token operator">&lt;</span>JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
    <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-07-01'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Monthly Subscription Sale'</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token number">150.00</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-07-02'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Office Supplies'</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token number">45.50</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span> date<span class="token punctuation">:</span> <span class="token string">'2026-07-05'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Consulting Service'</span><span class="token punctuation">,</span> total<span class="token punctuation">:</span> <span class="token number">1200.00</span> <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  readonly searchTerm <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly filteredEntries <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>e <span class="token operator">=&gt;</span>
      e<span class="token punctuation">.</span>description<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">searchTerm</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
  readonly filteredCount <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">filteredEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>No NgRx imports, no actions, no reducers. Two <code>signal()</code> calls and a <a target="_blank" href="https://angular.dev/guide/signals"><code>computed()</code></a>. That is it.</p><p><strong>Good for:</strong> Little code, easy to read, works with Angular change detection, fits local component state.</p><p><strong>Not good for:</strong> When many unrelated components need the same data. A search filter in one component is fine. A list of accounts that the entry form and the ledger both need is not.</p><h3 id="shared-feature-state-with-services">Shared Feature State with Services</h3><p>When multiple components need the same data, use a service with <a target="_blank" href="https://angular.dev/guide/signals">Signals</a>. This is the next step after local state.</p><p>The pattern is simple: the service keeps writable state in <code>private</code> fields. It exposes read-only Signals or Observables to the outside. Components call methods on the service instead of changing state directly. The <a target="_blank" href="https://angular.dev/guide/di"><code>@Service()</code></a> decorator makes the service available app-wide and works with <code>inject()</code>.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Service<span class="token punctuation">,</span> computed<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> httpResource <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Account</span> <span class="token punctuation">{</span>
  code<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  name<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'asset'</span> <span class="token operator">|</span> <span class="token string">'liability'</span> <span class="token operator">|</span> <span class="token string">'equity'</span> <span class="token operator">|</span> <span class="token string">'revenue'</span> <span class="token operator">|</span> <span class="token string">'expense'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Service</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AccountService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly _accountsRes <span class="token operator">=</span> httpResource<span class="token operator">&lt;</span>Account<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token string">'/api/accounts'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly _localAccounts <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Account<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'101'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Cash'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'asset'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'102'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Accounts Receivable'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'asset'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'401'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Sales Revenue'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'revenue'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> code<span class="token punctuation">:</span> <span class="token string">'501'</span><span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token string">'Operating Expenses'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'expense'</span> <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly _selectedCode <span class="token operator">=</span> signal<span class="token operator">&lt;</span><span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly accounts <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span>
    <span class="token operator">...</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_accountsRes<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">_localAccounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  readonly isLoading <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_accountsRes<span class="token punctuation">.</span>isLoading<span class="token punctuation">;</span>
  readonly selectedCode <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_selectedCode<span class="token punctuation">.</span><span class="token function">asReadonly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly selectedAccount <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">accounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>a <span class="token operator">=&gt;</span> a<span class="token punctuation">.</span>code <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">_selectedCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly revenueAccounts <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">accounts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>a <span class="token operator">=&gt;</span> a<span class="token punctuation">.</span><span class="token keyword">type</span> <span class="token operator">===</span> <span class="token string">'revenue'</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">selectAccount</span><span class="token punctuation">(</span>code<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_selectedCode<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">addAccount</span><span class="token punctuation">(</span>account<span class="token punctuation">:</span> Account<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_localAccounts<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>list <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>list<span class="token punctuation">,</span> account<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Any component can inject <code>AccountService</code> and get the same data. The <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/dropdowns/dropdownlist/"><code>&lt;kendo-dropdownlist&gt;</code></a> gives us search, keyboard navigation and disabled state with no extra code.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_DROPDOWNLIST <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-angular-dropdowns'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AccountService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../account.service'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-journal-entry'</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_DROPDOWNLIST<span class="token punctuation">]</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;h3&gt;New Journal Entry&lt;/h3&gt;

    &lt;label&gt;Revenue Account:&lt;/label&gt;
    &lt;kendo-dropdownlist
      [data]="revenueAccounts()"
      textField="name"
      valueField="code"
      [valuePrimitive]="true"
      [defaultItem]="{ code: '', name: 'Select account...' }"
      (valueChange)="onSelectAccount($event)"
    &gt;
    &lt;/kendo-dropdownlist&gt;

    @if (selectedAccount(); as acct) {
      &lt;p&gt;Selected: {{ acct.name }} - {{ acct.type }}&lt;/p&gt;
    }
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">JournalEntryComponent</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly accountService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>AccountService<span class="token punctuation">)</span><span class="token punctuation">;</span>

  readonly revenueAccounts <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>accountService<span class="token punctuation">.</span>revenueAccounts<span class="token punctuation">;</span>
  readonly selectedAccount <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>accountService<span class="token punctuation">.</span>selectedAccount<span class="token punctuation">;</span>

  <span class="token function">onSelectAccount</span><span class="token punctuation">(</span>code<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>accountService<span class="token punctuation">.</span><span class="token function">selectAccount</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p><strong>Use this when:</strong> State is shared by 2 to 10 components in the same feature. Examples: chart of accounts, user settings for a module or filters shared between a list and a detail view.</p><p><strong>Move up when:</strong> You need strict rules, computed state with complex logic or DevTools for debugging. That is when SignalStore helps.</p><h4 id="signals-vs.-behaviorsubject-for-service-stores">Signals vs. BehaviorSubject for Service Stores</h4><p>Which one should you use, <a target="_blank" href="https://angular.dev/guide/signals"><code>signal()</code></a> or <a target="_blank" href="https://rxjs.dev/api/index/class/BehaviorSubject"><code>BehaviorSubject</code></a>?</p><table><style>table, th, td {
  border: 1px;
  border-color: #bdbdba;
  border-style: dotted;
  border-collapse: collapse;
  margin-right: auto;
  padding:0in 5.4pt 0in 5.4pt;
  text-align :left;
}
</style>
<thead><tr><th>Concern</th><th><code>signal()</code></th><th><code>BehaviorSubject</code></th></tr></thead><tbody><tr><td>Read current value</td><td><code>items()</code></td><td><code>items$.value</code></td></tr><tr><td>Derived values</td><td><a target="_blank" href="https://angular.dev/guide/signals"><code>computed()</code></a></td><td><code>pipe(map(...))</code></td></tr><tr><td>Template syntax</td><td><code>{{ items() }}</code></td><td><code>{{ items$ | async }}</code></td></tr><tr><td>Change detection</td><td>Automatic</td><td>Needs <code>OnPush</code> or <code>async</code> pipe</td></tr><tr><td>Async composition</td><td>Needs <a target="_blank" href="https://ngrx.io/guide/signals/signal-store/rxjs-integration"><code>rxMethod</code></a></td><td><code>switchMap</code>, <code>combineLatest</code></td></tr></tbody></table><br /><p>Use <a target="_blank" href="https://rxjs.dev">RxJS</a> when you need stream features like debounce, cancellation or WebSocket events. For most synchronous state, Signals are simpler.</p><p>For new code, Signals are the better default. They are simpler, synchronous and work well with Angular&rsquo;s change detection.</p><h3 id="ngrx-signalstore-and-signalstate">NgRx SignalStore and SignalState</h3><p><a target="_blank" href="https://ngrx.io/guide/signals/signal-store">SignalStore</a> gives you structure without the extra code of classic NgRx. It is the recommended state management for Angular apps (NgRx team, v19+).</p><p>A simple service works for basic state. But as your feature grows, you want computed values, loading states and a consistent way to update data. Without these, different team members might use different patterns.</p><p>SignalStore is between a service and a full Redux store. Think of it as a service with clear rules.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> signalStore<span class="token punctuation">,</span> withState<span class="token punctuation">,</span> withComputed<span class="token punctuation">,</span> withMethods<span class="token punctuation">,</span> patchState<span class="token punctuation">,</span> withHooks <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> computed<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> HttpClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> rxMethod <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/signals/rxjs-interop'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> tapResponse <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/operators'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> pipe<span class="token punctuation">,</span> switchMap<span class="token punctuation">,</span> tap <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs'</span><span class="token punctuation">;</span>

<span class="token keyword">interface</span> <span class="token class-name">JournalLine</span> <span class="token punctuation">{</span>
  accountCode<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  debit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  credit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">interface</span> <span class="token class-name">JournalEntry</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  date<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  description<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  lines<span class="token punctuation">:</span> JournalLine<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">type</span> JournalState <span class="token operator">=</span> <span class="token punctuation">{</span>
  entries<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  isLoading<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> initialState<span class="token punctuation">:</span> JournalState <span class="token operator">=</span> <span class="token punctuation">{</span>
  entries<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
      date<span class="token punctuation">:</span> <span class="token string">'2026-07-01'</span><span class="token punctuation">,</span>
      description<span class="token punctuation">:</span> <span class="token string">'Initial Opening Balance'</span><span class="token punctuation">,</span>
      lines<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'101'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">5000</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'301'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">5000</span> <span class="token punctuation">}</span>
      <span class="token punctuation">]</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      id<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
      date<span class="token punctuation">:</span> <span class="token string">'2026-07-08'</span><span class="token punctuation">,</span>
      description<span class="token punctuation">:</span> <span class="token string">'Service Revenue Recognition'</span><span class="token punctuation">,</span>
      lines<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'102'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">1200</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> accountCode<span class="token punctuation">:</span> <span class="token string">'401'</span><span class="token punctuation">,</span> debit<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> credit<span class="token punctuation">:</span> <span class="token number">1200</span> <span class="token punctuation">}</span>
      <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
  isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">totalDebits</span><span class="token punctuation">(</span>entries<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> entries<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    sum <span class="token operator">+</span> e<span class="token punctuation">.</span>lines<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> l<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> s <span class="token operator">+</span> l<span class="token punctuation">.</span>debit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">function</span> <span class="token function">totalCredits</span><span class="token punctuation">(</span>entries<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> entries<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    sum <span class="token operator">+</span> e<span class="token punctuation">.</span>lines<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> l<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> s <span class="token operator">+</span> l<span class="token punctuation">.</span>credit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> JournalStore <span class="token operator">=</span> <span class="token function">signalStore</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">'root'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token function">withState</span><span class="token punctuation">(</span>initialState<span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token function">withComputed</span><span class="token punctuation">(</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    entryCount<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span>
    totalDebits<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">totalDebits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    totalCredits<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">totalCredits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    isBalanced<span class="token punctuation">:</span> <span class="token function">computed</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">totalDebits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token function">totalCredits</span><span class="token punctuation">(</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token function">withMethods</span><span class="token punctuation">(</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> http <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>HttpClient<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
    loadEntries<span class="token punctuation">:</span> rxMethod<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>
      <span class="token function">pipe</span><span class="token punctuation">(</span>
        <span class="token function">tap</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token function">switchMap</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
          http<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token operator">&lt;</span>JournalEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'/api/journal-entries'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>
            <span class="token function">tapResponse</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
              next<span class="token punctuation">:</span> <span class="token punctuation">(</span>entries<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> entries<span class="token punctuation">,</span> isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
              error<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">:</span> <span class="token keyword">false</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
          <span class="token punctuation">)</span>
        <span class="token punctuation">)</span>
      <span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">,</span>

    <span class="token function">addEntry</span><span class="token punctuation">(</span>entry<span class="token punctuation">:</span> JournalEntry<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">patchState</span><span class="token punctuation">(</span>store<span class="token punctuation">,</span> <span class="token punctuation">{</span> entries<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>store<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> entry<span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token function">withHooks</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token function">onInit</span><span class="token punctuation">(</span>store<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      store<span class="token punctuation">.</span><span class="token function">loadEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p><a target="_blank" href="https://ngrx.io/guide/signals/signal-store#signalstate">SignalState</a> is the lighter option. It has state and computed values but no methods or hooks. Use it when you want signal-based state in a service without the full store structure.</p><p>SignalStore also supports <a target="_blank" href="https://ngrx.io/guide/signals/signal-store/entity-management">entity management</a> with <code>withEntities()</code> for CRUD operations and the <a target="_blank" href="https://ngrx.io/guide/signals/signal-store/events">Events Plugin</a> for cross-store communication. These features make SignalStore a good fit for features that need structure but would be too complex with classic NgRx.</p><h3 id="classic-ngrx-store">Classic NgRx Store</h3><p>The <a target="_blank" href="https://ngrx.io/guide/store">classic NgRx Store</a> (actions, reducers, selectors, effects) is best for very complex data. Use it when you need a clear history of changes and strict patterns, even if it means more code.</p><p>This approach is useful when:</p><ul><li>Many features share a lot of connected data.</li><li>Your team needs Redux DevTools for debugging.</li><li>You need a record of every state change.</li><li>You have complex async workflows with multiple services.</li></ul><p>In the accounting dashboard, managing monthly periods fits here. Opening and closing a month affects entries, the ledger and reports.</p><p><a target="_blank" href="https://ngrx.io/guide/store"><code>createActionGroup</code></a> and <a target="_blank" href="https://ngrx.io/guide/store"><code>props</code></a> come from <code>@ngrx/store</code>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createActionGroup<span class="token punctuation">,</span> props <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/store'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> PeriodActions <span class="token operator">=</span> <span class="token function">createActionGroup</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  source<span class="token punctuation">:</span> <span class="token string">'Financial Period'</span><span class="token punctuation">,</span>
  events<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token string">'Open Period'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> periodId<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string">'Close Period'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> periodId<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string">'Period Closed'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> periodId<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span> closedAt<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string">'Period Error'</span><span class="token punctuation">:</span> props<span class="token operator">&lt;</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Effects handle the async workflow using <a target="_blank" href="https://ngrx.io/guide/effects"><code>createEffect</code></a>, <a target="_blank" href="https://ngrx.io/guide/effects"><code>Actions</code></a> and <a target="_blank" href="https://ngrx.io/guide/effects"><code>ofType</code></a>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createEffect<span class="token punctuation">,</span> Actions<span class="token punctuation">,</span> ofType <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@ngrx/effects'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> switchMap<span class="token punctuation">,</span> map<span class="token punctuation">,</span> catchError <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs/operators'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token keyword">of</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> closePeriod$ <span class="token operator">=</span> <span class="token function">createEffect</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span>actions$ <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>Actions<span class="token punctuation">)</span><span class="token punctuation">,</span> ledgerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>LedgerService<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    actions$<span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>
      <span class="token function">ofType</span><span class="token punctuation">(</span>PeriodActions<span class="token punctuation">.</span>closePeriod<span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token function">switchMap</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">{</span> periodId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
        ledgerService<span class="token punctuation">.</span><span class="token function">closePeriod</span><span class="token punctuation">(</span>periodId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>
          <span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>closedAt<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> PeriodActions<span class="token punctuation">.</span><span class="token function">periodClosed</span><span class="token punctuation">(</span><span class="token punctuation">{</span> periodId<span class="token punctuation">,</span> closedAt <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          <span class="token function">catchError</span><span class="token punctuation">(</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">of</span><span class="token punctuation">(</span>PeriodActions<span class="token punctuation">.</span><span class="token function">periodError</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> err<span class="token punctuation">.</span>message <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">)</span>
      <span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span> functional<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This is more code than SignalStore. The benefit is a clear history. Every time a month is closed, it is recorded as an action with a timestamp. <a target="_blank" href="https://ngrx.io/guide/store-devtools">Redux DevTools</a> shows exactly what happened.</p><p><strong>Use classic NgRx only for complex coordination problems.</strong> SignalStore is enough for most features. NgRx gives you predictability, devtools and clear data flow. But it has more code and a harder learning curve. <a target="_blank" href="https://ngrx.io">NgRx</a> is powerful, but that&rsquo;s not always needed.</p><h2 id="other-libraries-and-why-they-rank-lower-in-modern-guidance">Other Libraries and Why They Rank Lower in Modern Guidance</h2><p><a target="_blank" href="https://github.com/salesforce/akita">Akita</a> and <a target="_blank" href="https://www.ngxs.io/">NGXS</a> still appear in older comparison articles. But Akita is now <a target="_blank" href="https://github.com/salesforce/akita?tab=readme-ov-file#-deprecated">deprecated</a> and should not be used for new projects. NGXS is still maintained but its community has not grown like NgRx, and it has less adoption in the Angular ecosystem.</p><p>For most projects today, the choice is between a Signal-based service, SignalStore and classic NgRx Store. These options have the widest community support, more shared knowledge and best alignment with Angular&rsquo;s future direction.</p><h2 id="how-to-choose-the-right-state-strategy">How to Choose the Right State Strategy</h2><p>There is no single answer that works for every case. Ask these questions:</p><table><style>table, th, td {
  border: 1px;
  border-color: #bdbdba;
  border-style: dotted;
  border-collapse: collapse;
  margin-right: auto;
  padding:0in 5.4pt 0in 5.4pt;
  text-align :left;
}
</style>
<thead><tr><th>Question</th><th>Best Fit</th></tr></thead><tbody><tr><td>Does this data belong to one component only?</td><td>Local Signals</td></tr><tr><td>Is this data shared across 2-10 components?</td><td>Service with Signals</td></tr><tr><td>Do you want structure and rules but not full Redux?</td><td>SignalStore</td></tr><tr><td>Is this global data used across many features?</td><td>SignalStore or Classic Store</td></tr><tr><td>Do you need Redux DevTools and a clear history?</td><td>Classic Store</td></tr><tr><td>Is your data complex with many relationships?</td><td>Classic Store + <a target="_blank" href="https://ngrx.io/guide/entity">@ngrx/entity</a></td></tr></tbody></table><br /><p>Apply this framework to real examples:</p><ul><li><strong>Local modal state</strong> &ndash; A dialog open/close flag. Use a <code>signal(false)</code> in the component.</li><li><strong>Feature-level product filters</strong> &ndash; Filters shared between a product list and a detail view. Use a service with Signals.</li><li><strong>Global auth and entity cache</strong> &ndash; User session and a cache of business data. Classic NgRx Store gives you the predictability and tooling this level needs.</li></ul><p>Different parts of the same app can use different approaches. A dashboard might use local Signals for UI state, a service for feature state and classic NgRx for global coordination. This is not wrong. It is about using the right tool for each job.</p><h2 id="state-management-in-real-angular-architectures">State Management in Real Angular Architectures</h2><p>The best state strategy depends on more than just data scope. The size of your architecture and how your team works together often matter more than library popularity.</p><ul><li><strong>Enterprise apps</strong> &ndash; Large teams benefit from the structure of SignalStore or classic NgRx. Consistent patterns make it easier for new team members to learn the codebase.</li><li><strong>Design systems</strong> &ndash; Component libraries should not use global state. Use inputs, outputs and local Signals. Let the app that uses the library decide on shared state.</li><li><strong>Admin dashboards</strong> &ndash;Many widgets sharing data makes service stores and SignalStore a good fit. Each widget can use local Signals for its own UI state while reading shared data from a store.</li><li><strong>Micro-frontends and Nx-based architectures</strong> &ndash; Each micro-app should own its own state. Classic NgRx at the shell level handles cross-app coordination. SignalStore inside each micro-app keeps feature code clean.</li></ul><h2 id="signals-rxjs-and-ngrx-how-they-work-together">Signals, RxJS and NgRx: How They Work Together</h2><p>You do not have to choose only one. The modern Angular approach uses all three together.</p><ul><li><strong>Signals for local and view-model state.</strong> Use signals in components, computed for derived state and read-only signals from services. This covers most day-to-day state needs.</li><li><strong>RxJS for async and event streams.</strong> HTTP requests, WebSocket connections, debounced input and complex stream transformations are still good use cases for RxJS. Use <a target="_blank" href="https://ngrx.io/guide/signals/signal-store/rxjs-integration"><code>rxMethod</code></a> to connect RxJS with SignalStore.</li><li><strong>NgRx when you need structure or tooling.</strong> Classic NgRx Store and SignalStore give you conventions, DevTools and predictable data flow for the features that need them most.</li></ul><p>These tools work together. A component can use a Signal for local state, read from a SignalStore for feature data and dispatch classic NgRx actions for global coordination. The choice is practical, not about following rules.</p><h2 id="key-takeaways-and-next-steps">Key Takeaways and Next Steps</h2><ol><li><strong>Start with the simplest solution that works.</strong> Use <code>signal()</code> for local data. Move to a service when you need to share data. Use libraries only when the logic gets complex.</li><li><strong>Signals and services are the new default.</strong> Most data does not need a complex store.</li><li><strong>Use classic NgRx only for complex problems.</strong> If you need a clear history of changes or DevTools, the extra code is worth it.</li><li><strong>Mix different tools in the same app.</strong> Each part of the app can use the approach that fits best.</li></ol><p>Modern Angular state management is about choosing what fits, not about following strict rules. The right answer depends on your project size, team and needs.</p><p>To learn more, check <a target="_blank" href="https://angular.dev/guide/signals">Angular Signals</a>, the <a target="_blank" href="https://ngrx.io/guide/signals/signal-store">NgRx SignalStore guide</a>, <a target="_blank" href="https://ngrx.io/guide/component-store">ComponentStore migration</a>, <a target="_blank" href="https://rxjs.dev">RxJS</a> and architecture guides for large Angular projects.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">What Enterprise Teams Actually Look for in an Angular UI Library</h4></div><div class="col-8"><p class="u-fs16 u-mb0">See what mattered when a real <a target="_blank" href="https://www.telerik.com/blogs/what-enterprise-teams-actually-look-for-angular-ui-library">enterprise team was exploring which Angular component library</a> to use, and what keeps them using Kendo UI.</p></div></div></aside><img src="https://feeds.telerik.com/link/23055/17443644.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:18a8c83a-19ac-4a74-b424-ebdf4033bd47</id>
    <title type="text">Create a Kendo UI for Angular Dinner Picker</title>
    <summary type="text">Sick of picking where to go for dinner? Build this little Angular app to help you choose!</summary>
    <published>2026-08-20T17:09:35Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17424575/create-kendo-ui-angular-dinner-picker"/>
    <content type="text"><![CDATA[<p><span class="featured">Sick of picking where to go for dinner? Build this little Angular app to help you choose!</span></p><p>Do you ever argue about where to eat? If you&rsquo;re like me, you can&rsquo;t think of places half the time, and you just wish someone would pick for you. Well, here you go!</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/chrome_v90f4i218o.gif?sfvrsn=5c1916fc_2" height="717" style="max-width:100%;height:auto;" title="Angular Restaurant Picker" width="501" alt="Angular Restaurant Picker with options for dine-in or take-out" sf-size="382850" /></p><h2 id="tldr">TL;DR</h2><p>This app will create a random dinner picker from JSON data in Angular using Progress Kendo UI. You can choose from Takeout or Dine-in.</p><h2 id="setup">Setup</h2><p>Create a new <a target="_blank" href="https://angular.dev/">Angular</a>&nbsp;app:</p><pre class=" language-html"><code class="prism  language-html">ng new angular-dinner-picker
</code></pre><h3 id="get-a-kendo-ui-license">Get a Kendo UI License</h3><p>Log in to Progress and purchase a Kendo UI for Angular license, or try it for free.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/image.png?sfvrsn=bece1600_2" height="840" style="max-width:100%;height:auto;" title="Kendo UI License Key" width="1195" alt="Kendo UI License Key" sf-size="107716" /></p><p>Download the actual key, as you will need it later for deployment.</p><h3 id="install-kendo-ui-licensing">Install Kendo UI Licensing</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i -S @progress/kendo-licensing
</code></pre><p>Then run:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><p>This will set up Kendo UI on your local machine for this project. Make sure you have a working license at this point, and to <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/licensing">follow the correct setup</a>.</p><h3 id="install-packages">Install Packages</h3><pre class=" language-bash"><code class="prism  language-bash">ng add @progress/kendo-angular-buttons
ng add @progress/kendo-angular-dropdowns
ng add @progress/kendo-angular-layout
</code></pre><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> i -S <span class="token function">npm</span> <span class="token function">install</span> @angular/forms @progress/kendo-svg-icons @progress/kendo-theme-default
</code></pre><p> You may need to also install <code>@angular/localize</code>.</p><h3 id="install-tailwind">Install Tailwind</h3><p>Make sure Angular is configured for Tailwind. Follow the <a target="_blank" href="https://tailwindcss.com/docs/installation/framework-guides/angular">Tailwind Guide</a>.</p><h3 id="configure-styles">Configure Styles</h3><p>Make sure your styles are shown in <code>styles.css</code> correctly.</p><pre class=" language-bash"><code class="prism  language-bash">@import <span class="token string">'@progress/kendo-theme-default/dist/default-main.css'</span><span class="token punctuation">;</span>
@import <span class="token string">'tailwindcss'</span><span class="token punctuation">;</span>
</code></pre><h2 id="picker-component">Picker Component</h2><p>Generate a new picker component.</p><pre class=" language-bash"><code class="prism  language-bash">ng g c picker
</code></pre><h3 id="model">Model</h3><p>Create a model at <code>picker.model.ts</code>.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">export</span> <span class="token function">type</span> RestaurantMode <span class="token operator">=</span> <span class="token string">'dine-in'</span> <span class="token operator">|</span> <span class="token string">'takeout'</span><span class="token punctuation">;</span>

<span class="token function">export</span> <span class="token function">type</span> Restaurant <span class="token operator">=</span> <span class="token punctuation">{</span>
  id: number<span class="token punctuation">;</span>
  name: string<span class="token punctuation">;</span>
  cuisine: string<span class="token punctuation">;</span>
  mode: RestaurantMode<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">export</span> <span class="token function">type</span> RestaurantModeFilter <span class="token operator">=</span> <span class="token string">'all'</span> <span class="token operator">|</span> RestaurantMode<span class="token punctuation">;</span>
</code></pre><h3 id="data">Data</h3><p>You can declare your data in the <code>restaurants.ts</code>. You could obviously get more sophisticated and import data from a database if you like.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">import</span> <span class="token punctuation">{</span> Restaurant <span class="token punctuation">}</span> from <span class="token string">"./picker.model"</span><span class="token punctuation">;</span>

<span class="token function">export</span> const restaurants: Restaurant<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 1,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Monjunis"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"Italian"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 2,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Strawn's Eat Shop"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"Southern Diner"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token keyword">:</span> 3,
    <span class="token string">"name"</span><span class="token keyword">:</span> <span class="token string">"Country Tavern"</span>,
    <span class="token string">"cuisine"</span><span class="token keyword">:</span> <span class="token string">"BBQ"</span>,
    <span class="token string">"mode"</span><span class="token keyword">:</span> <span class="token string">"dine-in"</span>
  <span class="token punctuation">}</span>,
  
  <span class="token punctuation">..</span>.
  
<span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre><p> I used my city&rsquo;s info so I can actually use the app! You can customize this and deploy it separately for different situations or cities!</p><h3 id="picker-class">Picker Class</h3><p>Create the picker classes at <code>picker.ts</code>.</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">import</span> <span class="token punctuation">{</span> ChangeDetectionStrategy, Component <span class="token punctuation">}</span> from <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> FormsModule <span class="token punctuation">}</span> from <span class="token string">'@angular/forms'</span><span class="token punctuation">;</span>

<span class="token function">import</span> <span class="token punctuation">{</span> ButtonsModule <span class="token punctuation">}</span> from <span class="token string">'@progress/kendo-angular-buttons'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> DropDownsModule <span class="token punctuation">}</span> from <span class="token string">'@progress/kendo-angular-dropdowns'</span><span class="token punctuation">;</span>

<span class="token function">import</span> <span class="token punctuation">{</span> restaurants <span class="token punctuation">}</span> from <span class="token string">'./restaurants'</span><span class="token punctuation">;</span>
<span class="token function">import</span> <span class="token punctuation">{</span> Restaurant, RestaurantModeFilter <span class="token punctuation">}</span> from <span class="token string">'./picker.model'</span><span class="token punctuation">;</span>

<span class="token function">type</span> ModeOption <span class="token operator">=</span> <span class="token punctuation">{</span>
  label: string<span class="token punctuation">;</span>
  value: RestaurantModeFilter<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

@Component<span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector: <span class="token string">'app-picker'</span>,
  standalone: true,
  imports: <span class="token punctuation">[</span>
    FormsModule,
    ButtonsModule,
    DropDownsModule
  <span class="token punctuation">]</span>,
  templateUrl: <span class="token string">'./picker.html'</span>,
  changeDetection: ChangeDetectionStrategy.OnPush
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token function">export</span> class Picker <span class="token punctuation">{</span>
  <span class="token function">readonly</span> restaurants <span class="token operator">=</span> restaurants<span class="token punctuation">;</span>

  <span class="token function">readonly</span> modeOptions: ModeOption<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      label: <span class="token string">'Any'</span>,
      value: <span class="token string">'all'</span>
    <span class="token punctuation">}</span>,
    <span class="token punctuation">{</span>
      label: <span class="token string">'Dine-in'</span>,
      value: <span class="token string">'dine-in'</span>
    <span class="token punctuation">}</span>,
    <span class="token punctuation">{</span>
      label: <span class="token string">'Takeout'</span>,
      value: <span class="token string">'takeout'</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  selectedMode: RestaurantModeFilter <span class="token operator">=</span> <span class="token string">'all'</span><span class="token punctuation">;</span>
  selectedRestaurant: Restaurant <span class="token operator">|</span> null <span class="token operator">=</span> null<span class="token punctuation">;</span>

  get filteredRestaurants<span class="token punctuation">(</span><span class="token punctuation">)</span>: Restaurant<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>this.selectedMode <span class="token operator">==</span><span class="token operator">=</span> <span class="token string">'all'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> this.restaurants<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> this.restaurants.filter<span class="token punctuation">((</span>restaurant<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> restaurant.mode <span class="token operator">==</span><span class="token operator">=</span> this.selectedMode<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  pickRestaurant<span class="token punctuation">(</span><span class="token punctuation">)</span>: void <span class="token punctuation">{</span>
    const choices <span class="token operator">=</span> this.filteredRestaurants<span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>choices.length <span class="token operator">==</span><span class="token operator">=</span> 0<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      this.selectedRestaurant <span class="token operator">=</span> null<span class="token punctuation">;</span>
      <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    const pickableChoices <span class="token operator">=</span>
      this.selectedRestaurant <span class="token operator">&amp;&amp;</span> choices.length <span class="token operator">&gt;</span> 1
        ? choices.filter<span class="token punctuation">((</span>restaurant<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> restaurant.id <span class="token operator">!=</span><span class="token operator">=</span> this.selectedRestaurant?.id<span class="token punctuation">)</span>
        <span class="token keyword">:</span> choices<span class="token punctuation">;</span>

    const index <span class="token operator">=</span> Math.floor<span class="token punctuation">(</span>Math.random<span class="token punctuation">(</span><span class="token punctuation">)</span> * pickableChoices.length<span class="token punctuation">)</span><span class="token punctuation">;</span>

    this.selectedRestaurant <span class="token operator">=</span> pickableChoices<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><ul><li>Pick the restaurant with <code>pickResaturant()</code> function choosing a random JSON entry from the filtered results.</li><li>We have <code>any</code>, <code>dine-in</code> and <code>takeout</code>.</li><li>We will use the filtered results in the html template from <code>filteredResautrants()</code>.</li></ul><h3 id="picker-template">Picker Template</h3><pre class=" language-bash"><code class="prism  language-bash"><span class="token operator">&lt;</span>main
  class<span class="token operator">=</span><span class="token string">"grid min-h-screen place-items-center bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.95),rgba(255,255,255,0)_28%),linear-gradient(180deg,#f7efe7_0%,#eef2f7_100%)] p-4 sm:p-6"</span><span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>section
    class<span class="token operator">=</span><span class="token string">"w-full max-w-sm rounded-4xl border border-slate-300/40 bg-white/92 px-6 py-7 shadow-[0_24px_60px_rgba(15,23,42,0.12)] backdrop-blur-[14px] sm:px-8 sm:py-8"</span><span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>header class<span class="token operator">=</span><span class="token string">"space-y-3 text-center"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>h1 class<span class="token operator">=</span><span class="token string">"m-0 text-[2.05rem] font-semibold leading-[0.98] tracking-tight text-slate-800 sm:text-[2.3rem]"</span><span class="token operator">&gt;</span>
        Restaurant Picker
      <span class="token operator">&lt;</span>/h1<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>p class<span class="token operator">=</span><span class="token string">"mx-auto max-w-64 text-[1rem] leading-7 text-slate-500"</span><span class="token operator">&gt;</span>
        Pick where to eat without thinking about it.
      <span class="token operator">&lt;</span>/p<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/header<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"my-6 h-px bg-slate-200/80"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

    <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"space-y-6"</span><span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"space-y-3 text-center"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>label for<span class="token operator">=</span><span class="token string">"mode"</span> class<span class="token operator">=</span><span class="token string">"block text-[1.15rem] font-semibold leading-tight text-slate-800"</span><span class="token operator">&gt;</span>
          What kind?
        <span class="token operator">&lt;</span>/label<span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>kendo-dropdownlist id<span class="token operator">=</span><span class="token string">"mode"</span> class<span class="token operator">=</span><span class="token string">"w-full text-left"</span> <span class="token punctuation">[</span>data<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"modeOptions"</span> textField<span class="token operator">=</span><span class="token string">"label"</span> valueField<span class="token operator">=</span><span class="token string">"value"</span>
          <span class="token punctuation">[</span>valuePrimitive<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"true"</span> <span class="token punctuation">[</span><span class="token punctuation">(</span>ngModel<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"selectedMode"</span> /<span class="token operator">&gt;</span>
      <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>

      <span class="token operator">&lt;</span>button kendoButton class<span class="token operator">=</span><span class="token string">"min-h-14 w-full text-base font-semibold"</span> themeColor<span class="token operator">=</span><span class="token string">"primary"</span> size<span class="token operator">=</span><span class="token string">"large"</span>
        rounded<span class="token operator">=</span><span class="token string">"large"</span> <span class="token punctuation">[</span>disabled<span class="token punctuation">]</span><span class="token operator">=</span><span class="token string">"filteredRestaurants.length === 0"</span> <span class="token punctuation">(</span>click<span class="token punctuation">)</span><span class="token operator">=</span><span class="token string">"pickRestaurant()"</span><span class="token operator">&gt;</span>
        Pick Restaurant
      <span class="token operator">&lt;</span>/button<span class="token operator">&gt;</span>

      @if <span class="token punctuation">(</span>selectedRestaurant<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token operator">&lt;</span>section
        class<span class="token operator">=</span><span class="token string">"rounded-[1.75rem] border border-rose-200/80 bg-[linear-gradient(135deg,rgba(255,241,242,0.92),rgba(255,255,255,0.98))] px-6 py-6 shadow-[inset_0_1px_0_rgba(255,255,255,0.7)]"</span>
        aria-live<span class="token operator">=</span><span class="token string">"polite"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>div class<span class="token operator">=</span><span class="token string">"grid justify-items-center gap-4 text-center"</span><span class="token operator">&gt;</span>
          <span class="token operator">&lt;</span>p class<span class="token operator">=</span><span class="token string">"m-0 text-[0.78rem] font-bold uppercase tracking-[0.2em] text-rose-700"</span><span class="token operator">&gt;</span>Tonight<span class="token string">'s pick&lt;/p&gt;

          &lt;h2
            class="m-0 max-w-[11ch] text-[clamp(1.7rem,4vw,2rem)] font-semibold leading-[1.08] tracking-tight text-slate-900 text-balance"&gt;
            {{ selectedRestaurant.name }}
          &lt;/h2&gt;

          &lt;div class="grid justify-items-center gap-3"&gt;
            &lt;p class="m-0 text-[1rem] leading-6 text-slate-700"&gt;
              {{ selectedRestaurant.cuisine }}
            &lt;/p&gt;

            &lt;span
              class="inline-flex min-h-10 items-center justify-center rounded-full border border-slate-300/40 bg-white/90 px-5 text-sm font-bold text-slate-900 shadow-[0_6px_18px_rgba(15,23,42,0.06)]"&gt;
              {{ selectedRestaurant.mode === '</span>dine-in<span class="token string">' ? '</span>Dine-in<span class="token string">' : '</span>Takeout<span class="token string">' }}
            &lt;/span&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/section&gt;
      } @else {
      &lt;div
        class="rounded-[1.75rem] border border-dashed border-slate-400/60 bg-slate-50/75 px-5 py-6 text-center text-[0.95rem] leading-6 text-slate-500"&gt;
        @if (filteredRestaurants.length === 0) {
        No restaurants found for this option.
        } @else {
        Click the button to pick a restaurant.
        }
      &lt;/div&gt;
      }

      &lt;p class="text-center text-[0.82rem] font-semibold text-slate-500"&gt;
        {{ filteredRestaurants.length }}
        restaurant{{ filteredRestaurants.length === 1 ? '</span><span class="token string">' : '</span>s' <span class="token punctuation">}</span><span class="token punctuation">}</span> available
      <span class="token operator">&lt;</span>/p<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span>/div<span class="token operator">&gt;</span>
  <span class="token operator">&lt;</span>/section<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>/main<span class="token operator">&gt;</span>
</code></pre><p>Here&rsquo;s what&rsquo;s going on above:</p><ul><li>When we run <code>pickRestaurant()</code>, the app displays the filtered items as a signal.</li><li>The <code>kendo-dropdownlist</code> component uses <code>data</code> field for options with <code>label</code> and <code>value</code> matching the drop down.</li><li>We just add <code>kendoButton</code> to our <code>button</code> component to get the look we want. We can use Tailwind with the look!</li></ul><p>And it&rsquo;s that simple!</p><p><strong>Repo:</strong> <a target="_blank" href="https://github.com/jdgamble555/angular-dinner-picker">GitHub</a><br /><strong>Demo:</strong> <a target="_blank" href="https://angular-dinner-picker.vercel.app/">Vercel Serverless</a></p><p>You can try all of this yourself with the Kendo UI for Angular trial, free for 30 days.</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" target="_blank" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/23055/17424575.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:6baa7763-d9fc-430d-8c59-6719df533ba5</id>
    <title type="text">Biometric Authentication in Angular Using Auth0</title>
    <summary type="text">Learn to build an authentication system that supports passkeys using the Auth0 SDK within an Angular application.</summary>
    <published>2026-08-03T12:17:02Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Christian Nwamba </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17403333/biometric-authentication-angular-using-auth0"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn to build an authentication system that supports passkeys using the Auth0 SDK within an Angular application.</span></p><p>Passkeys combine both possession and inherence factors (hardware and biometrics) in a single authentication method. Basically, passkeys are public key cryptographic credentials that conform to FIDO protocols that provide strong, phishing-resistant authentication unlike regular password-based systems. They also sync across your devices, so you can easily sign in from your phone, laptop or tablet.</p><p>Building an authentication system or updating an existing one to support passkeys takes a lot of time. As a developer, you need to understand complex specs and get everything right. Identity providers like <a target="_blank" href="https://auth0.com/">Auth0</a> provide an easy-to-use suite of tools that allows you to build great authentication solutions that support the latest password-based to passwordless authentication methods without writing complicated code.</p><p>The goal of this article is to show how easy it is to build an authentication system that supports passkeys using the Auth0 SDK within an Angular application.</p><h2 id="prerequisites">Prerequisites</h2><p>To follow along with this guide, you need to be familiar with the <a target="_blank" href="https://angular.dev/">Angular</a> framework, have integrated or consumed RESTful APIs, and have a basic understanding of how authentication and authorization work.</p><h2 id="project-setup">Project Setup</h2><p>Let&rsquo;s create an Angular project by running the following command:</p><pre class=" language-shell"><code class="prism  language-shell">ng new ng-webauthn
</code></pre><p>Follow the prompts and accept all the defaults. This creates an Angular project in a folder called <code>ng-webauthn</code>.</p><p>We&rsquo;ll install our dependencies after we set up Auth0. Open your terminal and start your application by running the following command:</p><pre class=" language-shell"><code class="prism  language-shell">npm run start
</code></pre><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/angular-application.png?sfvrsn=a7e0e79_2" title="Angular application running on localhost:4200" alt="Angular application running on localhost:4200" /></p><p>One key thing to notice here is that our application runs on localhost:4200 by default. We&rsquo;ll need this information when configuring our application on the Auth0 dashboard.</p><h2 id="setting-up-auth0">Setting Up Auth0</h2><p>Let&rsquo;s now set up Auth0 and integrate it into our application.</p><p>Visit the <a target="_blank" href="https://auth0.com/signup">Auth0 onboarding page</a> to create an account. On the dashboard click on <strong>Applications</strong> &gt; Create <strong>Application</strong>.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/creating-application-on-auth0-dashboard.png?sfvrsn=72358021_2" title="Creating an application on Auth0 dashboard" alt="Creating an application on Auth0 dashboard" /></p><p>Since we&rsquo;re integrating with Angular, we created a single-page application on the Auth0 dashboard with an arbitrary name: &ldquo;test-app.&rdquo; You can choose your preferred name.</p><p>Next, let&rsquo;s click on the settings tab to configure our application.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/configuring-application-callback.png?sfvrsn=c567b2ee_2" title="Configuring application callback and Logout URLs" alt="Configuring application callback and Logout URLs" /></p><p>As seen above, the <strong>Callback URLs</strong> and <strong>Logout URLs</strong> input fields accept a comma-separated list of candidate URLs that we can redirect the user to after they log in, sign up or sign out, respectively.</p><p>To understand why we need this, we need to understand the default recommended authentication method when using Auth0, hosted or universal login.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/hosted-or-universal-login.png?sfvrsn=8c8f0984_2" title="Hosted or Universal login in Auth0" alt="Hosted or Universal login in Auth0" /></p><p>In the universal login flow, when a user wants to authenticate in an application (e.g., our Angular SPA), they are redirected to a page that is hosted and managed by Auth0 servers. This page is only accessible when a valid candidate redirect URL is provided. If authentication is successful, the user is redirected back to the Angular application.</p><p>Now, let&rsquo;s install the dependencies we&rsquo;ll need. From the root of your Angular project, open your terminal and run the following command:</p><pre class=" language-shell"><code class="prism  language-shell">npm install @auth0/auth0-angular@2.x
</code></pre><p>Next, let&rsquo;s take some code from the <strong>Quick Start</strong> section to initialize our application. We&rsquo;ll start by updating the contents of the <code>app.config.ts</code> file to look like this:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> ApplicationConfig<span class="token punctuation">,</span> provideZoneChangeDetection <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/router'</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> routes <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.routes'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> provideAuth0 <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@auth0/auth0-angular'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> appConfig<span class="token punctuation">:</span> ApplicationConfig <span class="token operator">=</span> <span class="token punctuation">{</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token function">provideZoneChangeDetection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> eventCoalescing<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">provideRouter</span><span class="token punctuation">(</span>routes<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">provideAuth0</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    domain<span class="token punctuation">:</span> <span class="token string">"YOUR-DOMAIN"</span><span class="token punctuation">,</span>
    clientId<span class="token punctuation">:</span> <span class="token string">"YOUR-CLIENT-ID"</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>The <code>provideAuth0</code> function accepts an object representing the configuration for our Auth0 application, which includes our domain and client ID.</p><p>Our <code>domain</code> is a unique URL created for us by Auth0 when we created an account. The <code>clientID</code> is a unique identifier for the application. We can get these credentials in the <strong>Settings</strong> tab, as shown below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/obtaining-credentials.png?sfvrsn=1ff6aeba_2" title="Obtaining credentials to setup Auth0" alt="Obtaining credentials to setup Auth0" /></p><p>The <code>provideAuth0</code> function internally exposes a service called <code>AuthService</code>, which will be available globally to all the other components in our application tree.</p><p>Next, update your <code>app.component.ts</code> file to look like this:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> CommonModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AuthService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@auth0/auth0-angular'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-root'</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    @if (auth.isLoading$ | async) {
      &lt;div&gt;Loading...&lt;/div&gt;
    } @else {
      @if ((auth.isAuthenticated$ | async) &amp;&amp; (auth.user$ | async); as user) {
        &lt;section class="profile"&gt;
          &lt;h1&gt;Profile&lt;/h1&gt;
          @if (user.picture) {
            &lt;img class="avatar" [src]="user.picture" [alt]="user.name || 'Profile photo'" /&gt;
          }
          &lt;div&gt;
            &lt;b&gt;Name&lt;/b&gt;
            &lt;p&gt;{{ user.name || user.nickname || '--' }}&lt;/p&gt;
            &lt;b&gt;Email&lt;/b&gt;
            &lt;p&gt;{{ user.email || '--' }}&lt;/p&gt;
            @if (user.email_verified !== undefined) {
              &lt;b&gt;Email verified&lt;/b&gt;
              &lt;p&gt;{{ user.email_verified ? 'Yes' : 'No' }}&lt;/p&gt;
            }
            @if (user.sub) {
              &lt;b&gt;User ID&lt;/b&gt;
              &lt;p&gt;{{ user.sub }}&lt;/p&gt;
            }
          &lt;/div&gt;
          &lt;button
            type="button"
            (click)="
              auth.logout({
                authorizationParams: { redirect_uri: 'http://localhost:4200' },
              })
            "
          &gt;
            Log out
          &lt;/button&gt;
        &lt;/section&gt;
      } @else {
        @if (auth.error$ | async; as error) {
          &lt;p&gt;Error: {{ error.message }}&lt;/p&gt;
        }
        &lt;button
          (click)="
            auth.loginWithRedirect({
              authorizationParams: {
                screen_hint: 'signup',
                redirect_uri: 'http://localhost:4200',
              },
            })
          "
        &gt;
          Sign Up
        &lt;/button&gt;
        &lt;button
          (click)="
            auth.loginWithRedirect({
              authorizationParams: { redirect_uri: 'http://localhost:4200' },
            })
          "
        &gt;
          Log In
        &lt;/button&gt;
      }
    }
  `</span></span><span class="token punctuation">,</span>
  styles<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CommonModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppComponent</span> <span class="token punctuation">{</span>
  auth <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>AuthService<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>To simplify our application, we won&rsquo;t be using any routes. All the code we need will be contained in the root app component.</p><p>We start by injecting the <code>AuthService</code>. We also imported the <code>CommonModule</code> to use the async pipe, since most of the properties of <code>AuthService</code> are observables.</p><p>In the template, we render different pieces of UI based on the authentication state:</p><ul><li>When loading, we display a loading message.</li><li>When the user is authenticated, we display the user&rsquo;s details with a button to log out.</li><li>If there is an error from the authentication flow, we display it on the screen.</li><li>Otherwise, the user is presented with two buttons to sign up or log in.</li></ul><p>Notice that when we trigger <code>loginWithRedirect</code> and <code>logout</code>, we additionally pass a <code>redirect_url</code> with a valid value as defined in the Callback URLs input field earlier when we configured our Auth0 app.</p><p>To see the running application, open your terminal and run the following command:</p><pre class=" language-shell"><code class="prism  language-shell">npm run start
</code></pre><h2 id="adding-support-for-passkeys">Adding Support for Passkeys</h2><p>In our current application, we can sign up and log in with email and password and use SSO, but passkeys are not yet supported. Thankfully, with Auth0 and its zero-code configuration, we no longer need to change any code to support passkeys. We just need to make a few tweaks in our dashboard to get it working.</p><p>Head over to the <strong>Database Connections</strong> section and choose the database connection associated with the app.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/database-connection.png?sfvrsn=d655d586_2" title="Database connection" alt="Database connection" /></p><p>A database connection is an abstracted term in Auth0. It represents the storage layer (database) that stores all user credentials and the configuration options for how we want to identify and authenticate users. By default, a database connection is automatically created when we create an Auth0 account.</p><p>In the <strong>Authentication Methods</strong> tab, click on <strong>Passkeys</strong> and enable passkeys.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/enabling-passkeys.png?sfvrsn=7625ad0e_2" title="Enabling passkeys" alt="Enabling passkeys" /></p><p>Passkey support may not be enabled immediately. Instead, you may be presented with a popup showing a checklist of tasks you need to fulfill to make it work, as shown below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/checklist-for-passkeys.png?sfvrsn=30b3d807_2" title="Checklist before enabling passkeys" alt="Checklist before enabling passkeys" /></p><p>In our case, the only pending item is to enable identifier-first login flow. If you are wondering what this means, Auth0 has a concept called Authentication Profiles, which allow us to configure the universal login page&rsquo;s flow when a user wants to authenticate.</p><p>In <strong>Authentication</strong> &gt; <strong>Authentication Profile</strong>, proceed to enable identifier-first login and click Save, as shown below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/identifier-first-login.png?sfvrsn=8f827dd4_2" title="Enabling identifier first login to use passkeys" alt="Enabling identifier first login to use passkeys" /></p><p>In the identifier-first authentication profile, during sign-up or login, the user is first prompted for their identifier (typically their email). Then, based on the authentication methods supported by the identifier, a decision is made whether the user needs to provide a password or proceed to use or create a passkey.</p><p>As of the time of writing, identifier-first authentication is the typical way we authenticate when signing in with Google or on Apple&rsquo;s App Store Connect, just to name a few examples. We typically provide our email first, and then the system decides whether we need to provide a password or use another method to authenticate.</p><p>Now that we have all the items on the checklist sorted, we can go ahead to enable passkey support. After clicking <strong>Save</strong>, notice that we have passkeys enabled.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/confirming-passkeys-enabled.png?sfvrsn=a17e64cb_2" title="Confirming that passkeys are enabled" alt="Confirming that passkeys are enabled" /></p><h2 id="using-passkeys-in-our-app">Using Passkeys in Our App</h2><p>We now have all the configurations needed to support passkeys. Let&rsquo;s head back and start our Angular application by running <code>npm run start</code>.</p><h2 id="the-signup-flow.">The Signup Flow</h2><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/the-signup-flow.gif?sfvrsn=80c65352_2" title="The signup flow" alt="The signup flow" /></p><p>Notice that, now during sign-up, we are first prompted to enter our email. The form gives us an opportunity to create a passkey or continue with a password.</p><p>If the user agrees to create a passkey, it triggers the WebAuthn registration ceremony, which is the sequence of steps required to create a passkey, and this is handled by Auth0 automatically. To give a sneak peek of what happens during the process, we&rsquo;ll also briefly discuss the three entities and the two FIDO protocols that make passkeys possible.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/fido-protocols.png?sfvrsn=cfaeb20_2" title="FIDO protocols: webAuthn and CTAP with supporting entities that make passkeys possible" alt="FIDO protocols: webAuthn and CTAP with supporting entities that make passkeys possible" /></p><p>The server (relying party) in the diagram above represents the server-side application that wants to enroll users for passkeys. It communicates with the webpage running on the client&rsquo;s device using the Web Authentication protocol over HTTP to send information about itself and the configuration required when creating or authenticating with passkeys. The client communicates with the authenticator following the low-level Client to Authenticator Protocol (CTAP) over USB or Bluetooth for roaming authenticators, or directly for platform authenticators.</p><p>Authenticators are hardware devices that create and persist the public key credentials representing the passkeys. Platform authenticators are authenticators built into a device (e.g., a MacBook&rsquo;s Touch ID or an iPhone&rsquo;s Face ID), while roaming authenticators are external to the user&rsquo;s device (e.g., a YubiKey). It is important to note that the interactions between these three entities varies depending on whether the user is creating passkeys (WebAuthn registration ceremony) or using passkeys to log in (WebAuthn authentication ceremony).</p><p>The following steps are involved during registration:</p><ol><li>Client clicks &ldquo;Continue with passkey.&rdquo;</li><li>Webpage requests credential creation options from Auth0 servers over HTTP.</li><li>Auth0 servers respond with credential creation options.</li><li>Client connects to authenticator by calling the <code>window.navigator.credentials.create()</code> method with the provided options.</li><li>The user is presented with a modal to verify their identity.</li><li>User verifies their identity using biometrics (Face ID, Touch ID, etc.), and the passkey is created.</li><li>The authenticator returns an attestation to the client, which then sends it to the server for verification.</li><li>If verification succeeds, a session is created for the user.</li></ol><h2 id="the-login-flow">The Login Flow</h2><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/passkey-configurations.png?sfvrsn=3ed52cb4_2" title="Passkey configurations mapping to universal login UI" alt="Passkey configurations mapping to universal login UI" /></p><p>During login, notice that we get autocomplete when entering the email. When we click the &ldquo;Continue with passkeys&rdquo; button to log in with passkeys and choose a passkey, we verify with a biometric gesture and we&rsquo;re logged in.<br />We need to note the following points that made this good UX possible, from both the configuration side on our Auth0 dashboard and the technical side of how passkeys work with the concept of resident keys.</p><p>First, the autocomplete and the &ldquo;Continue with passkey&rdquo; button are available because of the configuration we made on our dashboard earlier. This shows how simple it is to customize the authentication flow with passkeys with just a few tweaks.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/webauthn-auto-complete-attributes.png?sfvrsn=59232493_2" title="Webauthn auto-complete attribute" alt="Webauthn auto-complete attribute" /></p><p>Note that &ldquo;webauthn&rdquo; must be the entry in the autocomplete attribute, as shown above, to work correctly across different browsers.</p><p>Secondly, the autocomplete on the email input field is possible because of the <code>autocomplete="webauthn"</code> attribute on the input element, which allows the page to load any resident keys available for that domain.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/client-side-discoverable-keys.png?sfvrsn=eb67a609_2" title="Client-side discoverable keys are available in a pop-up after clicking continue with passkey" alt="Client-side discoverable keys are available in a pop-up after clicking continue with passkey" /></p><h2 id="resident-keys">Resident Keys</h2><p>During sign-up in the last section, when we created the passkey, Auth0 favored the client-side discoverable passkeys option (also known as resident keys). Resident keys are passkeys whose availability can be detected on the client side without asking the server for any information about which passkeys are available.</p><p>This addresses the problem of using server-side discoverable keys, which require the user to provide an identifier to enable the server to determine which passkeys they own. This method placed too much dependence on the server and hurt the login experience. With client-side discoverable keys, the user is able to log in just by clicking, without having to provide any information.</p><p>Tying it all together, let&rsquo;s briefly describe how the login flow works:</p><ul><li>Client visits the page. As soon as the page mounts, it requests authentication options from the server, and the authentication process is triggered. This loads the resident keys and enables autofill for passkeys.</li><li>Client clicks the passkey autocomplete option on the input field from the dropdown. <code>window.navigator.credentials.get()</code> is called with the options to connect to the authenticator.</li><li>User is prompted to verify using their biometrics. If it succeeds, an assertion response is generated and sent back to the client, which then sends it to the server for verification.</li><li>If the assertion response is verified, a session is created for the user.</li></ul><h2 id="best-practices-when-using-passkeys">Best Practices When Using Passkeys</h2><ul><li>Since passkeys created are scoped to a particular domain (also known as the relying party ID), when using passkeys in production we should try to always use a custom domain we own instead of the default domain Auth0 provides. Also note that changing domains will invalidate existing passkeys that are not scoped to the new domain.</li><li>For users who do not have a device that supports passkeys, it is important to support other authentication methods, such as email and password, as we already have in our project.</li><li>With progressive enrollment, which is enabled by default, users who are signed into our application with other methods will be intermittently prompted to enroll their devices and create passkeys.</li><li>Also, passkeys should only be enabled on one database connection on our Auth0 dashboard. This keeps all passkeys stored in a central storage location to help maintain consistency and avoid fragmentation.</li></ul><h2 id="conclusion">Conclusion</h2><p>Passkeys are being adopted by major companies like Amazon, Google, Apple, GitHub and Microsoft for security and ease of use. This guide provides an easy way to use providers like Auth0 to build robust authentication systems, using passkeys. Hopefully, this will serve as a reference point when you need to use passkeys in your future projects.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Building Component-Aware Production UIs with Angular and Kendo UI MCP</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Through <a target="_blank" href="https://www.telerik.com/blogs/building-component-aware-production-ui-angular-kendo-ui-mcp">MCP, we can use component-aware AI</a> to help us build user interfaces in conjunction with our favorite component libraries. See it in Angular!</p></div></div></aside><img src="https://feeds.telerik.com/link/23055/17403333.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:82ce8902-8991-4cbb-affc-7ca6326db284</id>
    <title type="text">What Enterprise Teams Actually Look for in an Angular UI Library</title>
    <summary type="text">See what mattered when a real enterprise team was exploring which Angular component library to use, and what keeps them using Kendo UI.</summary>
    <published>2026-07-28T12:57:08Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Petra Lazarova </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17390035/what-enterprise-teams-actually-look-for-angular-ui-library"/>
    <content type="text"><![CDATA[<p><span class="featured">See what mattered when a real enterprise team was exploring which Angular component library to use, and what keeps them using Kendo UI.</span></p><p><em>Editor&rsquo;s note: To respect our customer&rsquo;s privacy, some identifying details have been generalized while preserving technical insights and direct quotes from our conversation.</em></p><h2 id="tldr-what-enterprises-need-in-an-angular-library">TL;DR What Enterprises Need in an Angular Library</h2><p>Based on a conversation with an IT System Developer at an enterprise financial organization, five factors consistently influenced the team&rsquo;s evaluation of an Angular UI library:</p><ul><li>An enterprise-grade Angular data grid</li><li>A complete Angular component library</li><li>Documentation and API examples that keep developers productive</li><li>Reusable theming across multiple applications</li><li>Predictable upgrades and long-term stability</li></ul><p>While this conversation focused on Progress Kendo UI for Angular, these evaluation principles apply broadly to enterprise Angular applications.</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/what-enterprise-teams-need-in-an-angular-ui-library.png?sfvrsn=573b6b_2" height="1536" style="max-width:100%;height:auto;" title="What Enterprise Teams Need in an Angular UI Library" width="1024" alt="What Enterprise Teams Need in an Angular UI Library" sf-size="1495956" /></p><h2 id="intro">Intro</h2><p>When Angular UI libraries are compared, the conversation usually starts with feature lists, demos and the latest capabilities. But that&rsquo;s rarely where enterprise teams stop.</p><p>During a recent conversation with an IT System Developer at an enterprise financial organization, we noticed something interesting. The discussion kept returning to the same themes: an enterprise-grade grid, a complete Angular component library, documentation developers can rely on, reusable theming and predictable upgrades.</p><p>None of those topics are particularly flashy. Together, they reveal what experienced engineering teams often optimize for: not just what an Angular UI library can do today, but how it supports development over the years that follow.</p><h2 id="the-grid-is-key-but-the-whole-ecosystem-matters">The Grid Is Key, But the Whole Ecosystem Matters</h2><p>For this team, the <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid">Angular DataGrid</a> wasn&rsquo;t simply another component. It was at the center of many of their internal business applications and the first thing they evaluated.</p><blockquote><p>&ldquo;The demand is to have a very good and advanced grid, because we use grids all over in our web applications, so that&rsquo;s the primary choice.&rdquo;</p></blockquote><p>Their applications depend on complex data workflows, including master-detail views, grouping, sorting, and cell and row editing. Having those capabilities available out of the box saved the team from building and maintaining them themselves.</p><p>At first glance, it would be easy to conclude that this was simply a grid decision. But it wasn&rsquo;t.</p><blockquote><p>&ldquo;We chose Kendo UI because of the Grid, but also because we use a wide variety of components.&rdquo;</p></blockquote><p>Those components include buttons, text inputs, checkboxes, labels, dropdowns, combo boxes, forms, dialogs, charts and more. Rather than combining multiple libraries, the team chose a single Angular component library that covered the majority of their application needs.</p><p>Cost was part of the conversation, too.</p><blockquote><p>&ldquo;We had to weigh the subscription cost, of course, but I think it&rsquo;s very professional, a portfolio of components, and it fits our needs very, very well.&rdquo;</p></blockquote><p>The Grid may have started the evaluation, but the broader ecosystem is what kept coming up throughout the conversation.</p><p><strong>Learn more: <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid">Explore the Kendo UI for Angular Data Grid.</a></strong></p><h2 id="documentation-goes-beyond-support-to-productivity">Documentation Goes Beyond Support to Productivity</h2><p>Documentation turned out to be just as important as the components themselves.</p><blockquote><p>&ldquo;I almost never need to write to you [Progress]. Your support site and your specification site are doing it for me. Usually, I find my answers in the API documentation and by looking at your examples.&rdquo;</p></blockquote><p>Good documentation doesn&rsquo;t replace support. It means developers rarely need support.</p><p>For this team, comprehensive API references and practical examples meant fewer interruptions, fewer support requests and less time searching for answers. Documentation wasn&rsquo;t something separate from the product. It was part of the development experience.</p><p><strong>Explore the documentation: <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/getting-started">Browse the Kendo UI for Angular docs &amp; demos.</a></strong></p><h2 id="consistency-matters-more-as-your-application-portfolio-grows">Consistency Matters More as Your Application Portfolio Grows</h2><p>The value of theming changes when you&rsquo;re maintaining multiple applications instead of just one. Rather than creating a new theme for every project, this team maintains a single reusable theme across its Angular applications.</p><blockquote><p>&ldquo;We do the same theme for all our web applications. So when we have done one, we can just copy the files to the next. And that&rsquo;s to eliminate work.&rdquo;</p></blockquote><p>That approach keeps branding consistent while eliminating repetitive work.</p><p>The team also shared a practical example from their upgrade experience.</p><blockquote><p>&ldquo;Earlier, trying to cope with the changes in the style sheets could take some time. We are using the new theme editor for that, so it&rsquo;s OK now.&rdquo;</p></blockquote><p>That&rsquo;s a subtle but important distinction. Progress ThemeBuilder isn&rsquo;t only about creating themes. It&rsquo;s about making those themes easier to maintain over time.</p><p><strong>Explore ThemeBuilder: <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/styling/theme-builder">Learn how to create and maintain reusable themes across Angular applications.</a></strong></p><h2 id="for-enterprise-teams-predictability-is-a-must-have">For Enterprise Teams, Predictability Is a Must-Have</h2><p>Business-critical applications need to keep working through framework upgrades, dependency updates and ongoing maintenance. When we asked about upgrades, the discussion wasn&rsquo;t about the newest features. It was about confidence.</p><blockquote><p>&ldquo;In relation to major release updates, it&rsquo;s very well tested.&rdquo;</p></blockquote><blockquote><p>&ldquo;There can be some minor changes we have to do, but most backwards compatibility is good.&rdquo;</p></blockquote><p>No software upgrade is completely frictionless. For this team, predictable releases and good backward compatibility mattered because they made upgrades easier to manage. Sometimes the best release isn&rsquo;t the one with the longest list of new features. It&rsquo;s the one that lets developers keep moving.</p><h2 id="enterprise-teams-evaluate-platforms-not-features">Enterprise Teams Evaluate Platforms, Not Features</h2><p>Feature lists are easy to compare, but long-term developer experience isn&rsquo;t. One thing became clear as the conversation unfolded: it never stayed on a single capability for very long.</p><ul><li>The Angular Data Grid solved one set of challenges.</li><li>Documentation kept developers productive.</li><li>Reusable theming reduced repetitive work across applications.</li><li>Stable releases made upgrades easier to manage.</li></ul><p>None of those capabilities tells the whole story on its own.</p><p>Together, they suggest a different way of evaluating an Angular UI library: it&rsquo;s easy to compare individual components, but it&rsquo;s much harder, and often much more valuable, to evaluate how an ecosystem supports a team over the lifetime of an application.</p><p>That isn&rsquo;t a phrase the developer used during our conversation. It&rsquo;s the conclusion we came away with after listening to what mattered most.</p><p>If there&rsquo;s one lesson other engineering teams can take from this conversation, it&rsquo;s this:</p><p>Don&rsquo;t just compare features. Compare how much engineering work a platform helps your team avoid tomorrow, next year and several years from now.</p><p><a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Try Kendo UI for Angular</a></p><img src="https://feeds.telerik.com/link/23055/17390035.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:55d87d50-bd62-4a7e-8963-0392a696d082</id>
    <title type="text">The 10 Best Angular UI Grids</title>
    <summary type="text">TL;DR What’s the best Angular grid? For most enterprise Angular applications, Progress Kendo UI for Angular Grid is the strongest all-around choice. It combines high-performance data handling, virtualization, AI-assisted features, theming tools and integration with a broader UI component library.</summary>
    <published>2026-07-20T17:36:42Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17383440/10-best-angular-ui-grids"/>
    <content type="text"><![CDATA[<p><span class="featured"><strong>TL;DR What&rsquo;s the best Angular grid?</strong> For most enterprise Angular applications, Progress Kendo UI for Angular Grid is the strongest all-around choice. It combines high-performance data handling, virtualization, AI-assisted features, theming tools and integration with a broader UI component library.</span></p><p>On the internet, data is everything. Since the beginning of the web, showing lists has been the standard way to present information.</p><p>We started with simple HTML tables, but over the years, displaying data has become much more than just creating a list. Today, it involves complex interactions and the need for data to travel smoothly between many different components.</p><p>In any application, when a list is slow or difficult to use, it is not just a small problem. It is a major blocker. A grid that cannot handle real-time updates does more than frustrate users; it can cause serious business problems and angry users.</p><p>In 2026, choosing a grid component is a business decision, not just a technical one. Modern <a target="_blank" href="https://www.telerik.com/blogs/angular-grid-scale-how-kendo-ui-handles-millions-rows">grids must handle thousands of rows</a>, update every second, export data for reports, integrate with AI agents and look good on all screens. You need all of this without making your developers write thousands of lines of custom code.</p><p>To understand why this matters, the best way is with a real-world scenario.</p><h2 id="the-scenario">The Scenario</h2><p>You have been hired by a startup to build a trading dashboard with real-time market data, thousands of rows updating constantly, complex filters, frozen columns, and one-click export to Excel. The CTO wants a working minimum viable product (MVP) ASAP to compete with time-to-market.</p><p>In this situation, your grid is the most important part of the product. Every second of lag or every missing feature is a threat to your deadline. If the grid fails during a demo with investors, the rest of the app does not matter.</p><blockquote><p><strong>Consider:</strong> Many teams waste half their time building basic features like virtual scrolling. However, the biggest problem is communication. A grid must talk to the rest of your app. When you mix different libraries for your grid and your buttons, making them work together is a nightmare.</p></blockquote><p>Let&rsquo;s look at the best options available today and see how they can help you and your team.</p><h2 id="kendo-ui-for-angular-grid">1. Kendo UI for Angular Grid</h2><p>No matter how small, large or complex your data is, the Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/">Angular Grid</a> is the best choice when you need a grid that meets every requirement. It is built to handle everything from simple lists to massive enterprise datasets with real-time updates.</p><p>This component is not just powerful; it is also easy to customize. Because it is part of a complete suite, the grid integrates perfectly with other Kendo UI components like charts and dropdowns. This solves the &ldquo;headache&rdquo; of making different parts of your app talk to each other.</p><p>I have watched how building grids has evolved over many years. Based on that experience, here are the reasons why Kendo UI is the right choice for professional projects:</p><h3 id="why-kendo-ui-grid-is-the-right-choice-for-professional-projects">Why Kendo UI Grid is the Right Choice for Professional Projects</h3><ul><li><p><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/smart-grid">Smart Grid and AI Assistant</a>:</strong> This is where Kendo UI really changes the game. It is not just a table; it is a &ldquo;smart&rdquo; component. With the <strong>AI Smart Box</strong>, users can use natural language to search, filter and manage data. Instead of looking for exact keywords, the <strong>Semantic Search</strong> understands what the user actually wants.</p></li><li><p><strong>AI-powered insights:</strong> The grid can highlight important rows and provide smart data summaries automatically. This helps users find the most important information without manual work.</p></li><li><p><strong>Virtual scrolling and row virtualization:</strong> The grid handles hundreds of thousands of rows easily. Scrolling is fast even when data changes constantly, which is essential for trading.</p></li><li><p><strong>Real-time data binding:</strong> It works excellently with live data streams. You can update the grid without refreshing the entire dataset, keeping the UI fast.</p></li><li><p><strong>Frozen columns and column management:</strong> You can pin important columns while users scroll horizontally through large datasets.</p></li><li><p><strong>Built-in Excel and PDF export:</strong> This is a native feature, not a third-party plugin. It keeps your styles, filters and column settings.</p></li><li><p><strong><a target="_blank" href="https://www.telerik.com/mcp-servers">MCP Server</a>:</strong> The Kendo UI MCP Server helps AI tools (like Cursor or Copilot) write better code by providing the correct patterns for the component.</p></li><li><p><strong><a target="_blank" href="https://themebuilder.telerik.com/">ThemeBuilder</a>:</strong> You can import Figma design tokens to make the grid match your brand perfectly without writing complex CSS.</p></li><li><p><strong><a target="_blank" href="https://www.telerik.com/purchase/support-plans">Expert support</a>:</strong> If you have a problem during a busy week, you can get help directly from the engineers who built the grid.</p></li></ul><p><strong>Pro tip:</strong> You can <a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui">try the Kendo UI for Angular Grid for free</a> for 30 days. Test it with your real data before you decide.</p><div data-sf-ec-immutable="" class="-sf-relative" contenteditable="false" style="width:560px;height:600px;"><div data-sf-disable-link-event=""><iframe src="https://demos.telerik.com/kendo-angular-ui/demos/grid/filter-all-columns?theme=meridian-main" width="560" height="800" title="Kendo UI for Angular Grid example"></iframe></div></div><p>&nbsp;</p><p>If you have always worked within the official Angular ecosystem, the next option on this list will be very familiar to you.</p><h2 id="angular-material-table-cdk">2. Angular Material Table (CDK)</h2><p><a target="_blank" href="https://material.angular.io/">Angular Material</a> is the official UI library from the Angular team. Most Angular developers have used its table component.</p><ul><li><p><strong>The Good:</strong> It is very stable, updated frequently and follows Material Design standards. The Component Dev Kit (CDK) gives you a lot of flexibility.</p></li><li><p><strong>The Cons:</strong> You must build almost every feature yourself. Sorting, filtering, virtual scrolling and export are not included by default. For a project with a short deadline, this is too much work.</p></li><li><p><strong>My Feedback:</strong> Angular Material is a good starting point, but it is only a foundation. In a high-stakes project, you should not waste time building features that professional libraries have already solved.</p></li></ul><p>When you need a tool that focuses entirely on high-performance tables as a standalone component, you will likely look at the following library.</p><h2 id="ag-grid">3. AG Grid</h2><p><a target="_blank" href="https://www.ag-grid.com/angular-data-grid/">AG Grid</a> is a very popular standalone grid library. It is known for high performance and many enterprise features.</p><ul><li><p><strong>The Good:</strong> If your app is mostly about tables, AG Grid is very fast. The community version includes many features for free.</p></li><li><p><strong>The Cons:</strong> A real application needs more than just a grid. You will also need charts, date pickers and modals. AG Grid does not provide these. If you mix it with other libraries, it is difficult to keep the design and styling consistent.</p></li><li><p><strong>My Feedback:</strong> AG Grid is powerful, but it is a standalone tool. Kendo UI gives you the same power within a <strong>unified system</strong>. This means your grid, charts and dropdowns all use the same design and the same support team.</p></li></ul><p>For teams that prefer a large collection of components that are easy to implement, there is another very common choice in the community.</p><h2 id="primeng">4. PrimeNG</h2><p><a target="_blank" href="https://primeng.org/">PrimeNG</a> is a very complete library. Its <code>p-table</code> component is good for most standard use cases.</p><ul><li><p><strong>The Good:</strong> It has good documentation and is easy to start using. It works well for standard CRUD applications with medium-sized datasets.</p></li><li><p><strong>The Cons:</strong> For extreme scenarios, such as thousands of real-time updates, it can have performance issues. Virtual scrolling is available but requires more manual configuration.</p></li><li><p><strong>My Feedback:</strong> PrimeNG is a good choice for standard business apps. But for a fintech dashboard where the grid is the main product, you need a tool built for high performance from the start.</p></li></ul><p>As your project moves into the enterprise space with specific corporate requirements, you might consider an alternative that is widely used in large companies.</p><h2 id="syncfusion-angular-grid">5. Syncfusion Angular Grid</h2><p><a target="_blank" href="https://www.syncfusion.com/angular-ui-components/angular-grid">Syncfusion</a> offers an enterprise grid with many features like row grouping and PDF export.</p><ul><li><p><strong>The Good:</strong> It has a long list of features and handles large data well. It integrates well if you already use other Syncfusion tools.</p></li><li><p><strong>The Cons:</strong> The API does not always feel like standard Angular code, so there is a learning curve. Changing the design to match a Figma file can be difficult and slow.</p></li><li><p><strong>My Feedback:</strong> If your designers and developers need to work together daily, the difficulty in styling Syncfusion can be a problem. Progress <strong>ThemeBuilder</strong> makes this process much faster and more accurate in Kendo UI.</p></li></ul><p>Teams coming from a background in traditional software development often find that the next grid fits their existing mental model perfectly.</p><h2 id="devextreme-angular-datagrid">6. DevExtreme Angular DataGrid</h2><p><a target="_blank" href="https://js.devexpress.com/Angular/Documentation/Guide/UI_Components/DataGrid/Getting_Started_with_DataGrid/">DevExtreme</a> by DevExpress is a feature-rich grid often used in traditional enterprise software.</p><ul><li><p><strong>The Good:</strong> It is great for complex scenarios like master-detail views and multi-level grouping. It will feel familiar if your team has a background in .NET or WinForms.</p></li><li><p><strong>The Cons:</strong> Some of the patterns feel old for modern web development. Customizing the CSS to look modern can be a difficult task.</p></li><li><p><strong>My Feedback:</strong> Modern applications need to be fast and look current. DevExtreme&rsquo;s older patterns can slow down a team. The Kendo UI library uses a <strong>modern, Angular-native architecture</strong> that is easier to maintain.</p></li></ul><p>If your main priority is fast data visualization and specialized charts, there is a competitor that focuses heavily on those areas.</p><h2 id="ignite-ui-for-angular-grid">7. Ignite UI for Angular Grid</h2><p><a target="_blank" href="https://www.infragistics.com/products/ignite-ui-angular">Ignite UI</a> focuses on data visualization and high-performance grids.</p><ul><li><p><strong>The Good:</strong> The rendering engine is very fast and handles live updates well.</p></li><li><p><strong>The Cons:</strong> The community is smaller than other options. If you find a complex bug, it is harder to find answers or documentation.</p></li><li><p><strong>My Feedback:</strong> Performance is important, but support is also critical. The Kendo UI Grid offers similar performance but adds <strong>better documentation and a larger community</strong> to help you when you have problems.</p></li></ul><p>For developers who value a very clean design and a modular architecture based on modern TypeScript principles, there is an elegant solution to consider.</p><h2 id="taiga-ui-table">8. Taiga UI Table</h2><p><a target="_blank" href="https://taiga-ui.dev/">Taiga UI</a> is a modular library with a focus on TypeScript.</p><ul><li><p><strong>The Good:</strong> It has a very clean design. Because it is modular, you only use the code you need.</p></li><li><p><strong>The Cons:</strong> You have to build many features yourself. Virtual scrolling and export are not ready to use out of the box.</p></li><li><p><strong>My Feedback:</strong> Taiga UI is a great project, but it requires too much manual work for a short deadline. The Kendo UI library gives you <strong>120+ components that are already tested and ready to use</strong>.</p></li></ul><p>Some projects still use libraries that were very popular during the early versions of Angular and remain in many existing codebases.</p><h2 id="ng2-smart-table">9. ng2-smart-table</h2><p><a target="_blank" href="https://akveo.github.io/ng2-smart-table/">ng2-smart-table</a> was a very popular choice in the early days of Angular.</p><ul><li><p><strong>The Good:</strong> It is very simple to set up for basic tasks like sorting and filtering.</p></li><li><p><strong>The Cons:</strong> The library is not updated frequently anymore. This creates a risk of bugs or compatibility issues with new versions of Angular.</p></li><li><p><strong>My Feedback:</strong> Using a library that is no longer maintained creates &ldquo;technical debt.&rdquo; The Kendo UI library is supported by Progress Software, which means it receives <strong>regular updates and a clear roadmap</strong> you can trust.</p></li></ul><p>Finally, if your application requires a grid that behaves exactly like a spreadsheet instead of a standard list, there is a specialized tool for that specific case.</p><h2 id="handsontable">10. Handsontable</h2><p><a target="_blank" href="https://handsontable.com/">Handsontable</a> provides a spreadsheet-like experience (like Excel) in the browser.</p><ul><li><p><strong>The Good:</strong> It is the best choice if your users need to edit data like a spreadsheet (bulk edits, formulas, etc.).</p></li><li><p><strong>The Cons:</strong> The grid does not fully support Angular, works like a wrapper and is very limited (it also has a separate commercial license for bringing in Excel formulas features).</p></li><li><p><strong>My Feedback:</strong> Handsontable is perfect for spreadsheets, but a trading dashboard has different needs. The Kendo UI Grid provides the necessary Excel features while offering <strong>native Angular integration and real-time performance</strong>.</p></li></ul><h2 id="summary">Summary</h2><p>Today we learned how many good Angular grids exist, but when developers or agents in 2026 need to cover all the needs for your team and project, the list of options becomes much shorter. Mst grids on this list are good at one specific thing. However, for a project where the grid is the core of the product, you need a complete solution.</p><p>I pick the <strong>Kendo UI for Angular Grid</strong> as the best choice because it combines performance, features and design flexibility, and is also ready for the AI era with tools like the <strong><a target="_blank" href="https://www.telerik.com/mcp-servers">MCP Server</a></strong>.</p><p>When you have a short deadline, you need a tool that helps you work faster from day one and keeps the focus on building your product.</p><p>As I mentioned, Kendo UI for Angular comes with a free 30-day trial. So go ahead and poke around:</p><p><a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Download Free Trial</a></p><img src="https://feeds.telerik.com/link/23055/17383440.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:4db51f4e-9e2e-4b06-9992-7768fc46c6f0</id>
    <title type="text">Async Validation in Angular Signal Forms</title>
    <summary type="text">Learn how to implement async validation in Angular Signal Forms using the validateHttp function, for example to check whether a username is already taken.</summary>
    <published>2026-07-14T11:38:56Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dhananjay Kumar </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17380028/async-validation-angular-signal-forms"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to implement async validation in Angular Signal Forms using the validateHttp function, for example to check whether a username is already taken.</span></p><p>Signal Forms create a form from a writable signal model, where the model is the source of truth, and any change in the form or the model is automatically reflected in the other.</p><p>Read more about <a href="https://www.telerik.com/blogs/getting-started-angular-signal-forms" target="_blank">Signal Forms here</a>.</p><p>In this article, we will explore async validation in Signal Forms. You perform async validation for various purposes, such as to:</p><ul><li>Check if usernames or emails are already taken</li><li>Look up data in the database to confirm values</li><li>Use external services (APIs) to verify things like addresses or tax IDs</li><li>Apply business rules that only the server can validate</li></ul><p>When creating a signup form, you need to check whether the email is already in use by calling a database via an API. Since this takes time, you should use Signal Forms async validation to handle it.</p><p>To validate against an API or HTTP endpoint, Angular provides the <code>validateHttp()</code> function. Let&rsquo;s explore how to use that.</p><h2 id="create-a-signup-form">Create a Signup Form</h2><p>To begin, create a signup form by first defining an interface to represent the signup type.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">SignupFormValue</span> <span class="token punctuation">{</span>
  readonly email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  readonly password<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Next, use it to create the signal-based Signup form as shown in the next code listing:</p><pre class=" language-ts"><code class="prism  language-ts">  signupModel<span class="token punctuation">:</span> WritableSignal<span class="token operator">&lt;</span>SignupFormValue<span class="token operator">&gt;</span> <span class="token operator">=</span> signal<span class="token operator">&lt;</span>SignupFormValue<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    password<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  signupForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>signupModel<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Then, in the template, bind it to the controls as shown below.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">(submit)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onSubmit($event)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-email<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Email<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-email<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signupForm.email<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-password<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Password<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-password<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signupForm.password<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Sign up<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Next, create the submit function as shown below.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">protected</span> <span class="token function">onSubmit</span><span class="token punctuation">(</span>event<span class="token punctuation">:</span> Event<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">submit</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>signupForm<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> credentials <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">signupModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Signup submitted:'</span><span class="token punctuation">,</span> credentials<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</code></pre><p>As of now, we have created the signal-based signup form.</p><h2 id="adding-sync-validations">Adding Sync Validations</h2><p>To make it more usable, let us add some validations to the email field. We are going to add the validations below to the email field.</p><ol><li>Required</li><li>Email</li></ol><pre class=" language-ts"><code class="prism  language-ts">  signupForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>signupModel<span class="token punctuation">,</span> <span class="token punctuation">(</span>schemaPath<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">email</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Please enter a valid email address'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Both required and email are synchronous validators, and they run on each value change of controls. The <code>required()</code> validates that the control value is neither null nor an empty string (<code>''</code>), and <code>email()</code> validates the control value against a standard email regular expression pattern.</p><p>Both validators execute synchronously on each value change of the control.</p><p>We can conditionally show error messages for sync validators in the template using the field-state signals <code>touched()</code>, <code>invalid()</code> and <code>errors()</code>.</p><pre class=" language-ts"><code class="prism  language-ts">@<span class="token keyword">if</span> <span class="token punctuation">(</span>signupForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">touched</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> signupForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">invalid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">&lt;</span>ul <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"error-list"</span><span class="token operator">&gt;</span>
      @<span class="token keyword">for</span> <span class="token punctuation">(</span>error <span class="token keyword">of</span> signupForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">errors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> track error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token operator">&lt;</span>li<span class="token operator">&gt;</span><span class="token punctuation">{</span><span class="token punctuation">{</span> error<span class="token punctuation">.</span>message <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
      <span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">&gt;</span>
  <span class="token punctuation">}</span>
</code></pre><p>The <code>touched() &amp;&amp; invalid()</code> signals are used to show errors only after the user focuses and then blurs the field, so the form does not show errors on initial load.</p><h2 id="adding-async-validations">Adding Async Validations</h2><p>There are two ways you can add async validation to a signal form.</p><ol><li>Using <code>validateHttp()</code></li><li>Using <code>validateAysnc()</code></li></ol><p>In most cases, applications should use <code>validateHttp()</code> for asynchronous validation, as it simplifies HTTP-based validation with minimal configuration and supports most common scenarios.</p><p><code>validateAsync()</code> is a lower-level API that directly exposes Angular&rsquo;s resource primitive, providing full control over the validation process but requiring more implementation effort and a deeper understanding of the resource API.</p><p>Let&rsquo;s see how we can use <code>validateHttp()</code> to check whether an email exists or not .</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token function">validateHttp</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      request<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> value <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> emailValue <span class="token operator">=</span> <span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>emailValue<span class="token punctuation">)</span> <span class="token keyword">return</span> undefined<span class="token punctuation">;</span> <span class="token comment">// skip when empty</span>
        <span class="token keyword">return</span> <span class="token template-string"><span class="token string">`http://localhost:3000/user/check?email=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>emailValue<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span>response<span class="token punctuation">:</span> <span class="token punctuation">{</span> exists<span class="token punctuation">:</span> <span class="token keyword">boolean</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> response<span class="token punctuation">.</span>exists
          <span class="token operator">?</span> <span class="token punctuation">{</span>
              kind<span class="token punctuation">:</span> <span class="token string">'emailTaken'</span><span class="token punctuation">,</span>
              message<span class="token punctuation">:</span> <span class="token string">'This email is already registered'</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span>
          <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      onError<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        kind<span class="token punctuation">:</span> <span class="token string">'serverError'</span><span class="token punctuation">,</span>
        message<span class="token punctuation">:</span> <span class="token string">'Could not verify email. Please try again later.'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>We have an API that returns a response indicating whether the email already exists in the database, and we use the response&rsquo;s exists property to determine whether the email is already registered.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token punctuation">{</span>
  <span class="token string">"exists"</span><span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  <span class="token string">"user"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token string">"id"</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
    <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"User 1"</span><span class="token punctuation">,</span>
    <span class="token string">"email"</span><span class="token punctuation">:</span> <span class="token string">"user1@example.com"</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>We can show the pending state and error message from both sync and async validator as shown below:</p><pre class=" language-ts"><code class="prism  language-ts">@<span class="token keyword">if</span> <span class="token punctuation">(</span>signupForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pending</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">&lt;</span>span <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"pending"</span><span class="token operator">&gt;</span>Checking email availability<span class="token operator">...</span><span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span>
  <span class="token punctuation">}</span>
  @<span class="token keyword">if</span> <span class="token punctuation">(</span>signupForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">touched</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> signupForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">invalid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">&lt;</span>ul <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"error-list"</span><span class="token operator">&gt;</span>
      @<span class="token keyword">for</span> <span class="token punctuation">(</span>error <span class="token keyword">of</span> signupForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">errors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> track error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token operator">&lt;</span>li<span class="token operator">&gt;</span><span class="token punctuation">{</span><span class="token punctuation">{</span> error<span class="token punctuation">.</span>message <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
      <span class="token punctuation">}</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">&gt;</span>
  <span class="token punctuation">}</span>
</code></pre><p>The validation flow works like this:</p><ul><li>User enters a value.</li><li>Synchronous validators run first.</li><li>If sync validation fails, async validation does not run.</li><li>If sync validation passes, async validation starts, and <code>pending()</code> becomes true.</li><li>Once the request completes, <code>pending()</code> becomes false.</li><li>Errors are updated based on the response.</li></ul><p>This can be put in a flow chart as shown below:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/angular-signal-forms-validation-flow.png?sfvrsn=1b23b96b_2" alt="Angular Signal Form validation flow: User enters a value. Angular runs sync validators. If sync validation fails, show error message; if succeeds, Angular starts async validation and sets pending to true. Once request completes, pending becomes false. Errors are updated based on the response." /></p><p>Putting everything together, a signal-based signup form with both synchronous and asynchronous validators will look like this:</p><pre class=" language-ts"><code class="prism  language-ts">signupModel<span class="token punctuation">:</span> WritableSignal<span class="token operator">&lt;</span>SignupFormValue<span class="token operator">&gt;</span> <span class="token operator">=</span> signal<span class="token operator">&lt;</span>SignupFormValue<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    password<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  signupForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>signupModel<span class="token punctuation">,</span> <span class="token punctuation">(</span>schemaPath<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">email</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Please enter a valid email address'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">validateHttp</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      request<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> value <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> emailValue <span class="token operator">=</span> <span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>emailValue<span class="token punctuation">)</span> <span class="token keyword">return</span> undefined<span class="token punctuation">;</span> <span class="token comment">// skip when empty</span>
        <span class="token keyword">return</span> <span class="token template-string"><span class="token string">`http://localhost:3000/user/check?email=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>emailValue<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span>response<span class="token punctuation">:</span> <span class="token punctuation">{</span> exists<span class="token punctuation">:</span> <span class="token keyword">boolean</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> response<span class="token punctuation">.</span>exists
          <span class="token operator">?</span> <span class="token punctuation">{</span>
              kind<span class="token punctuation">:</span> <span class="token string">'emailTaken'</span><span class="token punctuation">,</span>
              message<span class="token punctuation">:</span> <span class="token string">'This email is already registered'</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span>
          <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      onError<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        kind<span class="token punctuation">:</span> <span class="token string">'serverError'</span><span class="token punctuation">,</span>
        message<span class="token punctuation">:</span> <span class="token string">'Could not verify email. Please try again later.'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">protected</span> <span class="token function">onSubmit</span><span class="token punctuation">(</span>event<span class="token punctuation">:</span> Event<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">submit</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>signupForm<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> credentials <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">signupModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Signup submitted:'</span><span class="token punctuation">,</span> credentials<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</code></pre><p>And on the template, the pending state and error messages can be shown as below:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">(submit)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onSubmit($event)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-email<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Email<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-email<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signupForm.email<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  @if (signupForm.email().pending()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pending<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Checking email availability...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
  }
  @if (signupForm.email().touched() &amp;&amp; signupForm.email().invalid()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>error-list<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      @for (error of signupForm.email().errors(); track error) {
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>{{ error.message }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
      }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>
  }
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-password<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Password<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signup-password<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>signupForm.password<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Sign up<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><h2 id="validatehttp-function">validateHttp Function</h2><p>The <code>validateHttp</code> function contains three components:</p><ol><li>Request function</li><li>Success handler function</li><li>Error handler function</li></ol><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/angular-signal-form-validatehttp.png?sfvrsn=68791e3b_2" alt="Angular Signal Form validateHtep function components: request function, success handler function, error handler function" /></p><p>In the <code>validateHttp</code>, we have set the request function. It returns one of these three values:</p><ol><li>Undefined to skip the validation.</li><li>A URL for performing the validation using the GET operation.</li><li>An <code>HttpResourceRequest</code> if you need to set additional request options, such as the verb header, etc. This is the same object you use with <code>httpResource</code> to configure the request object.</li></ol><blockquote><p>Read more about the <a href="https://www.telerik.com/blogs/getting-started-httpresource-api-angular" target="_blank">httpResource here</a>.</p></blockquote><p>The <code>onSuccess</code> function receives the HTTP response and returns validation errors or undefined for valid values. It can also return multiple errors when needed.</p><p>The <code>onError</code> function handles request failures like network errors or HTTP errors.</p><p>If you need to validate an email using an endpoint with a POST request, custom headers and the email in the request body, you can do so by returning an <code>httpResourceRequest</code> from <code>validateHttp</code>, as shown below.</p><pre class=" language-ts"><code class="prism  language-ts">signupForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>signupModel<span class="token punctuation">,</span> <span class="token punctuation">(</span>schemaPath<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">email</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'Please enter a valid email address'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">validateHttp</span><span class="token punctuation">(</span>schemaPath<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      request<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> value <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> emailValue <span class="token operator">=</span> <span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>emailValue<span class="token punctuation">)</span> <span class="token keyword">return</span> undefined<span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span>
          url<span class="token punctuation">:</span> <span class="token string">'http://localhost:3000/user/check'</span><span class="token punctuation">,</span>
          method<span class="token punctuation">:</span> <span class="token string">'POST'</span><span class="token punctuation">,</span>
          body<span class="token punctuation">:</span> <span class="token punctuation">{</span> email<span class="token punctuation">:</span> emailValue <span class="token punctuation">}</span><span class="token punctuation">,</span>
          headers<span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token string">'x-api-key'</span><span class="token punctuation">:</span> <span class="token string">'my-secret-key'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span>response<span class="token punctuation">:</span> <span class="token punctuation">{</span> exists<span class="token punctuation">:</span> <span class="token keyword">boolean</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> response<span class="token punctuation">.</span>exists
          <span class="token operator">?</span> <span class="token punctuation">{</span> kind<span class="token punctuation">:</span> <span class="token string">'emailTaken'</span><span class="token punctuation">,</span> message<span class="token punctuation">:</span> <span class="token string">'This email is already registered'</span> <span class="token punctuation">}</span>
          <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      onError<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        kind<span class="token punctuation">:</span> <span class="token string">'serverError'</span><span class="token punctuation">,</span>
        message<span class="token punctuation">:</span> <span class="token string">'Could not verify email. Please try again later.'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The other function for async validation is <code>validateAsync()</code> function. It is a lower-level API that exposes Angular&rsquo;s resource primitive directly. It offers complete control but requires more code and familiarity with Angular&rsquo;s resource API. Most applications should use <code>validateHttp()</code>.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-07/angular-signal-form-validateasync.png?sfvrsn=91cb8667_2" alt="For Angular Signal Forms. Based on Resource API, validateAsync onSuccess, onError, params, factory." /></p><p>You should use <code>validateAsync()</code> for these purposes:</p><ul><li>Non-HTTP validation &ndash; WebSocket connections, IndexedDB lookups or Web Worker computations</li><li>Custom caching strategies &ndash; Application-specific caching beyond simple memorization</li><li>Complex retry logic &ndash; Custom backoff strategies or conditional retries</li><li>Direct resource access &ndash; When you need the full resource lifecycle</li></ul><p>In a future article, we&rsquo;ll take a deeper look at <code>validateAsync</code>. For now, I hope this gives you a clear understanding of how to implement async validation in Angular Signal Forms using the <code>validateHttp</code> function. Thanks for reading!</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Angular Signals: The Essentials You Need to Know</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/angular-signals-essentials-you-need-know">Angular Signals</a> mark a significant shift for the framework, not an optional feature. Learn more so you can keep up!</p></div></div></aside><img src="https://feeds.telerik.com/link/23055/17380028.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:23c4afbf-be58-4e27-8443-2bfe893aab86</id>
    <title type="text">Getting Started with Angular Signal Forms</title>
    <summary type="text">Learn the basics of Angular Signal Forms, including models, validation and two-way synchronization, to build reactive forms with confidence.</summary>
    <published>2026-07-07T12:13:00Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dhananjay Kumar </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17374360/getting-started-angular-signal-forms"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn the basics of Angular Signal Forms, including models, validation and two-way synchronization, to build reactive forms with confidence.</span></p><p>Angular version 21.0 introduced one of the most exciting and awaited features: Signal Forms. Traditionally, there have been two types of forms:</p><ol><li>Template Forms</li><li>Reactive Forms</li></ol><p>Angular 21.0 introduced a new form type:</p><ol start="3"><li>Signal Forms</li></ol><p>And now,&nbsp;<a href="https://www.telerik.com/blogs/angular-22-evolution-modern-angular" target="_blank">Angular 22</a> makes Signal Forms stable.&nbsp;Signal Forms are a combination of Signal-based reactivity and forms. In my opinion, Signal Forms are the future of forms and user input in Angular.</p><p>This article gives you a basic introduction to Signal Forms and shows you how to work with models, validation and two-way synchronization.</p><h2 id="first-signal-form">First Signal Form</h2><p>Signal-based forms use a new form function. It creates a form from a writable signal model, where the model is the source of truth, and any change in the form or the model is automatically reflected in the other.</p><p>Let&rsquo;s start by creating an interface to represent the user:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IUser</span> <span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
    password<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Next, we create a model that will be used to create the form, as shown below. As mentioned earlier, the model must be a writable signal.</p><pre class=" language-ts"><code class="prism  language-ts">user <span class="token punctuation">:</span> WritableSignal<span class="token operator">&lt;</span>IUser<span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    password<span class="token punctuation">:</span> <span class="token string">''</span>  
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>We can create a signal-based form using the <code>form()</code> function from the <code>@angular/forms/signals</code>.</p><pre class=" language-ts"><code class="prism  language-ts">loginForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>That&rsquo;s it. You have created a signal-based form in Angular.</p><p>The form function returns a <code>FieldTree</code>, where each field is represented as a signal. In other words, the form function produces a data structure in which every field is a signal enriched with signal-based properties such as value, dirty, invalid and others.</p><p>To better understand this structure, let&rsquo;s print the following values:</p><pre class=" language-ts"><code class="prism  language-ts">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loginForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">valid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">invalid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">dirty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">touched</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">errors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>As shown in the example above, the email field is a signal, and all of its properties are signals as well. Because both the field and its properties are signals, you can use them directly in the template, as demonstrated below.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm.email<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm.password<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>To use <code>formField</code>, import it into the component class, as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> form<span class="token punctuation">,</span> FormField <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/forms/signals'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-login'</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>FormField<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">'./login.html'</span><span class="token punctuation">,</span>
  styleUrl<span class="token punctuation">:</span> <span class="token string">'./login.css'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Login</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>As discussed earlier, the <code>form()</code> function creates a form from the given model, where the model is the single source of truth, so changes in the form update the model and changes in the model update the form.</p><p>In simple words, the model and the form are connected through two-way data binding. This means that when the model signal changes, the form value updates accordingly, and when the form value changes, the model updates as well.</p><p>To demonstrate this, read the model signal value inside an effect, as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
  <span class="token function">effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre><p>As soon as you start typing in the form, you can read the updated value in the effect. Similarly, when you change the model signal value, the form value is reflected as well.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">this</span><span class="token punctuation">.</span>user<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  email<span class="token punctuation">:</span> <span class="token string">'a@a.com'</span><span class="token punctuation">,</span>
  password<span class="token punctuation">:</span><span class="token string">'abc'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loginForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// updated value of the form</span>
</code></pre><p>In summary, in signal-based forms, the model used by the form and the form itself are connected via two-way data binding. Updating one automatically updates the other.</p><h3 id="adding-validations">Adding Validations</h3><p>The <code>form()</code> function in Signal Forms makes it simple to add validators to form fields. You can pass a schema function to the <code>form()</code> function to add validators to the form.</p><p>In Signal Forms, validations are defined using a schema function. You pass this schema function as the second parameter to the <code>form()</code> function.</p><p>You can add the required validators to the email and password fields as shown below:</p><pre class=" language-ts"><code class="prism  language-ts">loginForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>userModel<span class="token punctuation">,</span><span class="token punctuation">(</span>schema<span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>
  <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span><span class="token punctuation">{</span>message<span class="token punctuation">:</span><span class="token string">'email is required'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">email</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span><span class="token punctuation">{</span>message<span class="token punctuation">:</span><span class="token string">'email is not valid'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>password<span class="token punctuation">,</span><span class="token punctuation">{</span>message<span class="token punctuation">:</span><span class="token string">'password is required'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>We have added the required and email built-in validators and used the message option to provide custom error messages.</p><p>As discussed earlier, in a Signal Form, all properties of a field are signals. This allows you to use them directly. For example, to display validation error messages for the email field, as shown below.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
  Email
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm.email<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
@if (loginForm.email().invalid() &amp;&amp; loginForm.email().touched()) {
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    @for (error of loginForm.email().errors(); track error.kind) {
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>{{ error.message }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    }
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>In the same way, error messages for the password field can be displayed as below:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
        Password
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm.password<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    @if (loginForm.password().invalid() &amp;&amp; loginForm.password().touched()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        @for (error of loginForm.password().errors(); track error.kind) {
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>{{ error.message }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    }
</code></pre><h3 id="submitting-the-form">Submitting the Form</h3><p>To complete the form, you can add submit functionality by importing the <code>submit()</code> function and using that to submit the form.</p><pre class=" language-ts"><code class="prism  language-ts">  <span class="token function">onSubmit</span><span class="token punctuation">(</span>event<span class="token punctuation">:</span> Event<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">submit</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> u <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">userModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Logging in with:'</span><span class="token punctuation">,</span> u<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</code></pre><p>The <code>submit()</code> function runs the logic only when the form is valid.</p><p>In the template, the form submit event can bind to the <code>onSubmit ()</code> function using event binding, as shown below. You can also disable the submit button when the form is invalid.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">(submit)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onSubmit($event)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    // other code for fields &hellip;..
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">[disabled]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm().invalid()<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Login<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="complete-signal-based-login-form">Complete Signal-based Login Form</h3><p>Putting everything together, the login form using Signal Forms can be implemented as shown below.</p><p>The IUser interface is created as shown below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IUser</span> <span class="token punctuation">{</span>
    email<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span> 
    password <span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> 
<span class="token punctuation">}</span>
</code></pre><p>The Login component using a Signal Form is created as shown below.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> signal<span class="token punctuation">,</span> WritableSignal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> IUser <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../user'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> email<span class="token punctuation">,</span> form<span class="token punctuation">,</span> FormField<span class="token punctuation">,</span> required<span class="token punctuation">,</span> submit <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/forms/signals'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-login'</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>FormField<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">'./login.html'</span><span class="token punctuation">,</span>
  styleUrl<span class="token punctuation">:</span> <span class="token string">'./login.css'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Login</span> <span class="token punctuation">{</span>

  userModel<span class="token punctuation">:</span> WritableSignal<span class="token operator">&lt;</span>IUser<span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    password<span class="token punctuation">:</span> <span class="token string">''</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  loginForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>userModel<span class="token punctuation">,</span> <span class="token punctuation">(</span>schema<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">email</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is not valid'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>password<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'password is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">onSubmit</span><span class="token punctuation">(</span>event<span class="token punctuation">:</span> Event<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">submit</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> u <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">userModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Logging in with:'</span><span class="token punctuation">,</span> u<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the template, the form can be created as shown below:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">(submit)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onSubmit($event)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
        Email
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm.email<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    @if (loginForm.email().invalid() &amp;&amp; loginForm.email().touched()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        @for (error of loginForm.email().errors(); track error.kind) {
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>{{ error.message }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    }

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
        Password
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm.password<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    @if (loginForm.password().invalid() &amp;&amp; loginForm.password().touched()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        @for (error of loginForm.password().errors(); track error.kind) {
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>{{ error.message }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">[disabled]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm().invalid()<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Login<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="understanding-forms-models">Understanding Forms Models</h3><p>Signal Forms are backed by a model, which is a writable signal. When the model value changes, the form updates automatically. Likewise, when the user interacts with the form, the model is updated. In this way, the model and the form are connected through two-way binding, which means changes in one are reflected in the other.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/model-signal-form.png?sfvrsn=a528e8f8_2" alt="Model and Signal Form exchange change updates" /></p><p>It is always best practice to use an interface to define the model. For example, if we are creating a form to edit a product, we can define a type for it as shown below.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IProduct</span><span class="token punctuation">{</span>
    id<span class="token punctuation">:</span><span class="token keyword">number</span><span class="token punctuation">;</span>
    name<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>
    price<span class="token punctuation">:</span><span class="token keyword">number</span><span class="token punctuation">;</span>
    description<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>
    category<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span> 
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IProductForm</span> <span class="token keyword">extends</span> <span class="token class-name">Omit</span><span class="token operator">&lt;</span>IProduct<span class="token punctuation">,</span> <span class="token string">'id'</span> <span class="token operator">|</span> <span class="token string">'category'</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>Now to edit the product, fetch it from the API using the resource API as shown below:</p><pre class=" language-ts"><code class="prism  language-ts">    idToFetch <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    productsResource <span class="token operator">=</span> <span class="token function">resource</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        params<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>idToFetch<span class="token punctuation">,</span>
        loader<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> params <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token keyword">let</span> id <span class="token operator">=</span> params<span class="token punctuation">;</span>
            <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`http://localhost:3000/product/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>response<span class="token punctuation">.</span>ok<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`HTTP error! status: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>response<span class="token punctuation">.</span>status<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> Promise<span class="token operator">&lt;</span>IProductForm<span class="token operator">&gt;</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>We have received the product data to be updated from the API. In the next step, we will create a signal-based form to bind this data and allow the user to update the product.</p><pre class=" language-ts"><code class="prism  language-ts">   productUpdateModel <span class="token operator">=</span> signal<span class="token operator">&lt;</span>IProductForm<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        name<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
        price<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
        description<span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    productForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>productUpdateModel<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>That&rsquo;s it. Now, in the template, use the <code>formField</code> directive to display the fetched product data in the form, as shown below.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">(submit)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onSubmit($event)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
        Name
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>productForm.name<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
        Price
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>number<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>productForm.price<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
        Description
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>productForm.description<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">[disabled]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>productForm().invalid()<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Update<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>To update the product, let&rsquo;s add an input field and a button to load the product.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Update Product Id : &lt;input #productIdInput type="number" [value] ='idToFetch()'/&gt;<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>fetchProduct(+productIdInput.value)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Load Product<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>To fetch the updated product, set the signal accordingly. There are multiple ways to bind the value returned from the API to the <code>productUpdateModel</code>. You can either use <code>productResource.value()</code> directly in the template or assign it as shown below.</p><pre class=" language-ts"><code class="prism  language-ts">   <span class="token function">fetchProduct</span><span class="token punctuation">(</span>id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>idToFetch<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>productsResource<span class="token punctuation">.</span><span class="token function">hasValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span>productUpdateModel<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>productsResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
</code></pre><p>As you see from the above example, the form model and form fields are in two-way data-binding synchronisation, and changes flow bidirectionally between them.</p><p>The interaction between user input and the model follows these steps:</p><ol><li>The user enters text or selects a value from the dropdown.</li><li>The <code>formField</code> directive detects the change.</li><li>The corresponding <code>FieldState</code> is updated.</li><li>The model signal is updated accordingly.</li></ol><p>This flow is illustrated in the diagram below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/user-input-model-interaction.png?sfvrsn=1c3ac370_2" alt="user inputs. formField directive detects the change. FieldState updated. Model signal updates" /></p><p>The flow between the model and the user input follows the steps below:</p><ol><li>Use code to <code>set()</code> or <code>update()</code> signal model</li><li>Notifies all subscribers</li><li>Field state updates</li><li><code>formField</code> directive updates the input element</li></ol><p>This flow is illustrated in the diagram below.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/set-update-signal-model.png?sfvrsn=19cf05a3_2" alt="set() or update() signal model. Notifies all subscribers. Field state updates. formField directive updates input element." /></p><p>Two-way data synchronization can be better understood by the following simple code example.</p><pre class=" language-ts"><code class="prism  language-ts">count <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">{</span> v<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  countInput <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">updateCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>count<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>c <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> v<span class="token punctuation">:</span> c<span class="token punctuation">.</span>v <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</code></pre><p>And on the template:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>number<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>countInput.v<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>updateCount()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>update count<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Count: {{ count().v }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>When you run the above code, you will see that the input and model value are in sync with each other.</p><h2 id="optional-fields">Optional Fields</h2><p>Always use explicit TypeScript type to define the model. So to create a login form create a type like below.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IUser</span> <span class="token punctuation">{</span>
    email<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span> 
    password <span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> 
    rememeberme<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>You use them to create a signal form as shown below:</p><pre class=" language-ts"><code class="prism  language-ts">  userModel<span class="token punctuation">:</span> WritableSignal<span class="token operator">&lt;</span>IUser<span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    password<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    rememberMe<span class="token punctuation">:</span> <span class="token keyword">null</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  loginForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>userModel<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>When you define a form using explicit types as shown above, the resulting field tree is fully type-safe. For example, <code>loginForm.email</code> is inferred as <code>FieldTree&lt;string&gt;</code>, and any attempt to access a property that doesn&rsquo;t exist will produce a compile-time error. In the code below, TypeScript will infer type of email as string.</p><pre class=" language-ts"><code class="prism  language-ts">email <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>loginForm<span class="token punctuation">.</span>email<span class="token punctuation">;</span>
</code></pre><p>And to set the optional field:</p><ul><li>Set the value explicitly to null</li><li>For input fields such <code>&lt;input type=&rdquo;text&rdquo; /&gt;</code>, set the value to empty string.</li><li>Do not use undefined</li></ul><p>Fields explicitly set to undefined are omitted from the field tree. A model defined as <code>{ value: undefined }</code> behaves the same as <code>{}</code>. Accessing that field returns undefined instead of a <code>FieldTree</code> instance.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/optional-fields.png?sfvrsn=916d9551_2" alt="Optional fields. Use null for optional fields. Do not use undefined, as it won’t be part of FieldTree." /></p><h2 id="nested-objects-in-model">Nested Objects in Model</h2><p>You can use nested objects to create a form like below:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IProduct</span> <span class="token punctuation">{</span>
    id<span class="token punctuation">:</span><span class="token keyword">number</span><span class="token punctuation">;</span>
    name<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>
    price<span class="token punctuation">:</span><span class="token keyword">number</span><span class="token punctuation">;</span>
    description<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>
    category<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>
    vendor <span class="token punctuation">:</span> IVendor<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IVendor</span><span class="token punctuation">{</span>
    id<span class="token punctuation">:</span><span class="token keyword">number</span><span class="token punctuation">;</span>
    name<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>
    email<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>
    phone<span class="token punctuation">:</span><span class="token keyword">string</span><span class="token punctuation">;</span>   
<span class="token punctuation">}</span>
</code></pre><p>Next, you can create the form using the nested object as shown below:</p><pre class=" language-ts"><code class="prism  language-ts">   productModel <span class="token operator">=</span> signal<span class="token operator">&lt;</span>IProduct<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    id<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
    name<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    price<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
    description<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    category<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>  
    vendor<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      id<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
      name<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
      email<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
      phone<span class="token punctuation">:</span> <span class="token string">''</span>
    <span class="token punctuation">}</span> 
   <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

   productForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>productModel<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>In the template, you can access nested object properties using dot notation, as shown below.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">(submit)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onSubmit($event)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>productForm.name<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Vendor Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>productForm.vendor.name<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Submit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Before moving to the validation section, note that the form model and the domain model are two different concepts and should be treated separately.</p><p>Your API response may map directly to the domain model, but you should define a dedicated form model when working with signal-based forms.</p><p>We will explore this distinction in more detail in a separate article.</p><h2 id="validations">Validations</h2><p>You pass validation rules to the Signal Forms using a schema. You pass schema as second parameter to the form function.</p><pre class=" language-ts"><code class="prism  language-ts">  userModel<span class="token punctuation">:</span> WritableSignal<span class="token operator">&lt;</span>IUser<span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    email<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    password<span class="token punctuation">:</span> <span class="token string">''</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>


  loginForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>userModel<span class="token punctuation">,</span> <span class="token punctuation">(</span>schema<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">email</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is not valid'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>password<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'password is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>As shown above, you provide a schema definition along with validation rules to apply validations to the respective fields of the form.</p><p>The schema function gets a <code>SchemaPathTree</code> object that lets us define the validation rules. The schema function runs at the time of initialization, and validation runs automatically whenever field values change.</p><p>On the template, you can use the validation as shown below:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>
        Email
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span> <span class="token attr-name">[formField]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loginForm.email<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    @if (loginForm.email().invalid() &amp;&amp; loginForm.email().touched()) {
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        @for (error of loginForm.email().errors(); track error.kind) {
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>{{ error.message }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    }
</code></pre><p>Validation works in these steps:</p><ol><li>Define validation rules in schema</li><li>Validation rules runs when filed value changes</li><li>Validation errors are propagated as signals</li><li>UI automatically updates when validation state changes</li></ol><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/define-validation-rules-flow.png?sfvrsn=d710c039_2" alt="Define validation rules. Automatic execution of validation rules. Validation error propagates as Signals. Reactive update of UI on validation state changes" /></p><p>There are two types of validations:</p><ol><li>Synchronous validation &ndash; <code>required()</code>, <code>email()</code>, etc.</li><li>Asynchronous validation &ndash; <code>validateHttp()</code></li></ol><p>When a field value changes, Angular runs validation in the sequence:</p><ol><li>First it runs all synchronous validations.</li><li>If all synchronous validation pass, then it runs asynchronous validation.</li><li>After that, it updates the field state signal values such as <code>valid()</code>, <code>invalid()</code>, <code>errors()</code> and <code>pending()</code>.</li></ol><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/sync-validations-flow.png?sfvrsn=d7f77ce4_2" alt="Flow chart. Run synchronous validations. Passed – no or Run async validation. Update field state." /></p><p>Angular provides these built-in validation rules:</p><ol><li><code>required()</code></li><li><code>email()</code></li><li><code>min()</code></li><li><code>max()</code></li><li><code>minLength()</code></li><li><code>maxLength()</code></li><li><code>pattern()</code></li></ol><p>You can apply <code>required()</code> and <code>email()</code> validation on email field as shown below:</p><pre class=" language-ts"><code class="prism  language-ts">  loginForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>userModel<span class="token punctuation">,</span> <span class="token punctuation">(</span>schema<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">email</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is not valid'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>When you have multiple validation rules applied, each rule runs independently and can cause form to be invalid.</p><p>Angular Signal Forms make it straightforward to implement custom validations. For example, if you want to verify that the email belongs to the nomadcoder domain, you can easily add a custom validator as shown below.</p><pre class=" language-ts"><code class="prism  language-ts">  loginForm <span class="token operator">=</span> <span class="token function">form</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>userModel<span class="token punctuation">,</span> <span class="token punctuation">(</span>schema<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">required</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is required'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">email</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">'email is not valid'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
     <span class="token function">validate</span><span class="token punctuation">(</span>schema<span class="token punctuation">.</span>email<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> value <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span><span class="token string">'@nomadcoder.ai'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span>
          kind<span class="token punctuation">:</span> <span class="token string">'emailDomain'</span><span class="token punctuation">,</span>
          message<span class="token punctuation">:</span> <span class="token string">'Email must end with @nomadcoder.ai'</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>In upcoming articles, we will get into details of custom validation and asynchronous validation.</p><h2 id="form-field-and-its-signal-properties">Form Field and Its Signal Properties</h2><p>The <code>form</code> function returns a <code>FieldTree</code> that represents all properties of the model. Each property is exposed as a signal, and each property signal includes a set of attributes that are also implemented as signals.</p><p>When you access a field from the field tree, as shown below, you can retrieve its associated attributes as illustrated:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/field-tree-attributes.png?sfvrsn=d73e4df6_2" alt="After  the next line has  and a list of attributes comes up" /></p><p>The various signal attributes are as below:</p><ol><li><code>value()</code></li><li><code>dirty()</code></li><li><code>hidden()</code></li><li><code>errors</code></li><li><code>errorSummary</code></li><li><code>valid</code></li><li><code>invalid</code></li><li><code>pending</code></li><li><code>submitting</code></li><li>Etc.</li></ol><p>As all these are <code>signals()</code>, they can be directly used on the template. I hope now you understand Angular Signal Forms. In coming articles, we will go deeper into:</p><ol><li><a href="https://www.telerik.com/blogs/async-validation-angular-signal-forms" target="_blank">asynchronous validators</a></li><li>custom validators</li></ol><p>I hope you find this article useful. Thanks for reading.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Angular Signals: Building a Reactive Countdown Timer</h4></div><div class="col-8"><p class="u-fs16 u-mb0">A comprehensive tutorial for experienced Angular developers to learn <a target="_blank" href="https://www.telerik.com/blogs/angular-signals-building-reactive-countdown-timer">Angular Signals by building a practical countdown timer</a> application with computed signals and effects.</p></div></div></aside><img src="https://feeds.telerik.com/link/23055/17374360.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:3f695ccd-2fff-4d8d-af98-598fe8161769</id>
    <title type="text">Building Component-Aware Production UIs with Angular and Kendo UI MCP</title>
    <summary type="text">Through MCP, we can use component-aware AI to help us build user interfaces in conjunction with our favorite component libraries. See it in Angular!</summary>
    <published>2026-06-15T16:26:20Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17361560/building-component-aware-production-ui-angular-kendo-ui-mcp"/>
    <content type="text"><![CDATA[<p><span class="featured">Through MCP, we can use component-aware AI to help us build user interfaces in conjunction with our favorite component libraries. See it in Angular!</span></p><p>We as developers use AI tools like Cursor, Claude Code and Copilot; they have changed how we write code. We delegate entire sections of our applications to an agent, and for many tasks, this works really well.</p><p>But when it comes to building user interfaces with a specific framework, things get complicated fast. Results are unpredictable, the code feels incomplete and we end up spending more time fixing than building.</p><p>If you have felt that, you are not alone. The good news is that the problem is not the AI. The problem is how we are using it.</p><p>We will see why AI struggles with UI development and how a new approach called &ldquo;component-aware AI&rdquo; changes everything. But first, let&rsquo;s understand what is really missing.</p><h2 id="the-problem">The Problem</h2><p>In the world of AI, we read about context all the time, which is the key problem when things go wrong. When you ask your AI assistant to use a component from a specific framework, it relies on its training data. It does not know:</p><ul><li>The exact version of Angular you are using</li><li>The best practices for your library</li><li>The specific design rules of your organization</li></ul><blockquote><p>Do you want to learn more about the <a href="https://www.progress.com/resources/webinars/the-context-problem-behind-stalled-ai-roi">context problem</a>?</p></blockquote><p>So the AI starts guessing. And that guessing creates real problems:</p><ul><li><strong>High costs and effort:</strong> We waste many tokens just explaining our UI library to the AI. We end up copying and pasting manuals, providing code examples and fixing mistakes, making the whole process slow and expensive.</li><li><strong>The &ldquo;fix and repeat&rdquo; loop:</strong> We copy the code, find a small bug, ask the AI to fix it and repeat the cycle.</li><li><strong>Low-quality code:</strong> The code the AI gives us is often incomplete or outdated. We spend more time fixing it than it would have taken to write it from scratch.</li></ul><p>But we have good news: using a new approach called &ldquo;component-aware AI&rdquo; changes everything.</p><h2 id="the-shift-to-mcp">The Shift to MCP</h2><p>We need a new way for the AI to work. We need &ldquo;component-aware AI.&rdquo; This shift is made possible by the <a href="https://www.telerik.com/blogs/promise-model-context-protocol">Model Context Protocol (MCP)</a>. Instead of the AI trying to remember code from its training data, it can read the live documentation of your library in real time.</p><p>Think of it like the difference between following a recipe from memory versus using a smart kitchen robot with everything preconfigured. It is automated, precise and much freer from errors.</p><p>When you use an MCP like the Progress Kendo UI for <a href="https://www.telerik.com/kendo-angular-ui/components/ai-tools/agentic-ui-generator/getting-started">Angular MCP</a>, the AI assistant queries the official library data before it writes any code. This verifies that every property and every method is correct.</p><p>The best way to understand the difference is to see it in action. I have experienced the shift from working with blind AI to giving agents the right tools to deliver code with confidence and without technical debt. Let&rsquo;s do it!</p><blockquote><p>Want to build your own MCP server? Check this out: <a href="https://www.youtube.com/watch?v=mM8T8bSCTyk">https://www.youtube.com/watch?v=mM8T8bSCTyk</a></p></blockquote><h2 id="the-traditional-way-the-blind-ai">The Traditional Way (The &ldquo;Blind&rdquo; AI)</h2><p>Let&rsquo;s start with the scenario you probably already know.</p><p>Open your terminal and run these two commands to create the project and install the Kendo UI for <a href="https://www.telerik.com/kendo-angular-ui/components/grid">Angular Grid</a>:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token comment"># Create the project</span>
ng new accounting-app --style<span class="token operator">=</span>scss --ssr<span class="token operator">=</span>false
 
<span class="token comment"># Install Kendo UI Grid</span>
ng add @progress/kendo-angular-grid
</code></pre><p>Then, you ask your AI assistant (without specialized context) to build the interface:</p><blockquote><p><strong>Prompt:</strong> &ldquo;Create a General Ledger grid with Kendo UI for Angular. Show Date, Description, Debit, and Credit. Add footer totals.&rdquo;</p></blockquote><p>The project compiles and runs, but the result is full of immediate technical debt. Here is what the AI generates:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// ❌ BLIND AI: Ignores Kendo built-in aggregation utilities</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
 <span class="token keyword">protected</span> readonly ledgerItems<span class="token punctuation">:</span> LedgerEntry<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
 
 <span class="token comment">// Manual logic the developer must maintain forever</span>
 <span class="token keyword">protected</span> <span class="token keyword">get</span> <span class="token function">totalDebit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
   <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>ledgerItems<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> item<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> sum <span class="token operator">+</span> item<span class="token punctuation">.</span>debit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 <span class="token punctuation">}</span>
 
 <span class="token keyword">protected</span> <span class="token keyword">get</span> <span class="token function">totalCredit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
   <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>ledgerItems<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> item<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> sum <span class="token operator">+</span> item<span class="token punctuation">.</span>credit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Let&rsquo;s look at the problems here. For <code>totalDebit</code>, if you add more columns with totals, you have to manually write a <code>.reduce()</code> for each one. The AI does not know that Kendo UI already has <a href="https://www.telerik.com/kendo-angular-ui/components/grid/grouping/aggregates"><code>aggregateBy</code></a> built in.</p><p>Another common inconsistency is with colors. Instead of using design tokens, it uses hard-coded values like <code>#f5f7fb</code>, which breaks visual coherence across the app.</p><p>The worst part is the lack of accessibility. There are no ARIA attributes or semantic roles, which Kendo UI handles automatically when configured correctly.</p><pre class=" language-html"><code class="prism  language-html"><span class="token comment">&lt;!-- ❌ BLIND AI: Hardcoded color, no accessibility, no built-in features --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span> <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ledgerItems<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#f5f7fb<span class="token punctuation">"</span></span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>debit<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Debit<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In the end, you spend more time cleaning up AI code than you would have spent writing it from scratch. The real issue is that we are not giving the AI the right tools to perform at its best.</p><p>We can fix that, because the solution is simpler than you might think.</p><h2 id="the-agentic-way-using-the-power-of-kendo-ui-mcp">The Agentic Way: Using the Power of Kendo UI MCP</h2><p>Now, let&rsquo;s move to the productive workflow and combine the power of Kendo UI MCP with our agents.</p><p>Instead of the manual configuration and the &ldquo;blind&rdquo; approach, we start with a single command. Open your terminal and run:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo angular setup
</code></pre><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/npx-kendo-angular-set.png?sfvrsn=58d383f_2" alt="Git npx kendo angular setup" /></p><p>Here is where the magic happens. It automatically installs the license and registers the <strong>Kendo UI MCP Server</strong> across your IDEs and AI agents (VS Code, Cursor, Claude Code). From this point on, your agent has full context and no longer has to guess.</p><p>Now we ask the same AI agent to build the same application with the same prompt. This time our agent, thanks to MCP, uses the <code>Kendo UI Generator</code> tool to generate the code.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/angular-mcp-grid.png?sfvrsn=8457b8db_2" alt="Create a general ledger grid with Kendo UI for Angular. Show date, description, debit, and credit. Add footer totals." /></p><p>Let&rsquo;s look at the generated code and why it is production-ready.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// ✅ COMPONENT-AWARE AI: Uses Signals, Modern Angular Patterns, and Kendo's aggregateBy</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> aggregateBy<span class="token punctuation">,</span> AggregateResult <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-data-query'</span><span class="token punctuation">;</span>
 
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">GeneralLedgerComponent</span> <span class="token punctuation">{</span>
 <span class="token keyword">private</span> ledgerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>LedgerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
 
 <span class="token comment">// Modern signal-based state</span>
 <span class="token keyword">protected</span> ledgerEntries <span class="token operator">=</span> signal<span class="token operator">&lt;</span>LedgerEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>ledgerService<span class="token punctuation">.</span><span class="token function">getLedgerEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 
 <span class="token comment">// Kendo's aggregateBy handles all column totals in one call, no manual .reduce() needed</span>
 <span class="token keyword">protected</span> totals <span class="token operator">=</span> computed<span class="token operator">&lt;</span>AggregateResult<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
   <span class="token function">aggregateBy</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">ledgerEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>
     <span class="token punctuation">{</span> field<span class="token punctuation">:</span> <span class="token string">'debit'</span><span class="token punctuation">,</span> aggregate<span class="token punctuation">:</span> <span class="token string">'sum'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
     <span class="token punctuation">{</span> field<span class="token punctuation">:</span> <span class="token string">'credit'</span><span class="token punctuation">,</span> aggregate<span class="token punctuation">:</span> <span class="token string">'sum'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
   <span class="token punctuation">]</span><span class="token punctuation">)</span>
 <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>The AI now uses Signals (<code>signal</code>, <code>computed</code>) and proper dependency injection (<code>inject</code>), which are the recommended modern Angular patterns. And instead of a manual <code>.reduce()</code> per column, it uses Kendo UI <code>aggregateBy</code>. Add a new column with a total, and you just add one line to the array, nothing else.</p><p>In the SCSS file, instead of hard-coding colors, it uses official Kendo UI Design Tokens such as <code>var(--kendo-color-surface-alt)</code> and <code>var(--kendo-color-success)</code>.</p><p>And in the template, the difference is clear:</p><pre class=" language-html"><code class="prism  language-html"><span class="token comment">&lt;!-- ✅ COMPONENT-AWARE AI: Design tokens, accessibility, full features enabled --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span>
   <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ledgerEntries()<span class="token punctuation">"</span></span>
   <span class="token attr-name">[reorderable]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
   <span class="token attr-name">[navigable]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
   <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>table<span class="token punctuation">"</span></span>
   <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>General Ledger Entries<span class="token punctuation">"</span></span>
<span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>That is the key difference. When we use <a href="https://www.telerik.com/kendo-angular-ui/components/ai-tools">Kendo UI for Angular MCP</a>, our agents do not just generate code. They generate the right code, follow best practices and deliver to a high standard.</p><p>Here are the three benefits I value most:</p><ol><li>Significantly reduced hallucinations: The AI does not guess property names. It reads the actual documentation in real time.</li><li>Accessible by default: Kendo UI components are built to follow accessibility standards. When the AI uses these components correctly, your app is automatically inclusive.</li><li>Token efficiency: No more copying large lines of documentation into your chat. The AI fetches only what it needs, saving you tokens and money.</li></ol><blockquote><p>Go deeper and explore <a href="https://www.telerik.com/kendo-angular-ui/components/ai-tools#kendo-ui-for-angular-ai-tools-overview">everything that Kendo UI MCP can do</a>.</p></blockquote><p>Now that we have seen both worlds side by side, let&rsquo;s talk about what this really means for how we choose our tools going forward.</p><h2 id="conclusion">Conclusion</h2><p>Simply using AI is not enough. And simply using a component library is not enough anymore, either.</p><p>This is the paradigm shift. We are moving from a world where a library just gives you components to one where a library also provides the context layer that makes modern development possible. Kendo UI MCP is exactly that: it acts as a bridge between your agents and the library so that the AI can do its job properly rather than guessing.</p><p>By bridging that context gap, we move from &ldquo;guessing&rdquo; to &ldquo;knowing&rdquo; and agents stop hallucinating APIs and start delivering production-ready code.</p><blockquote><p>My personal take: Next time you evaluate a UI library, do not just look at the component catalog. Ask if your framework has the tools for the way we build software today. Because that is the question I ask myself now, every time.</p></blockquote><blockquote><p>BTW, if you want to work less and ship more, it is time to <a href="https://www.telerik.com/blogs/angular-kendo-ui-mcp-making-agents-work">make your agents work for you</a> so you can focus on building what really matters.</p></blockquote><h3 id="resources">Resources</h3><ul><li><a href="https://www.telerik.com/kendo-angular-ui"><strong>Kendo UI for Angular</strong></a></li><li><strong>MCP Protocol:</strong> <a href="https://modelcontextprotocol.io/">Learn more about MCP</a></li></ul><h2 id="try-now">Try Now</h2><p>Remember, Kendo UI for Angular comes with a free 30-day trial, so you can explore all these goodies yourself.</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/23055/17361560.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:288b4cc6-8c92-4dec-a932-bed600474298</id>
    <title type="text">Angular 22: The Evolution of Modern Angular</title>
    <summary type="text">Signals, OnPush, declarative async resources: Angular 22 makes standard several features that help developers build better apps.</summary>
    <published>2026-06-05T21:01:27Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17354974/angular-22-evolution-modern-angular"/>
    <content type="text"><![CDATA[<p><span class="featured">Signals, OnPush, declarative async resources: Angular 22 makes standard several features that help developers build better apps.</span></p><p>A few days ago, we checked out <a target="_blank" href="https://www.telerik.com/blogs/google-io-2026-developers-moving-ai-agentic-era">Google I/O 2026</a>, and today we need to talk about our favorite framework: <strong>Angular</strong>. Why? Because <strong>version 22</strong> is here!</p><p>Angular v22 is a major update focused on stability, performance and ergonomic APIs. It brings a more polished signal-driven platform and better data loading primitives that make modern apps feel faster and simpler to build.</p><p>Before we look at the most interesting changes, let&rsquo;s talk about the foundation. Angular v22 stabilizes the features we have been testing in the last few versions. If you want to build a high-performance app today, these are the new standards.</p><h2 id="stable-signal-forms-and-httpresource">Stable Signal Forms and httpResource</h2><p>Reactive Forms were great, but they often felt separate from the modern Signal-based reactivity. In v22, <strong>Signal Forms are officially stable</strong>.</p><p>What does this mean for us?</p><ul><li><strong>Better reactivity:</strong> No more unnecessary updates when a single field changes.</li><li><strong>Better type safety:</strong> We finally have a form system that works perfectly with TypeScript.</li></ul><p>Do you remember <code>HttpClient</code> and the manual <code>toSignal</code> conversions? They worked, but they added extra code. Now we have <code>httpResource</code>.</p><p>It is the new standard for fetching data. It treats your HTTP requests as signals, automatically handling the loading, error, and data states for you.</p><blockquote><p><strong>Learn more:</strong> Dive deeper into <a target="_blank" href="https://www.telerik.com/blogs/getting-started-httpresource-api-angular">Getting Started with httpResource API</a> and learn more about <a target="_blank" href="https://www.telerik.com/blogs/angular-signal-forms-vs-reactive-forms">Angular Signal Forms vs. Reactive Forms</a>.</p></blockquote><p>But what if your request depends on another signal? In v22, we have the new <code>chain()</code> method. It allows you to link resources together. If the first resource is loading, the second one waits automatically. This is a big win for complex data!</p><p><strong>Wait, what if you are already using RxJS?</strong> Don&rsquo;t worry, Angular has a solution for that too. Let&rsquo;s see how v22 handles the transition from Observables to Resources.</p><h2 id="rxresource-vs.-httpresource">RxResource vs. httpResource</h2><p>A common question is: &ldquo;Should I stop using RxJS?&rdquo; The answer is <strong>no</strong>. Angular v22 introduces <code>rxResource</code> as the perfect partner for your existing Observable streams.</p><ul><li><strong><code>httpResource</code></strong>: Use this for standard API calls. It is optimized for the Fetch API and handles JSON automatically.</li><li><strong><code>rxResource</code></strong>: Use this when your data source is an existing Observable. It allows you to use the power of RxJS operators inside the clean, Signal-based Resource API.</li></ul><p>Now that we have our data fetching ready, how do we make sure our users and agents can navigate efficiently? Let&rsquo;s talk about the new smart navigation features in the Router.</p><h2 id="smart-navigation-and-incremental-hydration">Smart Navigation and Incremental Hydration</h2><p>The Angular Router received some updates in v22 that solve common problems.</p><ul><li><strong>URL decoupling:</strong> You can now keep a clean URL in the address bar while the app shows a different state. With the new <code>browserUrl</code> input on <code>RouterLink</code>, you can show <code>/settings</code> while the router actually uses <code>/</code><code>account/advanced-preferences</code>.</li><li><strong>Parameter inheritance:</strong> Child routes now inherit parameters from their parents by default, and you don&rsquo;t need extra configuration to get an ID from a parent route.</li></ul><p>But a great UI and smart routing mean nothing if the page is slow to load. This brings us to the most impressive performance update in v22: <strong>incremental hydration</strong>.</p><p>Server-Side Rendering (SSR) is no longer a problem. In v22, <strong>incremental hydration is the default</strong>.</p><p>Angular now loads your application in small parts as they become visible. Combined with <strong>resource caching</strong>, the client can use the data fetched on the server without making a second API call. The user sees the data instantly, and the &ldquo;flicker&rdquo; of reloading data is gone.</p><blockquote><p><strong>Learn more:</strong> Explore <a target="_blank" href="https://www.telerik.com/blogs/new-angular-hydration">The New Angular Hydration</a> for in-depth details.</p></blockquote><p>But performance is also about how the browser handles changes. This is where the biggest change in Angular&rsquo;s history reaches its peak.</p><h2 id="onpush-by-default">OnPush by Default</h2><p>For years, <code>ChangeDetectionStrategy.Eager</code> (previously known as `Default`) was the standard for Angular&rsquo;s change detection. With v22, <code>ChangeDetectionStrategy.OnPush</code>is now the default for new components!</p><p>By using <code>ChangeDetectionStrategy.OnPush</code> as the default, we get:
</p><ul><li><strong>Better performance:</strong> The framework only checks what it needs to, exactly when it needs to, avoiding unnecessary checks.</li><li><strong>A step closer to Zoneless:</strong> This sets the stage for eventually dropping <code>zone.js</code> in the future.</li></ul><p>If you have an older app, don&rsquo;t worry! There is a migration tool that adds <code>ChangeDetectionStrategy.Eager</code> (the new name for the old behavior) to your existing components exactly as before.</p><p>Now, how do we make sure our fast code actually works? Let&rsquo;s talk about the new speed king of testing.</p><h2 id="vitest">Vitest</h2><p>Testing used to be the slow part of our work. Not anymore. Angular v22 officially replaces Karma/Jasmine with <strong>Vitest</strong> as the default test runner.</p><p>It is very fast and shares the same configuration as your build pipeline. Also, the new <code>migrate-karma-to-vitest</code> tool makes the change very easy, even for complex tests!</p><blockquote><p>Do you want to try <a target="_blank" href="https://www.telerik.com/blogs/unit-testing-angular-modern-testing-vitest">Vitest</a>?</p></blockquote><h2 id="angular-aria-accessibility-for-everyone">Angular Aria: Accessibility for Everyone</h2><p>Building custom components with correct ARIA roles and focus management can be difficult. In v22, <strong>Angular Aria is officially stable</strong>.</p><p>Think of it as <strong>&ldquo;headless accessibility.&rdquo;</strong> It provides the logic you need to build your own accessible components. It handles the complicated parts of ARIA attributes and keyboard interactions for you.</p><p>Why does this matter for AI? Because accessible apps are much easier for AI Agents to understand!</p><blockquote><p>Check this example about <a target="_blank" href="https://www.telerik.com/blogs/build-accessible-components-angular-aria">how to build accessible components with Angular Aria</a>.</p></blockquote><h2 id="async-dependency-injection">Async Dependency Injection</h2><p>The <code>injectAsync()</code> function allows us to lazy-load services only when they are actually needed, while still creating the instance with Angular dependency injection. This keeps your app small and fast.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">CheckoutComponent</span> <span class="token punctuation">{</span>
  <span class="token comment">// We declare the lazy injection in the injection context (class field)</span>
  <span class="token keyword">private</span> paymentGatewayLoader <span class="token operator">=</span> <span class="token function">injectAsync</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> 
    <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token string">'./payment-gateway.service'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>m <span class="token operator">=&gt;</span> m<span class="token punctuation">.</span>PaymentGatewayService<span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">async</span> <span class="token function">onCheckoutCompleted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// We only load the heavy PaymentGatewayService when the user actually checks out!</span>
    <span class="token keyword">const</span> paymentGateway <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">paymentGatewayLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> paymentGateway<span class="token punctuation">.</span><span class="token function">processTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h2 id="building-a-dashboard-with-angular-22">Building a Dashboard with Angular 22</h2><p>Instead of just explaining features, let&rsquo;s build a practical dashboard example that uses the stable core of Angular v22: <code>@Service()</code>, <code>httpResource()</code> and the new template syntax.</p><blockquote><p><strong>Want to follow along?</strong> You can clone the complete project from GitHub: <a target="_blank" href="https://github.com/danywalls/angular-22-nba-finals">angular-22-nba-finals</a>. Just run <code>npm start</code> and you&rsquo;ll have the dashboard running locally.</p></blockquote><h3 id="the-data-service-with-service">The Data Service with @Service</h3><p>Now, let&rsquo;s create our service. We will use the new <code>@Service()</code> decorator (which replaces <code>@Injectable()</code>) and <code>httpResource</code> to fetch the games. Notice how <code>httpResource</code> handles loading and error states for you.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// src/app/nba.service.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Service <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> httpResource <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">NbaGame</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  gameNumber<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  homeTeam<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  homeScore<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  awayTeam<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  awayScore<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  status<span class="token punctuation">:</span> <span class="token string">'Final'</span> <span class="token operator">|</span> <span class="token string">'Scheduled'</span><span class="token punctuation">;</span>
  date<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  location<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Service</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">NbaService</span> <span class="token punctuation">{</span>
  <span class="token comment">// httpResource is the standard for fetching data</span>
  gamesResource <span class="token operator">=</span> httpResource<span class="token operator">&lt;</span>NbaGame<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'/api/nba-finals'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="the-ui-layer--error-states">The UI Layer &amp; Error States</h3><p>We display the data using the new <code>@for</code> block and keep the error handling inside the component template. This keeps the example focused on stable Angular v22 behavior.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// src/app/app.ts</span>
@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;app-dashboard&gt;&lt;/app-dashboard&gt;
    &lt;router-outlet&gt;&lt;/router-outlet&gt;
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Our Dashboard component remains simple:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// src/app/dashboard.component.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> NbaService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./nba.service'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-dashboard'</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;div class="dashboard"&gt;
      &lt;h1&gt;NBA Finals 2026: Knicks vs Spurs&lt;/h1&gt;

      @if (nbaService.gamesResource.isLoading()) {
         &lt;p&gt;Loading game data...&lt;/p&gt;
      } @else {
        &lt;div class="cards"&gt;
          @for (game of nbaService.gamesResource.value(); track game.id) {
            &lt;div class="card"&gt;
              &lt;div class="meta"&gt;Game {{ game.gameNumber }} &bull; {{ game.date }}&lt;/div&gt;
              &lt;div class="score"&gt;
                {{ game.homeTeam }} {{ game.homeScore }} vs {{ game.awayScore }} {{ game.awayTeam }}
              &lt;/div&gt;
              &lt;div class="status"&gt;{{ game.status }}&lt;/div&gt;
            &lt;/div&gt;
          }
        &lt;/div&gt;
      }
    &lt;/div&gt;
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DashboardComponent</span> <span class="token punctuation">{</span>
  nbaService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>NbaService<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Save changes and run <code>ng server</code> navigate in the browser and tada!!!</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-06/angular-22-signals-dashboard.png?sfvrsn=5aeda1c4_2" alt="Angular 22 example dashboard using signals and httpResource" /></p><p>Done!! We play with the stable part of Angular v22 using <code>@Service()</code> for a clean singleton service definition, <code>httpResource()</code> for declarative, signal-based HTTP loading and <code>@for</code> with <code>@if</code> in templates to render loading and data states.</p><p>And, of course, AI is not forgotten by the Angular team. They updated and added new tools for Angular MCP and Skills. Let&rsquo;s see them!</p><h2 id="angular-mcp-and-skills">Angular MCP and Skills</h2><p>Of course, Angular continues embracing AI. The Angular MCP exposes app state and builds workflows for tools that understand Angular projects. That includes server-side MCP tools such as <code>devserver.wait_for_build</code>, <code>devserver.start</code> and <code>devserver.stop</code>.</p><p>Version 22 also introduces Agent Skills for Angular, a lightweight set of skill definitions that give AI assistants direct knowledge of modern Angular idioms and best practices. These skills are useful for teams that want to integrate agent-assisted coding into their workflow without pain.</p><ul><li>Learn more about Angular MCP: <a target="_blank" href="https://angular.dev/ai/mcp">angular.dev/ai/mcp</a></li><li>Learn more about Agent Skills for Angular: <a target="_blank" href="https://github.com/angular/skills">github.com/angular/skills</a></li></ul><h2 id="recap">Recap</h2><p>Angular 22 is a declaration that the way we build for the web has evolved. And by using <strong>Signals</strong>, <strong>OnPush performance</strong> and <strong>declarative async resources</strong>, we can build applications that are faster, simpler and easier to maintain.</p><p>This release is about making stable patterns:</p><ul><li>Use <code>httpResource()</code> for fetch-heavy components.</li><li>Use <code>@Service()</code> for singleton services.</li><li>Use <code>injectAsync()</code> when you want to defer heavy service loading.</li><li>Let template syntax like <code>@for</code> and <code>@if</code> keep your view code readable.</li><li>Embrace the AI era with Agent Skills and MCP tools.</li></ul><p>My advice is don&rsquo;t just read this article&mdash;build something! Take the dashboard we sketched and turn it into an interactive experience with Progress <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/ai-chat">Kendo UI for Angular AI Chat</a>. A chat interface is a powerful way to explore the series score in real time and it&rsquo;s a great way to test Angular 22 with a modern Kendo UI scenario.</p><p>Feel free to <a target="_blank" href="https://youtu.be/h5OJUSS_8IA?is=WBc_bZuuQnx9b0EY">watch for all the new features in Angular 22</a>:</p><div data-sf-ec-immutable="" class="-sf-relative" contenteditable="false" style="width:560px;height:315px;"><div data-sf-disable-link-event=""><iframe width="560" height="315" src="https://www.youtube.com/embed/h5OJUSS_8IA?si=0Nzrxxm5BbAjRJz6" title="What’s new in Angular v22" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"></iframe></div></div><p>&nbsp;</p><p>For a complete overview and to verify all the new features, you can check the official release post: <a href="https://blog.angular.dev/announcing-angular-v22-c52bb83a4664" target="_blank">Announcing Angular v22</a>.</p><p><strong>Happy coding!</strong></p><img src="https://feeds.telerik.com/link/23055/17354974.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:00f2f87e-a41a-4c28-8ad3-079bcbc8bc0a</id>
    <title type="text">Angular Grid at Scale: How Kendo UI Handles Millions of Rows</title>
    <summary type="text">Angular Grid at Scale: How Kendo UI Handles Millions of Rows.</summary>
    <published>2026-04-21T16:17:26Z</published>
    <updated>2026-10-22T19:10:29Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23055/17323038/angular-grid-scale-how-kendo-ui-handles-millions-rows"/>
    <content type="text"><![CDATA[<p>Let&rsquo;s say the Monday after the Super Bowl, your boss wants to see a dashboard list of the more than 120 million people who watched it. (The reality of this situation does not matter. Just go with it!) Your boss says, &ldquo;The dashboard needs to show every connected viewer in real time. A grid. Sortable. Filterable. And it can&rsquo;t be slow.&rdquo;</p><p>You open your code editor, create an HTML table, and try to render one million rows. The browser freezes. The tab crashes. Your Monday just got worse.</p><p>You need to display large amounts of data in a grid without killing the browser. The trick is simple: don&rsquo;t render what the user can&rsquo;t see.</p><p>This is where <strong>virtual scrolling</strong> comes in. Instead of creating a DOM element for every single row, you only render the rows visible on screen, plus a few extra. The user scrolls smoothly through millions of records, each new batch loading in as needed, and the browser stays fast and responsive.</p><p>Let&rsquo;s do it together! Today, we will build a Super Bowl Dashboard in Angular. We will see how to handle one million viewers using Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/">Angular Grid</a>. We will start with a simple table, see why it fails and then fix it using virtual scrolling and server-side data fetching.</p><p>Let&rsquo;s make this work in a real project.</p><h2 id="setting-up-the-project">Setting Up the Project</h2><p>First, create a new Angular application by running the command:</p><pre class=" language-bash"><code class="prism  language-bash">ng new superbowl-dashboard
</code></pre><p>Navigate to the project:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">cd</span> superbowl-dashboard
</code></pre><p>Now install the Angular Grid. The <code>ng add</code> command handles dependencies and theme configuration for us:</p><pre class=" language-bash"><code class="prism  language-bash">ng add @progress/kendo-angular-grid --skip-confirmation
</code></pre><p>This installs the grid package and its peer dependencies and sets up the Kendo UI default theme.</p><p>Once the installation is done, we need to activate our Kendo UI license. This step removes the watermark and unlocks all the features.</p><blockquote><p>Don&rsquo;t have a license? Don&rsquo;t worry! You can get a <a target="_blank" href="https://www.telerik.com/try/kendo-ui">completely free trial</a> with <strong>no credit card required</strong>, so you can follow along and build the dashboard without worrying about the watermark.</p></blockquote><p>To do this, download the license key file from your Telerik account. Then, run this command inside your project folder:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><p>Because the Kendo UI Grid is so robust, it includes many features that can slightly increase the initial bundle size. We need to prevent Angular from throwing a &ldquo;bundle initial exceeded maximum budget&rdquo; error when we run or build an app. Open the <code>angular.json</code> file and increase the <code>budgets</code> config for the <code>initial</code> type to around <code>5MB</code> for warnings and <code>10MB</code> for errors.</p><p>Perfect! We have a fresh Angular project with Kendo UI Grid installed and styled. Now let&rsquo;s generate our Super Bowl viewers&rsquo; fake data.</p><h2 id="generating-one-million-viewers">Generating One Million Viewers</h2><p>Before we start with the grid, we need data. We&rsquo;ll create a single service that handles everything: generating fake viewer data for client-side demos and simulating a paginated server API for server-side scrolling.</p><p>To create the service, open the terminal and run the following Angular CLI command:</p><pre class=" language-bash"><code class="prism  language-bash">ng g s services/viewer
</code></pre><p>First, we will define a simple <code>Viewer</code> interface so our grid knows what fields to expect. Then, our service will include a <code>generateViewers</code> method to create mock data, and a <code>fetchPage</code> method that simulates a paginated server API. We won&rsquo;t use <code>fetchPage</code> right away, but it will be ready for when we implement server-side virtual scrolling later.</p><p>Don&rsquo;t worry too much about the exact implementation of the helper methods; the main idea here is not how to generate fake users, but how to create a grid capable of supporting massive amounts of data without breaking a sweat!</p><p>Open <code>src/app/services/viewer.ts</code> and add the following:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Viewer</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  username<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  watchTimeMin<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  isLive<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">PagedResult</span> <span class="token punctuation">{</span>
  data<span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  total<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> ADJECTIVES <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">"Swift"</span><span class="token punctuation">,</span>
  <span class="token string">"Lucky"</span><span class="token punctuation">,</span>
  <span class="token string">"Bold"</span><span class="token punctuation">,</span>
  <span class="token string">"Chill"</span><span class="token punctuation">,</span>
  <span class="token string">"Epic"</span><span class="token punctuation">,</span>
  <span class="token string">"Happy"</span><span class="token punctuation">,</span>
  <span class="token string">"Lazy"</span><span class="token punctuation">,</span>
  <span class="token string">"Wild"</span><span class="token punctuation">,</span>
  <span class="token string">"Cool"</span><span class="token punctuation">,</span>
  <span class="token string">"Fast"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> NOUNS <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">"Fan"</span><span class="token punctuation">,</span>
  <span class="token string">"Eagle"</span><span class="token punctuation">,</span>
  <span class="token string">"Tiger"</span><span class="token punctuation">,</span>
  <span class="token string">"Bear"</span><span class="token punctuation">,</span>
  <span class="token string">"Wolf"</span><span class="token punctuation">,</span>
  <span class="token string">"Hawk"</span><span class="token punctuation">,</span>
  <span class="token string">"Fox"</span><span class="token punctuation">,</span>
  <span class="token string">"Lion"</span><span class="token punctuation">,</span>
  <span class="token string">"Shark"</span><span class="token punctuation">,</span>
  <span class="token string">"Bull"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">"root"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ViewerService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly TOTAL_VIEWERS <span class="token operator">=</span> 1_000_000<span class="token punctuation">;</span>

  <span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> startId <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> viewers<span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> count<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      viewers<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        id<span class="token punctuation">:</span> startId <span class="token operator">+</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span>
        username<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomFrom</span><span class="token punctuation">(</span>ADJECTIVES<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomFrom</span><span class="token punctuation">(</span>NOUNS<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">9999</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span>
        watchTimeMin<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">240</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        isLive<span class="token punctuation">:</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&gt;</span> <span class="token number">0.08</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> viewers<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">/**
   * Simulates a server API call with pagination.
   * Uses a small delay to mimic real network latency.
   */</span>
  <span class="token keyword">async</span> <span class="token function">fetchPage</span><span class="token punctuation">(</span>skip<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> take<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>PagedResult<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> <span class="token number">80</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      data<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>take<span class="token punctuation">,</span> skip<span class="token punctuation">)</span><span class="token punctuation">,</span>
      total<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>TOTAL_VIEWERS<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> randomFrom<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token punctuation">(</span>arr<span class="token punctuation">:</span> T<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> T <span class="token punctuation">{</span>
    <span class="token keyword">return</span> arr<span class="token punctuation">[</span>Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">randomBetween</span><span class="token punctuation">(</span>min<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> max<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>max <span class="token operator">-</span> min <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> min<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Each viewer has a fun auto-generated username (like <code>SwiftEagle4821</code>), their watch time and a live status. The <code>fetchPage</code> method is an <code>async</code> function that simulates a paginated API with a small delay to mimic network latency. We&rsquo;ll use <code>generateViewers</code> first and come back to <code>fetchPage</code> later.</p><p>With our service ready, let&rsquo;s see what happens when we try to render all these viewers at once.</p><h2 id="the-problem-rendering-all-rows-at-once">The Problem: Rendering All Rows at Once</h2><p>Let&rsquo;s try to do it and experience the problem firsthand. Open <code>src/app/app.ts</code> and replace its content:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> CommonModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/common"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CommonModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span> 
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">loadViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s open our template file, <code>src/app/app.html</code> and add a few buttons for 1K, 10K and 100K viewers. These buttons will call our <code>loadViewers()</code> method to quickly generate different amounts of fake data.</p><p>Finally, we&rsquo;ll use the <code>&lt;kendo-grid&gt;</code> component in our template. The key property to pay attention to is <code>[data]="viewers()"</code>, which tells Kendo UI to read from our reactive signal and constantly display the list of viewers.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Connected viewers: {{ viewers().length.toLocaleString() }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>actions<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(10_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>10K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(100_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>100K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1M Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>grid-container<span class="token punctuation">"</span></span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">600</span>px<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>table</span> <span class="token attr-name">border</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">100%</span><span class="token punctuation">;</span> <span class="token property">border-collapse</span><span class="token punctuation">:</span> collapse<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>thead</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>#<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Username<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Watch (min)<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Live<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>thead</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tbody</span><span class="token punctuation">&gt;</span></span>
      @for (viewer of viewers(); track viewer.id) {
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.id }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.username }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.watchTimeMin }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.isLive ? 'Yes' : 'No' }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span>
      }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tbody</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>table</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Now we can run our app. Go to the terminal and execute the following command:</p><pre class=" language-bash"><code class="prism  language-bash">ng serve
</code></pre><p>This will start the local development server. Once it finishes compiling, open your browser and navigate to <code>http://localhost:4200</code>.</p><p>Click <strong>&ldquo;1K Viewers&rdquo;</strong> and it feels smooth. Click <strong>&ldquo;10K Viewers&rdquo;</strong> and you will notice a significant lag.</p><p>Now click <strong>&ldquo;100K Viewers&rdquo;</strong> &hellip; and watch the browser scream for help. The page will freeze, the scroll will be jumpy and you might even get the &ldquo;Page Unresponsive&rdquo; dialog.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-04/page-unresponsive-dialog.png?sfvrsn=bb5cb2ab_2" alt="Page Unresponsive dialog" /></p><p>We have seen the problem: standard HTML tables and simple loops cannot handle huge datasets. When you click the 1M Viewers button, the browser stops working.</p><p>OK, but how can we fix this?</p><h2 id="the-solution-angular-grid-withvirtual-scrolling">The Solution: Angular Grid withVirtual Scrolling</h2><p>If you read about Kendo UI, you know the Kendo UI Grid, but before we to start to use it, I want to explain &ldquo;virtual scrolling.&rdquo; Think of virtual scrolling like a camera moving over a big stadium. You only see the seats in the camera frame, maybe 50 seats. The stadium has 1,000,000 seats, but the camera doesn&rsquo;t need to show all of them at once. It only shows what is visible.</p><p>Kendo UI Grid does exactly this with one property: <code>scrollable="virtual"</code>. Let&rsquo;s use it.</p><p>First, update your <code>app.ts</code>. We need to import <code>KENDO_GRID</code> and remove the <code>CommonModule</code> because the grid will handle everything now:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@progress/kendo-angular-grid"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span> 
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">loadViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s update <code>src/app/app.html</code>. We will replace the standard <code>&lt;table&gt;</code> with the <code>&lt;kendo-grid&gt;</code> component:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Connected viewers: {{ viewers().length.toLocaleString() }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>actions<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(10_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>10K<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(100_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>100K<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1M<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span>
  <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewers()<span class="token punctuation">"</span></span>
  <span class="token attr-name">[height]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span>
  <span class="token attr-name">scrollable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>virtual<span class="token punctuation">"</span></span>
  <span class="token attr-name">[rowHeight]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>36<span class="token punctuation">"</span></span>
  <span class="token attr-name">[pageSize]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>50<span class="token punctuation">"</span></span>
<span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Username<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>180<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>watchTimeMin<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Watch (min)<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isLive<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Live<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="why-does-this-work">Why Does This Work?</h3><p>We added three important properties to the <code>&lt;kendo-grid&gt;</code> to make it fast:</p><ol><li><strong><code>scrollable="virtual"</code></strong>: This tells Kendo UI: &ldquo;Don&rsquo;t create all the rows at once. Only create the ones the user can see right now.&rdquo;</li><li><strong><code>[rowHeight]="36"</code></strong>: The grid needs to know exactly how tall each row is. This helps the grid calculate the scroll position correctly.</li><li><strong><code>[pageSize]="50"</code></strong>: This is how many rows Kendo UI keeps in the DOM. A good tip: set this to <strong>3 times</strong> the number of rows visible on your screen.</li></ol><p>Now, if you click the buttons, you will see that a page with 10,000, 100,000 or even <strong>one million viewers</strong> scrolls smoothly. The browser is fast because only ~50 rows are in the DOM at any time.</p><p>Kendo UI Grid does all the hard work for the rendering.</p><p>But there is one more problem. We fixed the <strong>rendering</strong>, but we are still loading one million records into the browser&rsquo;s memory. For a real app, loading 1,000,000 records at once is a bad idea. It uses too much RAM and it is slow to start.</p><p>What if we want to show one million users in real-time without using all the RAM? Let&rsquo;s use the final solution: <strong>Server-Side Data Fetching</strong>.</p><h2 id="server-side-data-fetching-with-endless-scrolling">Server-Side Data Fetching with Endless Scrolling</h2><p>Here&rsquo;s the reality: the Super Bowl has <strong>120 million viewers</strong>. We can&rsquo;t load all of them into the browser at once. Instead, the grid should fetch data page by page as the user scrolls , loading only what&rsquo;s needed, when it&rsquo;s needed.</p><p>Kendo UI Grid supports this out of the box with the <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/api/gridcomponent#scrollbottom"><code>scrollBottom</code></a> event. When the user scrolls to the bottom of the current data, the grid fires this event, and we simply fetch the next page and append it. Let&rsquo;s build it!</p><p>Generate a new component by running the command in the terminal:</p><pre class=" language-bash"><code class="prism  language-bash">ng g c components/live-grid
</code></pre><p>Open <code>src/app/components/live-grid/live-grid.ts</code> and replace the content with:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal<span class="token punctuation">,</span> ChangeDetectionStrategy <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@progress/kendo-angular-grid"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"../../services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-live-grid"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./live-grid.html"</span><span class="token punctuation">,</span>
  changeDetection<span class="token punctuation">:</span> ChangeDetectionStrategy<span class="token punctuation">.</span>OnPush<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LiveGrid</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>

  isConnected <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  loading <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  pageSize <span class="token operator">=</span> 1_000<span class="token punctuation">;</span>

  <span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>isConnected<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">onScrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loading</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>loading<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">fetchPage</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">viewers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>pageSize<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>current<span class="token punctuation">,</span> <span class="token operator">...</span>result<span class="token punctuation">.</span>data<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>loading<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The logic here is straightforward. First, <code>connect()</code> starts the process by fetching the initial data page using <code>loadMore()</code>. Then, whenever the user scrolls to the bottom of the grid, the <code>(scrollBottom)</code> event fires, triggering <code>onScrollBottom()</code> to fetch and append the next chunk of data to our <code>viewers</code> signal.</p><p>To make this work in the UI, we just need to set <code>scrollable="scrollable"</code> on our Kendo Grid to enable endless scrolling, and bind our <code>loading</code> signal to show a skeleton animation during the fetch.</p><p>Now let&rsquo;s build the template in <code>src/app/components/live-grid/live-grid.html</code> to tie all of this together:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Live Server Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Viewers: {{ viewers().length }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>connect()<span class="token punctuation">"</span></span> <span class="token attr-name">[disabled]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isConnected()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Connect to Live Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

@if (isConnected()) {
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span> 
    <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewers()<span class="token punctuation">"</span></span> 
    <span class="token attr-name">[height]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span> 
    <span class="token attr-name">scrollable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>scrollable<span class="token punctuation">"</span></span>
    <span class="token attr-name">[loading]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loading()<span class="token punctuation">"</span></span>
    <span class="token attr-name">(scrollBottom)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onScrollBottom()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Username<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>180<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>watchTimeMin<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Watch (min)<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isLive<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Live<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>Wire the component into the app. Update <code>app.ts</code>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> LiveGrid <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./components/live-grid/live-grid"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>LiveGrid<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>And <code>src/app/app.html</code>:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>app-live-grid</span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>Run <code>ng serve</code>, open <code>http://localhost:4200</code> and click &ldquo;Connect to Live Feed.&rdquo;</p><p>You&rsquo;ll see the grid load the first 1,000 viewers. Now scroll down. When you reach the bottom, the grid fetches the next 1,000 viewers and appends them and the loading skeleton appears briefly while data is being fetched. Keep scrolling, and watch the viewers counter grow until it gets to 1,000,000. Yeah!!</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-04/grid-scroll.gif?sfvrsn=56a027cd_2" alt="Super Bowl viewers dashboard being scrolled and scrolled, very smoothly" /></p><h2 id="recap">Recap</h2><p>We saw the problem: rendering thousands of rows at once makes the browser freeze. Then we added Kendo UI <strong>virtual scrolling</strong> with <code>scrollable="virtual"</code>. This is the secret to showing large datasets without crashing.</p><p>Finally, we used <strong>server-side data fetching</strong> with the <code>(scrollBottom)</code> event. The grid loads data page by page. This keeps the memory usage low and the experience smooth.</p><p>In your next challenge don&rsquo;t worry about the data, Kendo UI Grid makes it easy. With a few properties, your dashboard can handle millions of rows. ✌</p><p>Give it a try for free!</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" target="_blank" class="Btn">Try Kendo UI for Angular</a></p><p>Happy coding!</p><p>Source Code: <a target="_blank" href="https://github.com/danywalls/kendo-grid-large-app">https://github.com/danywalls/kendo-grid-large-app</a></p><img src="https://feeds.telerik.com/link/23055/17323038.gif" height="1" width="1"/>]]></content>
  </entry>
</feed>
