我正在使用 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 上。
我在 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
。
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();
}
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();
}
}
import { Subscription } from 'rxjs';
private yourSubscription!: Subscription;
ngOnInit() {
this.yourSubscription = this.someService.subscribe(() => {})
}
ngOnDestroy() {
this.yourSubscription.unsubscribe();
}
//这对我有用,我希望对你有帮助!
<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
是组件类中的可观察值
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) => {
}
);