Angular and RxJS logos linked by a data stream, illustrating consuming async data in Angular with Subjects
Back to Blog
Frontend Development
November 4, 2025
Emran Hossain

Consuming Async Data in Angular with Subjects

A worked example of consuming async data in Angular with Subjects, so your components stay in sync without tangled callbacks or repeated requests.

Angular and RxJS logos linked by a data stream, illustrating consuming async data in Angular with Subjects

Most Angular state problems are really one question: how do several parts of the app react to the same data without each one fetching it again? RxJS Subjects are the cleanest answer the framework gives you.

What a Subject actually is

A normal Observable is unicast. Every subscriber gets its own private execution, so if two components subscribe to the same HTTP call, you fire two requests. A Subject is multicast: one source, many listeners, all sharing the same emissions. It is an Observable and an Observer at once, so you can subscribe to it and also push values in with next().

const events = new Subject<string>();
events.subscribe(v => console.log('A:', v));
events.next('saved');     // A: saved
events.subscribe(v => console.log('B:', v));
events.next('updated');   // A: updated   B: updated

B never sees 'saved'. A plain Subject only delivers what happens after you subscribe.

Pick the right Subject for the job

  • Subject: bare multicast. Late subscribers miss earlier values. Good for one-off events like a save that just happened.
  • BehaviorSubject: holds the latest value and hands it to every new subscriber on subscription. Needs an initial value. This is the one you want for state: the current user, a filter, a loading flag.
  • ReplaySubject: replays the last N emissions to new subscribers. Reach for it when the latest value alone is not enough.

The pattern that matters: a shared data service

@Injectable({ providedIn: 'root' })
export class UserStore {
  private users = new BehaviorSubject<User[]>([]);
  users$ = this.users.asObservable();

  constructor(private http: HttpClient) {}

  load() {
    this.http.get<User[]>('/api/users')
      .subscribe(list => this.users.next(list));
  }
}

Two details earn their keep. The Subject is private, and users$ is exposed through asObservable() so components can read the stream but never push into it. And because it is a BehaviorSubject, a component that subscribes after load() ran still gets the current list straight away.

Consuming it in a component

@Component({
  template: '<li *ngFor="let u of users$ | async">{{ u.name }}</li>',
})
export class UserListComponent implements OnInit {
  users$ = this.store.users$;
  constructor(private store: UserStore) {}
  ngOnInit() { this.store.load(); }
}

Any other component that injects UserStore and reads users$ shows the same data, updated the moment load() emits. No inputs threaded through three layers of components, and no second request.

Two things that bite people

Expose asObservable(), not the raw Subject. If components can call next() on your state, you lose the single source of truth that made this worth doing. And prefer the async pipe over a manual subscribe(): it unsubscribes when the component is destroyed. If you must subscribe by hand, clean up with takeUntil(...) or take(1), or you will leak subscriptions.

For the streams Subjects build on, see Angular Observables. Need this inside a real product? ARITS does web application development.

Need extra engineers, or a team to run the build?

Tell us the shape of the work and your deadline. You will get a straight read on what it takes and what it will cost, from a team that has shipped 400+ projects out of Dhaka and London.

Cookies

We use cookies to see how people find this site and to measure our ads. You can turn those off — everything here still works. Privacy policy

Necessary

Keeps the site working and remembers this choice.

Always on

Analytics

Anonymous stats on which pages get read, so we know what to write next.

Marketing

Lets us measure which ads brought someone here.