0

I have a simple app in which users will end up sending messages (very much like a chat-app). I'm starting to implement RxJS to share state between components through a service:

// session.service.ts

export class SessionService {
  private subject = new Subject<sessionType.Session>();

  constructor() { }

  public setSession(session: sessionType.Session) {
    this.subject.next(session)
  }

  public getSession(): Observable<sessionType.Session> {
    return this.subject.asObservable()
  }
}
// join-session.component.ts

  public joinSession(code: FormControl) {
    ...

    this.getSessionSubscription = this.sessionQuery.valueChanges.subscribe(({data}) => {
      this.sessionService.setSession(data.getSession)
    })
  }
// app.component.ts

  public sessions: Array<sessionType.Session> = []
  public session: sessionType.Session | undefined;


  private subscription!: Subscription;

  constructor(
    private apollo: Apollo,
    private sessionService: SessionService
  ) {
    this.subscription = this.sessionService.getSession().subscribe({
      next: (session) => this.session = session,
      error: (error) => console.log(error),
      complete: () => console.log('complete')
    })
  }

This is all working as expected, when a user joins a session I can see the joined session in the app.component but I'm not very confident that this is the correct approach. I was hoping someone could either point out what I could improve, or perhaps acknowledged that this is indeed correct.

Peter Boomsma
  • 8,851
  • 16
  • 93
  • 185
  • 3
    This is a right approach Peter, but if you want to try to different approach then it's better to use behaviorsubject. – Goutham Harshith Feb 17 '23 at 12:30
  • 3
    This might work for simple scenarios but if it will get more complex I would suggest using a store instead. (Pick NGRX, NGXS or any other store implementation that suits you.) – Eldar Feb 17 '23 at 12:33
  • 1
    Maybe [this article](https://medium.com/p/88d2789e408a) can give you some inspiration. It describes how to use a service to hold state and drive the app reactively. On the surface it talks about react, but the key concept around the design of the service apply to angular too. In fact, following this approach, you keep Views very lightweight, moving most of the logic to the service itself. – Picci Feb 17 '23 at 13:33
  • Here is also a video that goes through the approach presented in the answer below: https://www.youtube.com/watch?v=vtCDRiG__D4 – DeborahK Feb 17 '23 at 20:42
  • Hey Deb, i just took s month on pluralsight to follow your course. Good stuff! – Peter Boomsma Feb 18 '23 at 12:33

1 Answers1

1

I would move the subscription out of the typescript and into the html with | async. This will automatically subscribe() and unsubscribe() for you so you don't experience potential memory leaks.

First we will update the service with a BehaviorSubject (we can also remove the function and use a property if desired, as I feel it looks cleaner). If we wanted to manipulate the data before the component has access, we could modify it in our service by replacing asObservable() with a pipe() and some operators such as map().

export class SessionService {
  private subject = new ReplaySubject<sessionType.Session>(1);
  public session$ = this.subject.asObservable();

  constructor() { }

  public setSession(session: sessionType.Session) {
    this.subject.next(session)
  }
}

Here is the app.component.ts, we can use as along with the | async pipe to set a variable within the template. This will allow us to access the value of the observable (aka our session data) using that variable name. In this example I use the variable name sessionVar. If you want to subscribe to multiple observables you could use an object (*ngIf="{a:a$|async,b:b$|async} as obj" then access it with obj.a or obj.b).

@Component({
  template: `
    <div *ngIf="session$ | async as sessionVar">
      {{sessionVar|json}}

      <!-- use async the same way in this component. -->
      <join-session></join-session>
    </div>
  `
})
export class AppComponent {
  public sessions: Array<sessionType.Session> = [];
  protected session$ = this.sessionService.session$;

  constructor(
    private sessionService: SessionService
  ) {}
}
Get Off My Lawn
  • 34,175
  • 38
  • 176
  • 338
  • I was having an issue where `BehaviorSubject` needs an argument, if I provide a object of type `Session` the `ngIf` in the template would (incorrectly for me) trigger. But I think with `https://www.learnrxjs.io/learn-rxjs/subjects/replaysubject` it works great since it doesn't need an argument. – Peter Boomsma Feb 17 '23 at 15:24
  • 1
    Yeah, a `ReplaySubject` with a value of `1` will provide the same effect as [`BehaviorSubject`](https://stackoverflow.com/questions/44693438/behaviour-subject-initial-value-null). – Get Off My Lawn Feb 17 '23 at 17:16