Управление подписками между службами и компонентами Angular

#javascript #angular #typescript #angularfire

#javascript #angular #typescript #angularfire

Вопрос:

Я разрабатываю веб-приложение, в котором несколько компонентов имеют схожие функции. Чтобы избежать дублирования кода в разных компонентах, я создал сервис для размещения этих функций.

Однако я не уверен, как управлять подписками на общие функции, поскольку они больше не являются частью каждого компонента и по-прежнему требуют подписки в службе. В идеале я бы вообще не подписывался на службу и подписывался только в компоненте, но характер функции и ее назначение не позволяют мне этого делать. Это функции AngularFire, вложенные одна в другую. Цель этой структуры — перемещаться по различным уровням базы данных и динамически заполнять объект. Затем этот объект используется для создания раздела заголовков главной таблицы. Количество уровней (столбцов и строк с накоплением) зависит от данных, присутствующих в БД, в зависимости от запрашиваемого узла БД. Это не мой любимый способ справиться с этим, поэтому, если вы думаете о более чистом способе сделать это, обязательно. Имейте в виду, что фактический код несколько сложнее, чем приведенный ниже пример.

Например, следующий объект генерируется этим кодом, и его соответствующий раздел заголовка отображается на следующем изображении :

 object = {

    title: 'PW100',
    sections: {
      section1: {
        title: 'HIGH PRESSURE ROTOR',
        sections: {
          section1: {
            title: 'HP TURBINE',
            sections : {
              section1: {title: 'ASS'},
              section2: {title: 'GRD'},
              section3: {title: 'BAL'}
            }
          },
          section2: {
            title: 'HP ROTOR',
            sections: {
              section1: {title: 'ASS'},
              section2: {title: 'BAL'}
            }
          }
        }
      },
      section2: {
        title: 'LOW PRESSURE ROTOR',
        sections: {
          section1: {
            title: 'LP TURBINE',
            sections : {
              section1: {title: 'ASS'},
              section2: {title: 'RIV'},
              section3: {title: 'GRD'},
              section4: {title: 'BAL'}
            }
          },
          section2: {
            title: 'LP ROTOR',
            sections: {
              section1: {title: 'ASS'},
              section2: {title: 'BAL'}
            }
          }
        }
      },
      section3: {
        title: 'POWER TURBINE PACK',
        sections: {
          section1: {
            title: '3RD STAGE TURBINE',
            sections : {
              section1: {title: 'ASS'},
              section2: {title: 'RIV'},
              section3: {title: 'GRD'},
              section4: {title: 'BAL'}
            }
          },
          section2: {
            title: '4TH STAGE TURBINE',
            sections : {
              section1: {title: 'ASS'},
              section2: {title: 'RIV'},
              section3: {title: 'GRD'},
              section4: {title: 'BAL'}
            }
          },
          section3: {
            title: 'PT PACK',
            sections: {
              section1: {title: 'ASS'},
              section2: {title: 'BAL'}
            }
          }
        }
      }
    }

  };
  

введите описание изображения здесь

То, что у меня было раньше, работало отлично :

Компоненты A и B раньше имели следующую структуру :

 export class ComponentXXClass implements OnInit, OnDestroy {

  // Variable used to unsubscribe in ngOnDestroy()
  $unsubscribe = new Subject();

  constructor(private afs: AngularFirestore) {}

  ngOnInit(): void {
    this.functionA().then(result => {
      // Use promise's result (which is an object) to generate the table's headers section
    });
  }

  ngOnDestroy(): void {
    this.$unsubscribe.next();
    this.$unsubscribe.complete();
  }

  functionA() {

    const objectToPopulateOnEachLevel = {};

    return new Promise((resolve) => {

      this.afs.collection('collectionName')
      .snapshotChanges()
      .pipe(takeUntil(this.$unsubscribe))
      .subscribe(level1VariableRequiredToAccessLevel2 => {

        level1VariableRequiredToAccessLevel2.forEach(level1Var => {

          // Store relevant information found at this level of the query
          objectToPopulateOnEachLevel['level1'] = level1Var.payload.doc.data()['variableOfInterest'];
    
          this.afs.collection(level1Var.payload.doc.ref.path   '/path1')
          .snapshotChanges()
          .pipe(takeUntil(this.$unsubscribe))
          .subscribe(level2Variable => {

            level2Variable.forEach(level2Var => {

              // Store relevant information found at this level of the query
              objectToPopulateOnEachLevel['level2'] = level2Var.payload.doc.data()['variableOfInterest'];
    
            });

            resolve(objectToPopulateOnEachLevel);

          });
        });
      });
    });
  }
}
  

Общая часть кода была продублирована между компонентами A и B (и многими другими в моем реальном сценарии), поэтому в итоге я сделал что-то вроде этого :

Обслуживание :

 export class Service {

  // Variable used to unsubscribe in components' ngOnDestroy()
  $unsubscribe = new Subject();

  constructor(private afs: AngularFirestore) {}

  functionA(parentPath: string, childPath: string) {

    const objectToPopulateOnEachLevel = {};

    return new Promise((resolve) => {

      this.afs.collection(parentPath)
      .snapshotChanges()
      .pipe(takeUntil(this.$unsubscribe))
      .subscribe(level1VariableRequiredToAccessLevel2 => {

        level1VariableRequiredToAccessLevel2.forEach(level1Var => {

          // Store relevant information found at this level of the query
          objectToPopulateOnEachLevel['level1'] = level1Var.payload.doc.data()['variableOfInterest'];
    
          this.afs.collection(level1Var.payload.doc.ref.path   '/'   childPath)
          .snapshotChanges()
          .pipe(takeUntil(this.$unsubscribe))
          .subscribe(level2Variable => {

            level2Variable.forEach(level2Var => {

              // Store relevant information found at this level of the query
              objectToPopulateOnEachLevel['level2'] = level2Var.payload.doc.data()['variableOfInterest'];
    
            });

            resolve(objectToPopulateOnEachLevel);

          });
        });
      });
    });
  }
}
  

Компоненты A и B :

 export class ComponentXXClass implements OnInit, OnDestroy {

  constructor(private _service: Service) {}

  ngOnInit(): void {
    this._service.functionA('parentPathString', 'childPathString').then(result => {
      // Use promise's result (which is an object) to generate the table's headers section
    });
  }

  ngOnDestroy(): void {
    this._service.$unsubscribe.next();
    this._service.$unsubscribe.complete();
  }

}
  

Это работает, но не полностью. Я имею в виду, что данные отображаются правильно, но кажется, что, хотя я не вышел из компонента A (например), подписка, похоже, не работает. Находясь в компоненте A, я попытался обновить переменную в БД на узле, на который подписывается служба. Ожидаемое поведение заключается в том, чтобы эта подписка запускалась и передавала новое значение компоненту A. Например, в приведенном выше примере (объект и соответствующие ему заголовки отображаются на изображении), если я зайду в БД и изменю «РОТОР ВЫСОКОГО ДАВЛЕНИЯ» на «РОТОР БЕЗ ДАВЛЕНИЯ», я ожидаю, что подпискачтобы запустить службу, извлеките это изменение, повторно создав объект в обещании и повторно отправив его компоненту A, который затем повторно отобразит заголовки таблицы. Однако этого не происходит. Я также попытался удалить часть «отписки», чтобы узнать, был ли причиной способ, которым я обработал отписку. Результат был тот же.

Есть мысли?

Спасибо

Ответ №1:

Вы должны использовать наблюдаемые для управления состоянием данных readonly objectPopulatedSubject = new BehaviorSubject({}) , также вам необходимо создать свойство для доступа к наблюдаемому readonly objectPopulated$ = objectPopulatedSubject.asObservable() , а также вам необходимо создать метод для обновления темы и создания нового значения для ваших подписок updateObjectPopulated(data: YourType) { this.objectPopulatedSubject.next(data)} , а в ваших компонентах вы должны подписаться на objectPopulated $ .

Также для управления подпиской вы должны следовать следующим инструкциям: Создайте свойство подписки objectPopulatedSubscription: Subscription; затем в вашем ngOnInit, this.objectPopulatedSubscription = this.yourservice.objectPopulated$.subscribe(data => {//put here your logic}) а также в вашем ngOnDestroy put this.objectPopulatedSubscription.unsubscribe()