Search code examples
angulartypescriptdesign-patternsbehaviorsubject

Public service variables should be defined with a BehaviorSubject type


Is it a good idea to make public variables on a service BehaviorSubject types as a standard approach.

The idea being, that if you want to make a variable public, chances are big that you want to respond to changes of them (now or in the near future). That seems to be the case for pretty much every public variable I add on a service.

I started to change the type of some variables because I had to, and now I am starting to see this as some kind of pattern.

An example would be a public variable which holds the connection state. isConnected. When the connection of the service is broken, you want to show a popup message to the end user, and make it dissapear when the connection is restored.

Another example: a variable contains the UI settings of the currently logged in user. When one of these settings changes, you want to apply these settings immediately and re-render the screen layout.

Since I haven't read about this as a pattern before, is this strategy flawed for some reason ? If so, why ?


Solution

  • Exposing a BehaviorSubject as public or any Subject which can be next'ed (is that a verb?) is in general a bad idea. This way you will partly expose the purpose of a service to the outside world, while only the service should be in charge of doing things. So best would be to use the .asObservable() method. This will return an Observable without the subject part:

    export class ConnectionService {
      private readonly isConnected = new BehaviorSubject(false);
    
      readonly isConnected$ = this.isConnected.asObservable();
    }
    

    A component or another service can then listen to this observable, without being able to update it directly:

    @Component({
      template: `
        <div *ngIf="!(isConnected$ | async)" class="popup">Oh no!</div>
      `
    })
    export class ConnectionPopupComponent {
      readonly isConnected$ = this.cs.isConnected$;
    
      constructor(private cs: ConnectionService) {}
    }
    

    When your app grows bigger, or you expect it to grow bigger in time, it might be an idea to look at state handling using ngrx or ngxs (or whatever state managing system will suit your needs)