0
votes

Hello I am basically new to Angular, Here I'm trying to access a boolean variable from service. A boolean variable with value true, I've declared in service. Based on some logic I'm going to change that service variable to false. That particular variable I'm using in several components. So I'm facing problem like, Initial value which I've assigned true is coming to all components where I've used, but after changing the value that is not reflecting in all components. Service class like this:

@Injectable()
export class MyService { 
running: boolean = true;
}

Component 1:

export class FirstComponent {
  constructor(private myService: MyService) { }
  check(){
  this.myservice.running = false;
  }
}

Component 2:

export class SecondComponent implements OnChanges {
      running;
      constructor(private myService: MyService) { }
      ngOnChanges(){
      this.running = this.myService.running;
      console.log('running', this.running);
      }
    }

This is the how I'm going to change boolean value and accessing from service in different components. I'm not sure whether this is correct approach or not, please correct me.

3
Did you add the service in app's root module? If not, then they aren't being shared among components.anuraagdjain
Have you tried providing your service in the root module? I know that angular will that way create a singleton instance of the service that can be shared across all of your components. I wonder if you are just getting a new service spun up and they are not monitoring the same stateO.MeeKoh
If you need that service to be singleton then you should add your service in approotmodule. then only it will act as singleton otherwise for each component different instance will be createdRenjith Raju
@anuraagdjain Yeah I've added in root module, I'm able to get initial boolean value, but after changing it is not getting reflected..might be some thing different process will be there for change will reflect by the time of accessing in componentsJayden
There must be same way to notify that data has changed, or else the other components will not get to know that. You can use Subject for this. Read more here blog.angularindepth.com/…Amit Chigadani

3 Answers

5
votes

How about a simple answer:

1) If you are using Angular v6 or newer, then simply change your service to the following:

@Injectable(providedIn: 'root')
export class MyService { 
   running: boolean = true;
}

2) Search your system and ensure that the MyService service is NOT listed in the providers array of any module or component.

3) Change your component to use a getter. As others have said, the ngOnChanges only works for @Input properties.

  export class SecondComponent {
      running;
      constructor(private myService: MyService) { }

      get running(): boolean {
        return this.myService.running;
      }
    }

That should do it!

When the value changes in the service, Angular's built-in change detection will pick up the change and reget the value, calling the getter and providing the updated value to the template.

You do not need a Subject or BehaviorSubject for something as straightforward as this. (Those are waaaay overused IMHO.)

I have a very simple (and similar) example here: https://stackblitz.com/edit/angular-simpleservice-deborahk

It uses a string, but would work exactly the same for a Boolean.

3
votes

You could use BehaviorSubject in your service. Then create an Observable through asObservable method of subject and then subscribe to that observable in whichever component you want.

Note that ngOnChanges is changed to ngOnInit.

In Service:

private runningSubject: BehaviorSubject<boolean> = new BehaviorSubject(true);
running = this.runningSubject.asObservable();

setRunning = (value: boolean) => {
   this.runningSubject.next(value);
}

In First Component:

export class FirstComponent {
  constructor(private myService: MyService) { }
  check(){
      this.myservice.setRunning(false);
  }
}

Second Component:

export class SecondComponent implements OnInit {
      running;
      constructor(private myService: MyService) { }

      ngOnInit(){
         this.myService.running.subscribe((isRunning) => {
            this.running = isRunning;
             console.log('running', this.running);
         });

      }
}
1
votes

At first, you must be sure that your service is singleton or at least have only one instance within scope of every component that you want to connect. The easiest way to achieve this is registering your service inside AppModule inside providers and nowhere else.

Second thing is that even if your service changes it's value, ngOnChanges will not be called without reason. It needs to be called by change detector, but service does not cause running change detection in way you are trying to use it.

Instead of that you should share this value with Observable.

export class TestService {
    private readonly _messageSubject: Subject<boolean>;

    get observable(): Observable<boolean> {
        return this._messageSubject.asObservable();
    }

    constructor() {
        this._messageSubject = new Subject<boolean>();
    }

    sendMessage(value: boolean): void {
        this._messageSubject.next(value);
    }
}

And now, every component which injects TestService has access to two methods:

observable which is getter. It returns observable from subject from your service. sendMessage(value: boolean) it sends your boolean message to every subscriber of _messageSubject.

If you want to subscribe that subject (and access every change of service's value) you need to use your service that way:

TestService.observable.subscribe(value => { /* You can access your boolean value here with name "value" */});

Notice that Subject shares something like changes of value, so callback inside subscribe method will only execute after every next change of that value (it means it will be called after every next call of sendMessage(value: boolean) method). If you want to access previous value (it means value which was latest before calling subscribe) use BehaviorSubject instead).