Back to Blog
General
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.

Have a project you want built right?

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