Reactive Architecture
The Goal
We will discuss the evolution of state and business logic moving further and further away from the component layer as a result of reactive architecture.
We will talk about what the introduction of signals means for reactive development.
We will explore various scenarios where signals are a better choice than RxJS and vice versa.
Our goal is to create wire up a feature using an RxJS subject.
The Code
The Facade
npx nx g @nx/angular:library challenges-local-data --directory=libs/challenges-local-data --standalone=false --projectNameAndRootFormat=as-provided
We are creating ChallengesLocalFacade
to have the same interface as ChallengesFacade
so that we can inject them interchangeably.
import { Injectable } from '@angular/core';
import { Challenge } from '@proto/api-interfaces';
import { BehaviorSubject } from 'rxjs';
const mockChallenges: Challenge[] = [
{
id: 'challenge1',
title: 'Local Challenge 01',
description: 'Local challenge description',
comment: 'Nice work!',
repo_url: 'enterprisey.apps',
completed: true,
user_id: 'user1',
},
{
id: 'challenge2',
title: 'Local Challenge 02',
description: 'Local challenge description',
comment: 'Hooray!',
repo_url: 'enterprisey.apps',
completed: true,
user_id: 'user1',
},
{
id: 'challenge3',
title: 'Local Challenge 03',
description: 'Local challenge description',
comment: 'See comments',
repo_url: 'enterprisey.apps',
completed: false,
user_id: 'user1',
},
];
const mockChallenge = {
id: null,
title: '',
description: '',
comment: '',
repo_url: '',
completed: false,
user_id: '',
};
@Injectable({
providedIn: 'root'
})
export class ChallengesLocalFacade {
private loaded = new BehaviorSubject<boolean>(false);
private challenges = new BehaviorSubject<Challenge[]>([]);
private selectedChallenge = new BehaviorSubject<Challenge>(mockChallenge);
loaded$ = this.loaded.asObservable();
allChallenges$ = this.challenges.asObservable();
selectedChallenge$ = this.selectedChallenge.asObservable();
resetSelectedChallenge() {}
selectChallenge(selectedId: string) {
const challenge =
this.challenges.value.find((challenge) => challenge.id == selectedId) ||
mockChallenge;
this.selectedChallenge.next(challenge);
}
loadChallenges() {
this.challenges.next(mockChallenges);
}
loadChallenge(challengeId: string) {
const challenge =
this.challenges.value.find((challenge) => challenge.id == challengeId) ||
mockChallenge;
this.selectedChallenge.next(challenge);
}
saveChallenge(challenge: Challenge) {
if (challenge.id) {
this.updateChallenge(challenge);
} else {
this.createChallenge(challenge);
}
}
createChallenge(challenge: Challenge) {
this.challenges.next([...this.challenges.value, challenge]);
}
updateChallenge(challenge: Challenge) {
const challenges = this.challenges.value.map((c) => {
return c.id == challenge.id ? Object.assign({}, challenge) : challenge;
});
this.challenges.next(challenges);
}
deleteChallenge(challenge: Challenge) {
const challenges = this.challenges.value.filter((c) => c.id == challenge.id);
this.challenges.next(challenges);
}
}
We are able to provide ChallengesLocalFacade
in place of ChallengesFacade
by using the useClass
provider syntax.
import { ChallengesFacade } from '@proto/challenges-state';
import { ChallengesLocalFacade } from '@proto/challenges-local-state';
export const appConfig: ApplicationConfig = {
providers: [
{ provide: ChallengesFacade, useClass: ChallengesLocalFacade },
// ...
]
Resources
Component Communication with Signals: Inputs, Two-Way Bindings, and Content/ View Queries