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.

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.

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

Block quote

Ordered list

  1. Item 1
  2. Item 2
  3. Item 3

Unordered list

  • Item A
  • Item B
  • Item C

Text link

Bold text

Emphasis

Superscript

Subscript