...
@Component({
  ...
  template: `
    ...
    <main>
      ...            👇
      @if (todo$ | async; as todo) {
        <p>Title: {{todo.title}}</p>
      }
    </main>
   ...
  `,
  standalone: true,
  ...
})
export class ShareReplayComponent {
  todo$ = inject(HttpClient)
    .get<Todo>('https://jsonplaceholder.typicode.com/todos/1');
}
Angular's recent releases have kept the framework in a steady rhythm of improvement, with the team consistently demonstrating a commitment to its developer community. Angular v17, along with its subsequent minor versions, introduced a range of substantial features. Among these, the new built-in block template syntax—though still in developer preview—stood out as a major simplification for working with templates. The latest releases also resolved two long-standing issues in the Angular repository. The major release, v18, brought the Unified Control State Change Events to the framework. Subsequently, the minor release, v18.1, leveraged the block template syntax to introduce a new built-in template feature known as Template Local Variables, which are declared using the @let block.

Refer to the official blog post for a detailed explanation of how @let variables are defined, their constraints, and their update mechanism.

Essentially, Template Local Variables enable developers to declare variables directly within the template, mirroring the way variables are declared in the component class. This capability streamlines template logic, offering an alternative to established patterns and unlocking new use cases, as detailed in this article by @eneajaho. This article's inspiration comes from a Reddit thread that questioned the necessity and benefits of @let declarations.

Matthieu Riegler, a key Angular contributor, shares his perspective on this topic in a short video here.

In the following sections, I'll demonstrate a practical application of these local template variables that proved valuable in a recent project. This approach allowed me to eliminate the need for client-side "caching" with the RxJS shareReplay operator when using the same data in multiple sections of a template. Let's begin 🚀.

RxJS "Caching" via the shareReplay Operator

A common task in web development is making HTTP requests. Angular handles this through its observable-based HttpClient API. Since the fetched data is usually displayed in the template, developers often adopt a declarative pattern using the Async pipe. This pipe is considered best practice because it automatically manages the subscription, subscribing to the observable when the component loads and unsubscribing upon its destruction 👇:
...
@Component({
  template: `
    ...
    <main>
      ...           👇
      @if (todo$ | async; as todo) {
        <p>Title: {{todo.title}}</p>
      }
    </main>

    <aside>
      ...            👇
      @if (todo$ | async; as todo) {
        <p>Is Completed: {{todo.completed}}</p>
      }
    </aside>
   ...    
  `,
  standalone: true,
})
export class ShareReplayComponent {
  todo$ = inject(HttpClient)
    .get<Todo>('https://jsonplaceholder.typicode.com/todos/1');
}
However, situations arise where this same data stream is needed elsewhere in the template. Simply binding the observable with the Async pipe again in a different location leads to two separate subscriptions 👇:
Duplicate HTTP requests for the same observable stream bound twice in the template<br> This results in two distinct subscriptions to the same observable, causing two duplicate HTTP requests for the same data unnecessarily 👇:
...
@Component({
  template: `
    ...
    <main>
      ...            👇
      @if (todo$ | async; as todo) {
        <p>Title: {{todo.title}}</p>
      }
    </main>

    <aside>
      ...            👇
      @if (todo$ | async; as todo) {
        <p>Is Completed: {{todo.completed}}</p>
      }
    </aside>
   ...
  `,
  standalone: true,

})
export class ShareReplayComponent {
  todo$ = inject(HttpClient)
    .get<Todo>('https://jsonplaceholder.typicode.com/todos/1')
    .pipe(shareReplay(1)); 👈
}
A common solution to this problem, which I've frequently observed, is to introduce caching at the RxJS level. The first HTTP request's data is cached using the shareReplay operator:
Caching HTTP request data with shareReplay RxJS operator This ensures that even if the observable is bound with the Async pipe in multiple places, only a single HTTP request is made. The response is then cached and shared among all subscriptions 👇:
...
@Component({
  template: `
    ...
    @let todo = todo$ | async; 👈
    <main>
      ...
      @if (todo) {
        <p>Title: {{todo.title}}</p>
      }
    </main>

    <aside>
      ...
      @if (todo) {
        <p>Is Completed: {{todo.completed}}</p>
      }
    </aside>
   ...
  `,
  standalone: true,

})
export class LetVariablesComponent {
  todo$ = inject(HttpClient)
    .get<Todo>('https://jsonplaceholder.typicode.com/todos/1');
}
While this pattern is effective, is there a more straightforward method to achieve the same outcome? Let's explore 💪.

Streamlining with @let Declarations

The RxJS approach works as intended, but the @let declaration, new in Angular v18.1, provides a simpler, template-centric solution 👇:
Avoid duplicate HTTP requests using @let local variables As you can see, this introduces a form of "template-based caching." The HTTP observable is bound and subscribed to just once within the template 👇: The result is that no duplicate HTTP requests are made, and the RxJS shareReplay operator is no longer necessary. 🚀🚀

Note💡: This approach is effective for caching data for use in the template. If you need to access the cached data within the component's class, the shareReplay operator remains the required solution.


Special thanks to @kreuzerk and @eneajaho for their review. Thank you for reading! I trust you found this article valuable. If you enjoyed it, please consider sharing it with your network. For any questions or feedback, please leave a comment below 👇. To stay updated on future articles, you can follow me at @lilbeqiri, dev.to, or Medium. 📖