dalox
dalox

Reputation: 157

how to replace a service with a Subject with signals?

Say I have a very simple shared service holding a subject:

@Injectable({ providedIn: 'root' })
export class MyService {
  private _runTaskSubject = new Subject<string>();
  
   get runTaskSubject$() {
     return this._runTaskSubject.asObservable();
   }

   runTask(name: string) {
     this._runTaskSubject.next(string);
   }
}

Observers of that runTaskSubject$ will only get notified once runTask is called.

Now I was thinking to convert this service using signals, but it seems that not an option as signals always hold an initial value. So does it makes sense to keep my rxjs Subject for this use case?

Upvotes: 2

Views: 217

Answers (1)

Naren Murali
Naren Murali

Reputation: 57986

The signals equivalent for the same is as below.

@Injectable({ providedIn: 'root' })
export class MyService {
  private _runTaskSignal = signal<string | undefined>(undefined);
  
   get runTaskSignal() {
     return this._runTaskSignal.asReadonly();
   }

   runTask(name: string) {
     this._runTaskSignal.set(name);
   }
}
  • We can set the initial value of the signal to be undefined to mimic the subject.

  • We can use asReadonly method to convert the signal to a readOnly signal, the advantage being the readOnly signal does not have access to set and update methods, hence if the signal should be updated, it can be updated only through the runTask method.

  • When you read the signal and do not want it to fire on initial load, all you need to do is check that the value is not undefined and then emit values.

      export class SomeComponent {
        myService: MyService = inject(MyService);
    
        constructor() {
          effect(() => {
            // simulate event bus
            const runTaskValue = this.myService.runTaskSignal();
            if(typeof runTaskValue !== 'undefined') {
              // ... some actions happen here!
            }
          });
        }
        ...
    

Full Code:

import { Component, Injectable, signal, inject, effect } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

@Injectable({ providedIn: 'root' })
export class MyService {
  private _runTaskSignal = signal<string | undefined>(undefined);

  get runTaskSignal() {
    return this._runTaskSignal.asReadonly();
  }

  runTask(name: string) {
    this._runTaskSignal.set(name);
  }
}

@Component({
  selector: 'app-root',
  template: `
    <h1>Hello from {{ myService.runTaskSignal() }}!</h1>
  `,
})
export class App {
  myService: MyService = inject(MyService);

  constructor() {
    effect(() => {
      // simulate event bus
      const runTaskValue = this.myService.runTaskSignal();
      if (typeof runTaskValue !== 'undefined') {
        console.log('value changed: ', runTaskValue);
      }
    });
  }

  ngOnInit() {
    setInterval(() => {
      this.myService.runTask(`${Math.random()}`);
    }, 2000);
  }
}

bootstrapApplication(App);

Stackblitz Demo

Upvotes: 0

Related Questions