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

标签 javascript angular rxjs reactive-programming rxjs-observables

我正在使用 Angular 11,并且我正在使用 访问我的组件模板中的 observable异步 管道。
路线的第一次加载,一切正常。没有错误。当我离开页面并返回时,出现以下错误:
组件模板:
*ngIf="(layers$ | async) 作为层"
[层]="layers.layerConfig"
showLayersPanel="true"id="RIS-map">

组件

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);
  }
}
全 map -view.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 observable 再次抛出错误。
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提到模板上的 observable 存在问题。
我不确定如何处理这个问题。此模板位于 Route B 上。

最佳答案

我搜索了 ObjectUnsubscribedError 的地方被抛出 rxjs github 项目并试图在实际抛出此错误时获得洞察力。我可以在 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在组件中。

关于javascript - Angular 模板中可观察对象上的 ObjectUnsubscribedErrorImpl,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67235013/

相关文章:

javascript - 我无法从 draft-js 获得 html 输出?

javascript - Backbone Js 分部分加载应用程序

angular - Visual Studio Code - Angular 2 自动导入扩展

json - 使用通用 JSON 对象作为请求体

redux - 从 Redux Observable 返回一个 Promise

javascript - 使用 css 样式打印 div

javascript - (TS)HTMLInputElement 类型上不存在属性行

javascript - 如何为新的 Angular 项目设置 Tailwind?

javascript - Rxjs 阻止订阅中的订阅

Angular 8 订阅拦截器中的可观察对象