Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I detect service variable change when updated from another component?

I'm trying to get the updated value from a service variable (isSidebarVisible) which is keeps on updated by another component (header) with a click event (toggleSidebar).

sidebar.service.ts

import { Injectable } from '@angular/core'; import { Subject } from 'rxjs/Subject';  @Injectable()  export class SidebarService {     isSidebarVisible: boolean;      sidebarVisibilityChange: Subject<boolean> = new Subject<boolean>();      constructor()  {         this.isSidebarVisible = false;     }      toggleSidebarVisibilty() {         this.isSidebarVisible = !this.isSidebarVisible         this.sidebarVisibilityChange.next(this.isSidebarVisible);     } } 

sidebar.component.ts

export class SidebarComponent implements OnInit {     asideVisible: boolean;     _asideSubscription: any;      constructor(private sidebarService: SidebarService) {         this.asideVisible = sidebarService.isSidebarVisible         this._asideSubscription = sidebarService.sidebarVisibilityChange.subscribe((value) => {             this.asideVisible = value         })     }          ngOnInit() {     } } 

header.component.ts (Where service variable is updated)

export class HeaderComponent implements OnInit {     isSidebarVisible: boolean;     _subscription: any;      constructor(private sidebarService: SidebarService) {         this._subscription = sidebarService.sidebarVisibilityChange.subscribe((value) => {             this.isSidebarVisible = value         })     }      toggleSidebar() {         this.sidebarService.toggleSidebarVisibilty()     }      ngOnInit() {      } } 

I can see the service variable value change in header.component.html when {{ isSidebarVisible }} but In sidebar.component.html it always prints the default value and never listened to the changes.

Please help me fix this.

like image 904
Body Avatar asked Apr 01 '17 16:04

Body


People also ask

How Angular 2 detect changes?

There are two types of change detection: default change detection: Angular decides if the view needs to be updated by comparing all the template expression values before and after the occurrence of an event, for all components of the component tree.

How does Angular detect changes in service?

The answer is using observables. To run the change detector manually: Inject ChangeDetectorRef service in the component. Use markForCheck in the subscription method to instruct Angular to check the component the next time change detectors run. On the ngOnDestroy() life cycle hook, unsubscribe from the observable.

Which of the following detects the changes during run time in an Angular application?

Angular provides two strategies for Change Detection. In its default strategy, whenever any data is mutated or changed, Angular will run the change detector to update the DOM. In the onPush strategy, Angular will only run the change detector when a new reference is passed to @Input() data.


2 Answers

Move subscription to the service, and both components can access this value. If you need value only once, you can use it directly (like I did in sidebar.component); If you need to update something with this value it you can use getter (example in header.component).

sidebar.service.ts:

@Injectable()  export class SidebarService {     isSidebarVisible: boolean;      sidebarVisibilityChange: Subject<boolean> = new Subject<boolean>();      constructor()  {         this.sidebarVisibilityChange.subscribe((value) => {             this.isSidebarVisible = value         });     }      toggleSidebarVisibility() {         this.sidebarVisibilityChange.next(!this.isSidebarVisible);     } } 

sidebar.component.ts

export class SidebarComponent {     asideVisible: boolean;      constructor(private sidebarService: SidebarService) {         this.asideVisible = sidebarService.isSidebarVisible;     } } 

header.component.ts

export class HeaderComponent {     constructor(private sidebarService: SidebarService) { }      get isSidebarVisible(): boolean {         return this.sidebarService.isSidebarVisible;     }      toggleSidebar() {         this.sidebarService.toggleSidebarVisibility()     } } 

You can also subscribe to the subject in either/both components and get the value there:

this.sidebarService.sidebarVisibilityChange.subscribe(value => {...}); 

If you want to know more about Subjects take a look here.

like image 54
Sasxa Avatar answered Oct 07 '22 01:10

Sasxa


@isherwood put me on the track, thanks! This was the question and answer that looked the most like my situation.

In my case, I have an app-wide userService that listens to changes in AngularFireAuth (authState) and when a valid user exists, it sets up a listener to that user's AngularFirestore document, containing the user's info like name, email, color, whatever (/appusers/${user.id}). All the pages that need anything from userService get those data live, since it's a listener ( .subscribe() )

My problem was that pages relying on the user document being ready (logged in and user document fetched) before going on to do more database stuff. When navigating straight to an internal page via deep links, the page would try to do database things before the user doc was ready, leading to error/crash.

Following @isherwoods instructions above, this is how I make sure my app's pages wait for the user data to be ready before trying to do additional database things with it:

user.service.ts

export class UserService {  userDocumentReady: boolean = false; // initial value is "userdoc is not ready" userDocObserver: Subject<boolean> = new Subject<boolean>(); // observing that bool  constructor(   public fireauth: AngularFireAuth,   public afs: AngularFirestore, ) {  // watch variable this.userDocObserver.subscribe(value => this.userDocumentReady = value);  this.fireauth.authState   .subscribe(user => {     if (user) {        this.afs.doc(`/tooluser/${user.uid}`).snapshotChanges()       .subscribe(usr => {         if (usr.payload.data()) {           console.log(`got user object from database:`, usr.payload.data());                         this.currentUser = usr.payload.data();            this.userDocObserver.next(true); // flip UserDocumentReady flag 

mytools.page.ts

// this is a logged-in page of the app, // accessible through deep link https://my.app.domain/mytools  export class UsersPage implements OnInit {  constructor(   public userService: UserService, ) { }  ngOnInit() {   if (this.userService.userDocumentReady) {     console.log(`users: userdoc already here, no need to wait`);     this.setupPageData();     return;   }    // wait for userdoc before setting up page   this.userService.userDocObserver.subscribe(docReady => {     if (docReady){       console.log(`mytools: user ready value ${docReady}. now setup page.`);       this.setupPageData(); // this function does the database stuff that depends on the user data to be ready first     }   }); } 

This "worksforme" now and behavior seems consistent. Also very handy when developing, as typing a URL in the browser bar causes this kind of direct navigation. Writing this to remember it - and who knows if it can be useful to others?

This is only my second ionic app, so if something is horribly wrong with my structuring/setup, I'm very much interested in comments on this solution :)

edit: added check in mytools.page.ts so that we won't wait (forever) on a new user doc if one already exists

like image 26
Arno Teigseth Avatar answered Oct 07 '22 01:10

Arno Teigseth