- read

How to Refactor Your Angular App from RxJS to Angular Signals

Marek Panti 30

How to Refactor Your Angular App from RxJS to Angular Signals

Marek Panti
JavaScript in Plain English
3 min read5 days ago

--

It has been some time since Angular introduced signals, and the community began to discuss whether it is a step forward or a step back. For me, it is definitely a step forward.

Let’s quickly recap what signals are, and then we will go through quick steps and tips on how to consider your corporate app and how to refactor it, discussing what approach should be taken.

Firstly, in the beginning, the Angular team took a somewhat peculiar approach and introduced the RxJs pattern for everything, even for use cases where it is not the best choice. For example, if you want to make an HTTP request, save the response, and show it in the view, you would need to first import HttpClient from Angular, then call the API by subscribing to it or create another layer using the .pipe() operator. Afterward, you would subscribe to it:

// API service
@Injectable()
export class FetchingApiService {
constructor(private http: HttpClient) {}

fetchTodos(): Observable<ToDoInterface[]> {
return this.http.get<ToDoInterface[]>('https://jsonplaceholder.typicode.com/todos/');
}
}

// facade service
@Injectable()
export class FetchingFacadeService {
public allToDos$ = new BehaviorSubject(null);

constructor(private fetchService: FetchingApiService) {}

fetchTodos(): Observable<ToDoInterface[]> {
return this.fetchService.fetchTodos().pipe(
tap((todos: ToDoInterface[]) => {
this.allToDos$.next(todos);
})
);
}
}

// Component
fetchTodos(): Observable<ToDoInterface[]> {
return this.facade.fetchTodos();
}

This approach is also heavily dependent on Zone.js, which will re-render allToDos$ just to render it once or a couple of times when you add a new item to the list. You can check out my older article about Signals at this link: https://medium.com/@marekpanti/how-to-refactor-observable-patterns-to-angular-signals-3c998796e081

With signals however our approach would be different, we will use the javascript native solution with .fetch() api and with async and await

// API Service
@Injectable()
export class FetchingApiService {
async fetchTodosClassicApproach(): Promise<ToDoInterface[]> {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/');
const todos = await response.json();
return todos;
}
}

// Facade

@Injectable()
export class FetchingFacadeService {
public allTodosSignal = signal([]);

constructor(private fetchService: FetchingApiService) {}

async fetchTodos() {
const myTodosResponse: ToDoInterface[] =
await this.fetchService.fetchTodosClassicApproach();
this.allTodosSignal.set(myTodosResponse);
}

// Component .html
<div *ngFor="let todo of (facade.allTodosSignal())">

In this solution, we saved a lot of imports, and at the same time, we prepared our component for the next Angular release when we can use a flag to indicate that the component is using signals and is essentially out of the Zone.js scope.

But what approach should be taken when we want to update our app to use signals?

I would recommend to create three tickets:
1. Refactor all component.ts and change the the RxJs streams to signals with .toSignal() method. With that we will have our view based on signal and we can continue with the logic.

2. Then I would continue with the services and facades — here you can create more tasks based on layers and complexity of your app. So I would change all .pipe() to computed signals. After this step is done, basically almost all of our application is refactored, but do not forget the last step.

const name = signal('John');
const lastName = signal('Doe');
// computed can use if and other function, so you don't need to chain your operators
const FullName = Signal<string> = computed(() => name + lastName);

3. Change all your API calls that are using httpClient for native solution.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

In conclusion, if we consider the change carefully and already have our architecture clean, the refactoring should be straightforward. Thanks to the Angular Team, we can do it step by step with functions such as .toSignal(), and we don't have to get rid of Zone.js in one step.

In Plain English

Thank you for being a part of our community! Before you go: