Angular 模板中可观察对象上的 ObjectUnsubscribedErrorImpl

问题描述 投票:0回答:6

我正在使用 Angular 11,并且我正在使用 async 管道访问组件模板中的可观察对象。

首次加载路线,一切正常。没有错误。当我离开该页面并返回时,出现以下错误:

组件模板:

组件

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs';

import { FullMapViewService } from '../services/full-map-view.service';
import { RISLayerConfigResponse } from '@RM/interfaces';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'RM-full-map-view',
  templateUrl: './full-map-view.component.html',
  styleUrls: ['./full-map-view.component.scss']
})
export class FullMapViewComponent implements OnInit, OnDestroy {
  layers$: Observable<RISLayerConfigResponse>;
  destroyed$: Subject<boolean> = new Subject();
  constructor(private fullMapViewService: FullMapViewService) {}

  ngOnInit(): void {
    this.fullMapViewService.setParamsRequiredForRIS();
    this.fullMapViewService.initializeRISLayerCreationService();
    this.layers$ = this.fullMapViewService
      .getLayersForAllProjects()
      .pipe(takeUntil(this.destroyed$));
  }

  ngOnDestroy() {
    this.destroyed$.next(true);
  }
}

完整地图视图.service.ts

import { DEPLOYMENT_PATH, SET_FROM_SERVER } from '@XYZ/RIS';
import {
  DataSet,
  DatasetsAndLayerConfig,
  RISLayerConfigResponse,
  RISLayerSettingsWithKind,
  Layer,
  LayerConfig,
  UpdateViewVCS
} from '@XYZ/interfaces';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable, Subscription } from 'rxjs';

import { API_PATHS } from '../../api-paths';
import { BaseAPIService } from '@XYZ/core';
import { ClauseGenerationUtility } from '../../utils/clause-generation/clause-generation.util';
import { RISLayerCreationService } from '@ABC-innersource/RIS-canvas';
import { LAYER_COLOR_PALLET } from '../../services/services.constant';
import { map } from 'rxjs/operators';

@Injectable()
export class FullMapViewService implements OnDestroy {
  layersMappingConfiguration: {};
  layers: LayerConfig;
  private clauseGenerator = new ClauseGenerationUtility();
  private addUpdateVCSForKindSubscriptions: Subscription[];
  private initializeRISLayerCreationServiceSubscription: Subscription;
  private deploymentUrl: string;
  private appKey: string;
  private ABCDataPartitionId: string;
  private sToken: string;

  constructor(
    private baseAPIService: BaseAPIService,
    private layerCreationService: RISLayerCreationService
  ) {}

  // eslint-disable-next-line max-lines-per-function
  getLayersForAllProjects(): Observable<RISLayerConfigResponse> {
    return this.baseAPIService
      .get(API_PATHS.LAYERS.GET_LAYERS + '/projects/all')
      .pipe(
        map((res: DatasetsAndLayerConfig) => {
          return res;
        }),
        // eslint-disable-next-line max-lines-per-function
        map((datasetsAndLayerConfig: DatasetsAndLayerConfig) => {
          const datasets = [...datasetsAndLayerConfig.datasets];
          const notConfiguredKinds = [
            ...datasetsAndLayerConfig.layerConfig.notConfiguredKinds
          ];
          const notConfiguredKindsLayers = this.getNonConfiguredKindsLayers(
            notConfiguredKinds
          );
          const layers = this.combineLayersAndNotConfiguredKindsLayers(
            datasetsAndLayerConfig.layerConfig.layerConfig,
            notConfiguredKindsLayers
          );
          const kindsLayersHashmap = this.getKindsLayersHashmap(layers);
          const layersByDatasets = datasets
            .map((dataset: DataSet) => {
              return {
                ...this.updateLayersWithDatasetNameAndClauses(
                  kindsLayersHashmap,
                  dataset
                )
              };
            })
            .filter((layer) => {
              return Object.keys(layer).length !== 0;
            })
            .map((layer, index) => {
              return {
                ...this.assignColourToLayer(layer, index)
              };
            });
          return {
            layerConfig: layersByDatasets,
            notConfiguredKinds: []
          };
        })
      );
  }

  setParamsRequiredForRIS(): void {
    this.sToken = SET_FROM_SERVER;
    this.deploymentUrl = DEPLOYMENT_PATH;
    this.appKey = SET_FROM_SERVER;
    this.ABCDataPartitionId = SET_FROM_SERVER;
  }

  initializeRISLayerCreationService(): void {
    this.initializeRISLayerCreationServiceSubscription = this.layerCreationService
      .initialize(
        this.sToken,
        this.deploymentUrl,
        this.appKey,
        this.ABCDataPartitionId
      )
      .subscribe();
  }

  ngOnDestroy(): void {
    this.initializeRISLayerCreationServiceSubscription.unsubscribe();
    this.addUpdateVCSForKindSubscriptions.forEach(
      (subscription: Subscription) => {
        subscription.unsubscribe();
      }
    );
  }

  private updateLayersWithDatasetNameAndClauses(
    kindsLayersHashmap: Map<string, RISLayerSettingsWithKind>,
    dataset: DataSet
  ): RISLayerSettingsWithKind {
    const currentDataset = { ...dataset };
    const datasetKind = this.generateKindFromDataset(currentDataset);
    const layer = kindsLayersHashmap.get(datasetKind);
    const queryRef = this.getFormattedQuery(
      currentDataset.dataSetDefinition.queryDefinition.queryRef
    );
    const clause = this.clauseGenerator.generateClause(queryRef);

    if (!layer) {
      return undefined;
    }

    layer.name = currentDataset.name;
    layer.tableInfo.where = clause;
    return JSON.parse(JSON.stringify(layer));
  }

  private generateKindFromDataset(dataset: DataSet): string {
    const currentDataset = { ...dataset };
    const datasetQueryDefinition =
      currentDataset.dataSetDefinition.queryDefinition;
    return `${datasetQueryDefinition.authority}:${datasetQueryDefinition.source}:${datasetQueryDefinition.entity}:${datasetQueryDefinition.version}`;
  }

  private getKindsLayersHashmap(
    layers: RISLayerSettingsWithKind[]
  ): Map<string, RISLayerSettingsWithKind> {
    const kindsLayersHashmap = new Map();
    const allLayers = [...layers];
    allLayers.forEach((layer: RISLayerSettingsWithKind) => {
      kindsLayersHashmap.set(layer.kind, layer);
    });
    return kindsLayersHashmap;
  }

  private getNonConfiguredKindsLayers(
    kinds: string[]
  ): RISLayerSettingsWithKind[] {
    const notConfiguredKindsLayers: RISLayerSettingsWithKind[] = [];
    kinds.forEach((kind) => {
      const layer: RISLayerSettingsWithKind[] = this.layerCreationService.getLayerInfoByKindName(
        kind
      ) as RISLayerSettingsWithKind[];
      if (layer.length > 0) {
        layer[0].kind = kind;
        notConfiguredKindsLayers.push(layer[0]);
        this.addUpdateRISLayerInVCS({ kind: kind, configuration: layer[0] });
      }
    });
    return notConfiguredKindsLayers;
  }

  private addUpdateRISLayerInVCS(layer: Layer): void {
    const currentLayer = { ...layer };
    const updateViewPayload: UpdateViewVCS = {
      control: 'RIS',
      definition: [{ ...currentLayer.configuration }]
    };
    this.addUpdateVCSForKind(currentLayer.kind, updateViewPayload);
  }

  private addUpdateVCSForKind(kind: string, payload: UpdateViewVCS): void {
    const subscription = this.baseAPIService
      .post(
        `${API_PATHS.CONFIG.VIEW.UPDATE_RIS_VIEW_CONFIG}`.replace(
          '${kind}',
          kind
        ),
        payload
      )
      .subscribe();
    this.addUpdateVCSForKindSubscriptions.push(subscription);
  }

  private combineLayersAndNotConfiguredKindsLayers(
    layers: RISLayerSettingsWithKind[],
    notConfiguredKindsLayers: RISLayerSettingsWithKind[]
  ): RISLayerSettingsWithKind[] {
    const allLayers = [...layers];
    const allNotConfiguredKindsLayers = [...notConfiguredKindsLayers];
    return [...allLayers, ...allNotConfiguredKindsLayers];
  }

  private getFormattedQuery(query: string): string {
    let formattedQuery = '';
    if (
      this.clauseGenerator.hasAndOperator(query) ||
      this.clauseGenerator.hasOrOperator(query)
    ) {
      formattedQuery = this.clauseGenerator.isWrappedWithRoundBrackets(query)
        ? query
        : `(${query})`;
      return formattedQuery;
    }
    return formattedQuery;
  }

  private assignColourToLayer(
    layer: RISLayerSettingsWithKind,
    index: number
  ): RISLayerSettingsWithKind {
    const colors = LAYER_COLOR_PALLET;
    const currentLayer = JSON.parse(JSON.stringify(layer));
    currentLayer.style.rules[0].style.fillColor = colors[index];
    currentLayer.style.rules[0].style.borderColor = '#000';
    return currentLayer;
  }
}

例如路线 B 是我包含可观察到的组件 A ---> B 可观察到的负载非常好。 B ----> A 并再次 A ----> B 可观察到的抛出以下错误。

ObjectUnsubscribedErrorImpl {message: "object unsubscribed", name: "ObjectUnsubscribedError"}
message: "object unsubscribed"
name: "ObjectUnsubscribedError"

完整堆栈跟踪快照如下所示:

    core.js:6162 ERROR ObjectUnsubscribedErrorImpl {message: "object unsubscribed", name: "ObjectUnsubscribedError"}message: "object unsubscribed"name: "ObjectUnsubscribedError"__proto__: Error

defaultErrorLogger  @   core.js:6162
handleError @   core.js:6210
(anonymous) @   core.js:29503
invoke  @   zone-evergreen.js:364
run @   zone-evergreen.js:123
runOutsideAngular   @   core.js:28439
tick    @   core.js:29503
(anonymous) @   core.js:29372
invoke  @   zone-evergreen.js:364
onInvoke    @   core.js:28510
invoke  @   zone-evergreen.js:363
run @   zone-evergreen.js:123
run @   core.js:28394
next    @   core.js:29371
schedulerFn @   core.js:25848
__tryOrUnsub    @   Subscriber.js:183
next    @   Subscriber.js:122
_next   @   Subscriber.js:72
next    @   Subscriber.js:49
next    @   Subject.js:39
emit    @   core.js:25838
checkStable @   core.js:28447
onLeave @   core.js:28560
onInvokeTask    @   core.js:28504
invokeTask  @   zone-evergreen.js:398
runTask @   zone-evergreen.js:167
invokeTask  @   zone-evergreen.js:480
invokeTask  @   zone-evergreen.js:1621
globalZoneAwareCallback @   zone-evergreen.js:1658
load (async)        
customScheduleGlobal    @   zone-evergreen.js:1773
scheduleTask    @   zone-evergreen.js:385
onScheduleTask  @   zone-evergreen.js:272
scheduleTask    @   zone-evergreen.js:378
scheduleTask    @   zone-evergreen.js:210
scheduleEventTask   @   zone-evergreen.js:236
(anonymous) @   zone-evergreen.js:1928
(anonymous) @   http.js:1805
_trySubscribe   @   Observable.js:42
subscribe   @   Observable.js:28
call    @   catchError.js:14
subscribe   @   Observable.js:23
call    @   catchError.js:14
subscribe   @   Observable.js:23
innerSubscribe  @   innerSubscribe.js:67
_innerSub   @   mergeMap.js:57
_tryNext    @   mergeMap.js:51
_next   @   mergeMap.js:34
next    @   Subscriber.js:49
(anonymous) @   subscribeToArray.js:3
_trySubscribe   @   Observable.js:42
subscribe   @   Observable.js:28
call    @   mergeMap.js:19
subscribe   @   Observable.js:23
call    @   filter.js:13
subscribe   @   Observable.js:23
call    @   map.js:16
subscribe   @   Observable.js:23
call    @   map.js:16
subscribe   @   Observable.js:23
call    @   map.js:16
subscribe   @   Observable.js:23
createSubscription  @   common.js:4224
_subscribe  @   common.js:4305
transform   @   common.js:4292
ɵɵpipeBind1 @   core.js:25718
FullMapViewComponent_Template   @   full-map-view.component.html:2
executeTemplate @   core.js:9549
refreshView @   core.js:9418
refreshComponent    @   core.js:10584
refreshChildComponents  @   core.js:9215
refreshView @   core.js:9468
refreshEmbeddedViews    @   core.js:10538
refreshView @   core.js:9442
refreshEmbeddedViews    @   core.js:10538
refreshView @   core.js:9442
refreshComponent    @   core.js:10584
refreshChildComponents  @   core.js:9215
refreshView @   core.js:9468
renderComponentOrTemplate   @   core.js:9532
tickRootContext @   core.js:10758
detectChangesInRootView @   core.js:10783
detectChanges   @   core.js:22751
tick    @   core.js:29491
(anonymous) @   core.js:29372
invoke  @   zone-evergreen.js:364
onInvoke    @   core.js:28510
invoke  @   zone-evergreen.js:363
run @   zone-evergreen.js:123
run @   core.js:28394
next    @   core.js:29371
schedulerFn @   core.js:25848
__tryOrUnsub    @   Subscriber.js:183
next    @   Subscriber.js:122
_next   @   Subscriber.js:72
next    @   Subscriber.js:49
next    @   Subject.js:39
emit    @   core.js:25838
checkStable @   core.js:28447
onHasTask   @   core.js:28527
hasTask @   zone-evergreen.js:419
_updateTaskCount    @   zone-evergreen.js:440
_updateTaskCount    @   zone-evergreen.js:263
runTask @   zone-evergreen.js:184
drainMicroTaskQueue @   zone-evergreen.js:569
invokeTask  @   zone-evergreen.js:484
invokeTask  @   zone-evergreen.js:1621
globalZoneAwareCallback @   zone-evergreen.js:1647

如果您看到,

FullMapViewComponent_Template  @   full-map-view.component.html:2
提到了模板上可观察值的问题。

我不确定如何处理这个问题。此模板位于路线 B 上。

javascript angular rxjs reactive-programming rxjs-observables
6个回答
19
投票

我在 rxjs github 项目中搜索了 ObjectUnsubscribedError 抛出的位置,并尝试在实际抛出此错误时获取见解。我可以在“this”测试中找到最好的见解。我尝试总结一下我如何理解这种行为:

当您直接取消订阅某个主题时,该主题将被关闭。每当您尝试重新订阅它时,它都会抛出 ObjectUnsubscribedError。

被知晓意味着您很可能保留您的主题(为您服务),尽管您的组件被丢弃并取消订阅。当您重新路由到组件时,它会尝试再次订阅,然后抛出错误。我有一个从上面的链接测试中获取的最小可重现示例:

const { Subject } = rxjs; const subject = new Subject(); subject.subscribe() subject.next("foo") subject.unsubscribe() try { subject.subscribe() } catch (e) { console.error("the error: ", e) }
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>

现在我们知道如何抛出错误,可能的解决方案可能是仅在组件中的

unsubscribe

上使用

subscription
,然后是新组件特定主题中的值。

伪示例代码:

// Your subscription on wich you can unsubscribe private copySubjectSubscription; // This subject is used in your component with the async pipe public readonly copySubject = new Subject(); constructor(serviceWithSubject) { this.copySubjectSubscription = serviceWithSubject.subject.subscribe(e => copySubject.next()) } ngOnDestroy() { this.copySubjectSubscription.unsubscribe(); }

由于我不喜欢你的代码,而且时间也有限,你可能会找到一个更优雅的解决方案来复制主题及其值,而无需直接取消订阅。

我个人经常使用 rxjs,但从来没有遇到过这个问题。也许我总是可以通过不同的设计方法来避免这种行为,因为我总是尝试在组件中创建和销毁

Observables

    


2
投票

import { Component, OnDestroy, OnInit } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { FullMapViewService } from '../services/full-map-view.service'; import { RISLayerConfigResponse } from '@RM/interfaces'; import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'RM-full-map-view', templateUrl: './full-map-view.component.html', styleUrls: ['./full-map-view.component.scss'] }) export class FullMapViewComponent implements OnInit, OnDestroy { layers$: Observable<RISLayerConfigResponse>; constructor(private fullMapViewService: FullMapViewService) {} ngOnInit(): void { this.fullMapViewService.setParamsRequiredForRIS(); this.fullMapViewService.initializeRISLayerCreationService(); this.layers$ = this.fullMapViewService .getLayersForAllProjects(); } }

此外,如果您想使用 takeUntil 方法,则在 ngOnDestry 函数中,发出值后,您需要将该主题标记为完成。

ngOnDestroy() { this.destroy$.next(true); this.destroy$.complete(); }



0
投票

export class FullMapViewComponent implements OnInit, OnDestroy { layers$: Observable<RISLayerConfigResponse>; destroyed$: Subject<boolean> = new Subject(); subscriptionsObserver: Subcription; constructor(private fullMapViewService: FullMapViewService) { this.subscriptionsObserver = new Subscription(); } ngOnInit(): void { this.fullMapViewService.setParamsRequiredForRIS(); this.fullMapViewService.initializeRISLayerCreationService(); this.layers$ = this.fullMapViewService .getLayersForAllProjects(); this.subcriptionsObserver.add(this.layers$); } ngOnDestroy() { // this.destroyed$.next(true); this.subscriptionsObserver.unsubscribe(); } }



0
投票

import { Subscription } from 'rxjs'; private yourSubscription!: Subscription; ngOnInit() { this.yourSubscription = this.someService.subscribe(() => {}) } ngOnDestroy() { this.yourSubscription.unsubscribe(); }

//这对我有用,我希望对你有帮助!


0
投票
<ng-template>

中进行异步订阅时,我收到了此错误。当我通过上下文传递异步订阅后,它工作得很好。希望这对某人有帮助。

之前

<ng-container *ngTemplateOutlet="modal"></ng-container> <ng-template #modal let-data> <!-- Had the error when using directly here --> {{(obs | async)?.data}} <ng-template>

之后

<ng-container *ngTemplateOutlet="modal; context:{$implicit: (obs | async)?.data}"></ng-container> <ng-template #modal let-data> <!-- fine to use data here, no error --> {{data}} <ng-template>

obs

是组件类中的可观察值

    


0
投票

import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; ... private _unsubscribeSignal$: Subject<void> = new Subject(); ... ngOnDestroy() { this._unsubscribeSignal$.next(); this._unsubscribeSignal$.unsubscribe(); } ... // Somewhere new Observable() // Here your observable .pipe(takeUntil(this._unsubscribeSignal$.asObservable())) .subscribe( (result) => { }, (err) => { } );

注意编辑:takeUntil 正在完成这项工作。

2024 年 3 月更新

目前从 Angular 16 开始,方法变得更加平滑

import { DestroyRef, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; ... private _destroyRef = inject(DestroyRef); ... // Somewhere new Observable() // Here your observable .pipe(takeUntilDestroyed(this._destroyRef)) .subscribe( (result) => { }, (err) => { } );

© www.soinside.com 2019 - 2024. All rights reserved.