79 votes

Comment puis-je détecter le changement de variable de service lorsqu'elle est mise à jour à partir d'un autre composant?

Je suis en train d'essayer d'obtenir la valeur mise à jour à partir d'une variable de service (isSidebarVisible) qui est constamment mise à jour par un autre composant (header) avec un événement de clic (toggleSidebar).

sidebar.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable() 
export class SidebarService {
    isSidebarVisible: boolean;

    sidebarVisibilityChange: Subject = new Subject();

    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 (Où la variable de service est mise à jour)

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() {

    }
}

Je peux voir le changement de la valeur de la variable de service dans header.component.html lorsque {{ isSidebarVisible }} mais dans sidebar.component.html cela affiche toujours la valeur par défaut et n'écoute jamais les changements.

Merci de m'aider à résoudre ce problème.

117voto

Sasxa Points 3969

Déplacez l'abonnement au service et les deux composants peuvent accéder à cette valeur. Si vous avez besoin de la valeur une seule fois, vous pouvez l'utiliser directement (comme je l'ai fait dans sidebar.component); Si vous avez besoin de mettre à jour quelque chose avec cette valeur, vous pouvez utiliser un getter (exemple dans header.component).

sidebar.service.ts :

@Injectable() 
export class SidebarService {
    isSidebarVisible: boolean;

    sidebarVisibilityChange: Subject = new Subject();

    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()
    }
}

Vous pouvez également vous abonner au sujet dans l'un ou l'autre des composants et obtenir la valeur à cet endroit :

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

Si vous voulez en savoir plus sur les sujets, jetez un œil ici.

0 votes

Merci! Si je veux afficher la valeur de retour de isSidebarVisible() dans header.component, comment puis-je faire cela?

1 votes

`

{{ isSidebarVisible | json }}

`

0 votes

Merci! Cela est vraiment utile.

2voto

Arno Teigseth Points 167

@isherwood m'a mis sur la voie, merci! C'était la question et la réponse qui ressemblaient le plus à ma situation.

Dans mon cas, j'ai un service utilisateur global qui écoute les changements dans AngularFireAuth (authState) et lorsqu'un utilisateur valide existe, il met en place un écouteur sur le document AngularFirestore de cet utilisateur, contenant les informations de l'utilisateur comme son nom, son email, sa couleur, etc (/utilisateursapp/${user.id}). Toutes les pages qui ont besoin de quelque chose du service utilisateur obtiennent ces données en direct, puisque c'est un écouteur ( .subscribe() )

Mon problème était que les pages qui dépendaient du document utilisateur étant prêt (connecté et document utilisateur récupéré) avant de passer à faire d'autres choses avec la base de données. Lors de la navigation directe vers une page interne via des liens profonds, la page essayait de faire des choses avec la base de données avant que le document utilisateur ne soit prêt, ce qui entraînait une erreur/un crash.

En suivant les instructions de @isherwood ci-dessus, voici comment je m'assure que les pages de mon application attendent que les données de l'utilisateur soient prêtes avant d'essayer de faire des choses supplémentaires avec elles :

user.service.ts

export class UserService {

userDocumentReady: boolean = false; // la valeur initiale est "le doc utilisateur n'est pas prêt"
userDocObserver: Subject = new Subject(); // observer ce booléen

constructor(
  public fireauth: AngularFireAuth,
  public afs: AngularFirestore,
) {

// observer la variable
this.userDocObserver.subscribe(value => this.userDocumentReady = value);

this.fireauth.authState
  .subscribe(user => {
    if (user) {

      this.afs.doc(`/outildutilisateur/${user.uid}`).snapshotChanges()
      .subscribe(usr => {
        if (usr.payload.data()) {
          console.log(`obtenu un objet utilisateur de la base de données :`, usr.payload.data());              
          this.currentUser = usr.payload.data();

          this.userDocObserver.next(true); // basculer le drapeau UserDocumentReady

mytools.page.ts

// il s'agit d'une page connectée de l'application,
// accessible via le lien profond https://mon.app.domain/mesoutils

export class UsersPage implements OnInit {

constructor(
  public userService: UserService,
) { }

ngOnInit() {
  if (this.userService.userDocumentReady) {
    console.log(`utilisateurs : le document utilisateur est déjà ici, pas besoin d'attendre`);
    this.setupPageData();
    return;
  }

  // attendre le doc utilisateur avant de configurer la page
  this.userService.userDocObserver.subscribe(docReady => {
    if (docReady){
      console.log(`mes outils : valeur de l'utilisateur prêt ${docReady}. maintenant configurer la page.`);
      this.setupPageData(); // cette fonction effectue les opérations de base de données qui dépendent des données utilisateur déjà prêtes
    }
  });
}

Cela "fonctionne pour moi" maintenant et le comportement semble cohérent. Aussi très pratique lors du développement, car taper une URL dans la barre du navigateur provoque ce type de navigation directe. J'écris ceci pour m'en souvenir - et qui sait si cela peut être utile à d'autres ?

Ceci n'est que mon deuxième application Ionic, donc si quelque chose est terriblement faux dans ma structuration/mise en place, je suis très intéressé par les commentaires sur cette solution :)

éditer : ajout d'une vérification dans mytools.page.ts pour ne pas attendre (indéfiniment) un nouveau doc utilisateur s'il en existe déjà un

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X