, ,

Angular 2025: Remote Data, Signals, and the Questions You’re Asking

Jun 14, 2025 reading time 11 minutes

Angular 2025: Remote Data, Signals, and the Questions You’re Asking

In this blog post, I respond to questions from the community about how to communicate with remote servers in modern Angular applications. With the framework evolving — introducing Signals, the Resource API, and changes in best practices — it’s easy to feel overwhelmed. We will talk about using toSignal(), whether the async pipe is still relevant, when subscribe() is okay, and how to think about mutability and state.

Angular 2025: Remote Data, Signals, and the Questions You’re Asking

I recently saw this post on LinkedIn

https://www.linkedin.com/feed/update/urn:li:activity:7336724491722506240

And to be honest, I was really feeling this developer and the struggles currently.

Angular changed a lot in the last years and brought up many new things. If you are starting with Angular nowadays, I can totally see why this questions come up.

In this blog post, I would love to get into the questions and put my personal view on all of those points. Of course this is opinionated, but I will try to be as objective as I can be. The closing words are then my personal final thoughts.

Should I convert every Observable to a Signal using toSignal()?

I think we have to shed a little light here, before we dive into this.

In a nutshell, without OnPush, Angular had to check the complete application for changes every time something happened to the app. “Complete application” means “every rendered Component in the DOM”. This was time-consuming and not very efficient. OnPush helped there, because the component was only checked, when a reference of an input was changed or the async-Pipe reports a new value delivered by an Observable. Instead of the async-Pipe with Observable we can now also directly bind to Signals.
Signals generally now have the ability to hold a value and to tell their listeners (mostly the DOM, but also effects can be listeners) that they changed. They can raise their hand and tell, “Hey, I got a new value. Please, the next time you pass by, ask me for my new value”.
Angular tracks which signals are dirty, asks them for the new value and reflects it on the DOM. That is what signals do in a nutshell. Having said that `OnPush` works with signals and observables btw.

RxJs or Observables on the other side, are for streams of data. They take care about, let’s say web sockets or streams, where data is being pushed into your app.

Going back to the question: Should I convert every Observable to a Signal using toSignal()? No. I don’t think so.

You could do something like

@Component({
  // ...
  template: ` 
  @for ( product of products$ | async; track $index) {
    // ...
  }`,
})
export class ProductsComponent {
  private readonly http = inject(HttpClient);

  products$ = this.http.get<Product[]>('http://localhost:3000/products'); 
}

and convert it to

@Component({
  // ...
  template: ` 
  @for ( product of products(); track $index) {
    // ...
  }`,
})
export class ProductsComponent {
  private readonly http = inject(HttpClient);

  products = toSignal(this.http.get<Product[]>('http://localhost:3000/products')); 
}

Which would be a signal now but makes error handling a little more effort, for example. So this is not the best usage for toSignal. But if you have an observable which holds any kind of state, and you want to use it for example inside a computed to create a new value, you can use toSignal for this.

export class MyComponent {
  readonly errors$: Observable<string[]>...
  readonly user: signal<string>();

  readonly message = computed(() => 
    `${this.user()} has this errors ${toSignal(this.errors$).join(", ")}`);
}

Now the template as a consumer can use the latest signal improvements. This code is a typical construct of having observables for historical reasons as well as new signals on the other side. Those observables can come from many services etc.

If you have something in your business logic like

const socket$ = webSocket('wss://stocks.example.com');

socket$.pipe(
  throttleTime(100), // only process one message every 100ms
  filter(msg => msg.price > 100) // only show stocks above 100
).subscribe(message => {
  // do something with the stock info
});

There is no need to convert to signals for now. This is in the business logic layer, no UI in sight, and you are stream based getting pushed data from the outside. Use signals when you are more of in a UI environment. When it comes to business logic, RxJs can be a nice tool to use.

Is using the async Pipe in the template still the best practice?

If you are using the async pipe in the template, you did many things very correct. Because you used almost every inch of reactivity you got from the system.
You do not manually subscribe in your component but work with RxJs streams until the DOM which is the closest to the UI as you can get. UI triggers something, you are being in reactivity through your app and all the way back to your UI, which subscribes through the async pipe, and finally updates. So you did not “end” the stream manually with a subscribe. So yes, this is a good practice You do not have to migrate from the async pipe to signals immediately. But new code should all be signal based. Migrate old if you have time and want to make your app future ready.

When is it okay to just subscribe() and set the result to a regular field?

As we have just said, you can totally do that. But over the time, you will notice that most of the time you do not have to. Everything you want to do can be done with RxJs operators as well.

@Component({
  // ...
  template: ` 
  @for ( product of products; track $index) {
    // ...
  }`,
})
export class ProductsComponent {
  // ...

  products = [];

  this.http.get<Product[]>('http://localhost:3000/products').subscribe((products)=>{
      this.products = products;
  }); 
}

This code has much code noise and ends the reactivity before the template. Also, `OnPush` would not work here anymore. It is not wrong per se. But the subscribe is simply not needed. It can be transformed to

@Component({
  // ...
  template: ` 
  @for ( product of products$ | async; track $index) {
    // ...
  }`,
})
export class ProductsComponent {
  products$ = this.http.get<Product[]>('http://localhost:3000/products'); 
}

No need to set the regular field and we can still execute logic.

But if you want to work with a value which is not needed in the template, a subscribe can make sense.

@Component({
  // ...
  template: `...`,
})
export class ProductsComponent {
  // ...
  this.http.delete<Product[]>('http://localhost:3000/products').subscribe(() => {
      this.toaster.showToast("I deleted something");
  }); 
}

But here, no template manipulation is involved in the subscribe and the reactivity is ended before the template.

What is the role of mutability in all this? Should I prefer immutable streams or mutable state with signals?

Oh, that state thing, :-) This is what it all boils down to. That is a deep question, though.

Signals can be mutated. Not signals directly, but their value. Signals can cope with this as they track their dirtiness inside and tell their listeners or consumers. However, most of the time there is a much more sensible reaction to changing something’s value in comparison to changing something’s reference.

When you for instance return a new array with the just added entry you can be sure that the reference AND the value changed. If you just push inside an array, you have changed the value but not the reference.

Imagine Angular using the OnPush change detection. Changing the value would not do a thing. Changing the reference, however, would bring the component to update the UI.

I would always consider using immutable things, but also working with signals. This is why I almost always use a state management system like NgRx in my applications. Then I do not have those problems. I will get to this later.

When you are working with events over time, then you can use the RxJs tool to cover these cases. If you are managing component and UI State Signals would be the better choice.

What about the new resource API. Is it production ready, or should I avoid it for now?

The resource API is currently in an experimental state. But you can try it out. What it actually does is trying to fill the gap between exactly the points mentioned. We have an http call and instead of subscribing (RxJs), adding it to a field or using toSignal and all of this, it provides the “I-can-do-a-call-when-something-changes-and-return-the-response-as-a-signal/…-for-you”. It provides a signal out of an http call, automatically recalculating when the source changes. There will be more to come on this in the future, but it is a nice thing to play around with. I would recommend trying it out :-)

Final thoughts

All those things boil down to state in the end. State and how to manage it. It is very interesting to see those questions, as they would not arise with a state management system like the NgRx Signal Store. All of those things would be clarified by it.

A component using signals provided over a store:

@Component({
  // ...
  template: ` 
  @for ( product of store.products(); track $index ) {
    // ...
  }`,
  providers: [ProductsStore]
})
export class ProductsComponent {
  readonly store = inject(ProductsStore);
}
export const ProductsStore = signalStore(
  withState({
    products: [] as Product[],
  }),
  withMethods((store, productsService = inject(ProductsService)) => ({
    loadProducts: rxMethod<void>(
      exhaustMap(() =>
        productsService.loadProducts().pipe(
          tapResponse({
            next: (products) => patchState(store, { products }),
            error: (error: HttpErrorResponse) =>
              console.error('Failed to load products.', error.message),
          }),
        ),
      ),
    ),
  })),
  withHooks({
    onInit: (store) => store.loadProducts(),
  }),
);

or even easier with promises

export const ProductsStore = signalStore(
  withState({
    products: [] as Product[],
  }),
  withMethods((store, productsService = inject(ProductsService)) => ({
    async loadProducts(): Promise<void> {
      const products = await lastValueFrom(productsService.loadProducts());
      patchState(store, { products });
    },
  })),
  withHooks({
    onInit: (store) => store.loadProducts(),
  }),
);

And the call from the component would not even change. The component is not interested in if there are promises or RxJs involved. This is how it should be. We have RxJs where it needs to be, or we use promises. We have signals where they need to be, no async pipe, no toSignal, no question about mutability and no subscribe in sight. And the ProductsService is a clear single responsible service: Only making http calls. The ProductsStore taking care of the state, and we have no logic inside the components which makes them easy to test and does not pollute them :-) In addition to that we get provided a clear pattern how things should be accomplished and can provide that to our team.

I hope this helped

Fabian