From 34fec1dd61499cfbed15af8dfa3a69c2a647044c Mon Sep 17 00:00:00 2001
From: danielwiehl <daniel.wiehl@yahoo.com>
Date: Tue, 6 Dec 2022 11:44:22 +0100
Subject: [PATCH] deps(workbench): update @scion/microfrontend-platform to
 version 1.0.0-rc.11

BREAKING CHANGE: Updating @scion/microfrontend-platform to version 1.0.0-rc.11 introduced a breaking change.

More information on how to migrate can be found in the [changelog](https://github.com/SchweizerischeBundesbahnen/scion-microfrontend-platform/blob/master/docs/site/changelog/changelog.md) of the SCION Microfrontend Platform.

*For Angular applications, we strongly recommend replacing zone-specific decorators for `MessageClient` and `IntentClient` with an `ObservableDecorator`. Otherwise, you may experience performance degradation due to frequent change detection cycles.*

To migrate:
- Remove decorators for `MessageClient` and `IntentClient`, including their registration in the bean manager (e.g., `NgZoneMessageClientDecorator` and `NgZoneIntentClientDecorator`).
- Provide a `NgZoneObservableDecorator` and register it in the bean manager before starting the platform. Note to register it as a bean, not as a decorator.

#### Example of an `ObservableDecorator` for Angular
```ts
export class NgZoneObservableDecorator implements ObservableDecorator {

  constructor(private zone: NgZone) {
  }

  public decorate$<T>(source$: Observable<T>): Observable<T> {
    return new Observable<T>(observer => {
      const insideAngular = NgZone.isInAngularZone();
      const subscription = source$
        .pipe(
          subscribeInside(fn => this.zone.runOutsideAngular(fn)),
          observeInside(fn => insideAngular ? this.zone.run(fn) : this.zone.runOutsideAngular(fn)),
        )
        .subscribe(observer);
      return () => subscription.unsubscribe();
    });
  }
}
```

#### Example of Registering an `ObservableDecorator` in Angular
```ts
const zone: NgZone = ...;

// Register decorator
Beans.register(ObservableDecorator, {useValue: new NgZoneObservableDecorator(zone)});
// Connect to the host
zone.runOutsideAngular(() => WorkbenchClient.connect(...));
```
---
 .../workbench-client/ng-zone-decorators.ts    | 90 ------------------
 .../ng-zone-observable-decorator.ts           | 36 +++++++
 .../workbench-microfrontend-support.ts        | 16 ++--
 ...devtools-capability-interceptor.service.ts |  2 +-
 .../src/environments/environment.ts           |  2 +-
 .../src/environments/environment.vercel.ts    |  2 +-
 package-lock.json                             | 14 +--
 package.json                                  |  2 +-
 projects/scion/workbench-client/package.json  |  2 +-
 projects/scion/workbench/package.json         |  2 +-
 ...rofrontend-platform-initializer.service.ts | 12 +--
 .../initialization/ng-zone-decorators.ts      | 94 -------------------
 .../ng-zone-observable-decorator.ts           | 37 ++++++++
 .../workbench-microfrontend-support.ts        |  5 +-
 14 files changed, 99 insertions(+), 217 deletions(-)
 delete mode 100644 apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-decorators.ts
 create mode 100644 apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-observable-decorator.ts
 delete mode 100644 projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-decorators.ts
 create mode 100644 projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-observable-decorator.ts

diff --git a/apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-decorators.ts b/apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-decorators.ts
deleted file mode 100644
index 638ce94e6..000000000
--- a/apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-decorators.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (c) 2018-2022 Swiss Federal Railways
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-
-import {BeanDecorator} from '@scion/toolkit/bean-manager';
-import {Intent, IntentClient, IntentMessage, IntentSelector, MessageClient, PublishOptions, RequestOptions, TopicMessage} from '@scion/microfrontend-platform';
-import {Injectable, NgZone} from '@angular/core';
-import {MonoTypeOperatorFunction, Observable, pipe, Subscription} from 'rxjs';
-import {observeInside, subscribeInside} from '@scion/toolkit/operators';
-
-/**
- * Synchronizes Observable emissions of the {@link MessageClient} with the Angular zone.
- */
-@Injectable()
-export class NgZoneMessageClientDecorator implements BeanDecorator<MessageClient> {
-
-  constructor(private _zone: NgZone) {
-  }
-
-  public decorate(messageClient: MessageClient): MessageClient {
-    const zone = this._zone;
-    return new class implements MessageClient {
-
-      public publish<T = any>(topic: string, message?: T, options?: PublishOptions): Promise<void> {
-        return messageClient.publish(topic, message, options);
-      }
-
-      public request$<T>(topic: string, request?: any, options?: RequestOptions): Observable<TopicMessage<T>> {
-        return messageClient.request$<T>(topic, request, options).pipe(synchronizeWithAngular(zone));
-      }
-
-      public observe$<T>(topic: string): Observable<TopicMessage<T>> {
-        return messageClient.observe$<T>(topic).pipe(synchronizeWithAngular(zone));
-      }
-
-      public onMessage<IN = any, OUT = any>(topic: string, callback: (message: TopicMessage<IN>) => Observable<OUT> | Promise<OUT> | OUT | void): Subscription {
-        return messageClient.onMessage(topic, callback);
-      }
-
-      public subscriberCount$(topic: string): Observable<number> {
-        return messageClient.subscriberCount$(topic).pipe(synchronizeWithAngular(zone));
-      }
-    };
-  }
-}
-
-/**
- * Synchronizes Observable emissions of the {@link IntentClient} with the Angular zone.
- */
-@Injectable()
-export class NgZoneIntentClientDecorator implements BeanDecorator<IntentClient> {
-
-  constructor(private _zone: NgZone) {
-  }
-
-  public decorate(intentClient: IntentClient): IntentClient {
-    const zone = this._zone;
-    return new class implements IntentClient {
-
-      public publish<T = any>(intent: Intent, body?: T, options?: PublishOptions): Promise<void> {
-        return intentClient.publish(intent, body, options);
-      }
-
-      public request$<T>(intent: Intent, body?: any, options?: RequestOptions): Observable<TopicMessage<T>> {
-        return intentClient.request$<T>(intent, body, options).pipe(synchronizeWithAngular(zone));
-      }
-
-      public observe$<T>(selector?: Intent): Observable<IntentMessage<T>> {
-        return intentClient.observe$<T>(selector).pipe(synchronizeWithAngular(zone));
-      }
-
-      public onIntent<IN = any, OUT = any>(selector: IntentSelector, callback: (intentMessage: IntentMessage<IN>) => Observable<OUT> | Promise<OUT> | OUT | void): Subscription {
-        return intentClient.onIntent(selector, callback);
-      }
-    };
-  }
-}
-
-function synchronizeWithAngular<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
-  return pipe(
-    subscribeInside(continueFn => zone.runOutsideAngular(continueFn)),
-    observeInside(continueFn => zone.run(continueFn)),
-  );
-}
diff --git a/apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-observable-decorator.ts b/apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-observable-decorator.ts
new file mode 100644
index 000000000..e39b50935
--- /dev/null
+++ b/apps/workbench-client-testing-app/src/app/workbench-client/ng-zone-observable-decorator.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2022 Swiss Federal Railways
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+import {Observable} from 'rxjs';
+import {NgZone} from '@angular/core';
+import {observeInside, subscribeInside} from '@scion/toolkit/operators';
+import {ObservableDecorator} from '@scion/microfrontend-platform';
+
+/**
+ * Mirrors the source, but ensures subscription and emission {@link NgZone} to be identical.
+ */
+export class NgZoneObservableDecorator implements ObservableDecorator {
+
+  constructor(private _zone: NgZone) {
+  }
+
+  public decorate$<T>(source$: Observable<T>): Observable<T> {
+    return new Observable<T>(observer => {
+      const insideAngular = NgZone.isInAngularZone();
+      const subscription = source$
+        .pipe(
+          subscribeInside(fn => this._zone.runOutsideAngular(fn)),
+          observeInside(fn => insideAngular ? this._zone.run(fn) : this._zone.runOutsideAngular(fn)),
+        )
+        .subscribe(observer);
+      return () => subscription.unsubscribe();
+    });
+  }
+}
diff --git a/apps/workbench-client-testing-app/src/app/workbench-client/workbench-microfrontend-support.ts b/apps/workbench-client-testing-app/src/app/workbench-client/workbench-microfrontend-support.ts
index 014bf1308..22387cc7f 100644
--- a/apps/workbench-client-testing-app/src/app/workbench-client/workbench-microfrontend-support.ts
+++ b/apps/workbench-client-testing-app/src/app/workbench-client/workbench-microfrontend-support.ts
@@ -8,10 +8,10 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-import {APP_INITIALIZER, NgZone, Provider} from '@angular/core';
-import {APP_IDENTITY, ContextService, FocusMonitor, IntentClient, ManifestService, MessageClient, OutletRouter, PlatformPropertyService, PreferredSizeService} from '@scion/microfrontend-platform';
+import {APP_INITIALIZER, inject, NgZone, Provider} from '@angular/core';
+import {APP_IDENTITY, ContextService, FocusMonitor, IntentClient, ManifestService, MessageClient, ObservableDecorator, OutletRouter, PlatformPropertyService, PreferredSizeService} from '@scion/microfrontend-platform';
 import {WorkbenchClient, WorkbenchMessageBoxService, WorkbenchNotificationService, WorkbenchPopup, WorkbenchPopupService, WorkbenchRouter, WorkbenchView} from '@scion/workbench-client';
-import {NgZoneIntentClientDecorator, NgZoneMessageClientDecorator} from './ng-zone-decorators';
+import {NgZoneObservableDecorator} from './ng-zone-observable-decorator';
 import {Beans} from '@scion/toolkit/bean-manager';
 import {environment} from '../../environments/environment';
 
@@ -27,11 +27,8 @@ export function provideWorkbenchClientInitializer(): Provider[] {
     {
       provide: APP_INITIALIZER,
       useFactory: connectToWorkbenchFn,
-      deps: [NgZoneMessageClientDecorator, NgZoneIntentClientDecorator, NgZone],
       multi: true,
     },
-    NgZoneMessageClientDecorator,
-    NgZoneIntentClientDecorator,
     {provide: APP_IDENTITY, useFactory: () => Beans.get(APP_IDENTITY)},
     {provide: MessageClient, useFactory: () => Beans.get(MessageClient)},
     {provide: IntentClient, useFactory: () => Beans.get(IntentClient)},
@@ -53,11 +50,10 @@ export function provideWorkbenchClientInitializer(): Provider[] {
 /**
  * Connects this app to the workbench in the host app.
  */
-export function connectToWorkbenchFn(ngZoneMessageClientDecorator: NgZoneMessageClientDecorator, ngZoneIntentClientDecorator: NgZoneIntentClientDecorator, zone: NgZone): () => Promise<void> {
+export function connectToWorkbenchFn(): () => Promise<void> {
+  const zone = inject(NgZone);
   return (): Promise<void> => {
-    Beans.registerDecorator(MessageClient, {useValue: ngZoneMessageClientDecorator});
-    Beans.registerDecorator(IntentClient, {useValue: ngZoneIntentClientDecorator});
-    // We connect to the host outside the Angular zone in order to avoid excessive change detection cycles of platform-internal subscriptions to global DOM events.
+    Beans.register(ObservableDecorator, {useValue: new NgZoneObservableDecorator(zone)});
     return zone.runOutsideAngular(() => WorkbenchClient.connect(determineAppSymbolicName()));
   };
 }
diff --git a/apps/workbench-testing-app/src/app/devtools/devtools-capability-interceptor.service.ts b/apps/workbench-testing-app/src/app/devtools/devtools-capability-interceptor.service.ts
index 883cacd5e..bce503ddb 100644
--- a/apps/workbench-testing-app/src/app/devtools/devtools-capability-interceptor.service.ts
+++ b/apps/workbench-testing-app/src/app/devtools/devtools-capability-interceptor.service.ts
@@ -17,7 +17,7 @@ import {Beans} from '@scion/toolkit/bean-manager';
 /**
  * Qualifier of the SCION DevTools view capability.
  */
-const DEVTOOLS_QUALIFIER_MATCHER = new QualifierMatcher({component: 'devtools', vendor: 'scion'}, {evalAsterisk: false, evalOptional: false});
+const DEVTOOLS_QUALIFIER_MATCHER = new QualifierMatcher({component: 'devtools', vendor: 'scion'});
 
 /**
  * Intercepts the DevTools view capability to pin it to the start page.
diff --git a/apps/workbench-testing-app/src/environments/environment.ts b/apps/workbench-testing-app/src/environments/environment.ts
index 8e429712d..df11088f8 100644
--- a/apps/workbench-testing-app/src/environments/environment.ts
+++ b/apps/workbench-testing-app/src/environments/environment.ts
@@ -31,7 +31,7 @@ const microfrontendPlatformConfig: MicrofrontendPlatformConfig = {
   applications: [
     {symbolicName: 'workbench-client-testing-app1', manifestUrl: 'http://localhost:4201/assets/manifest-app1.json', intentionRegisterApiDisabled: false},
     {symbolicName: 'workbench-client-testing-app2', manifestUrl: 'http://localhost:4202/assets/manifest-app2.json', intentionRegisterApiDisabled: false},
-    {symbolicName: 'devtools', manifestUrl: 'https://scion-microfrontend-platform-devtools-v1-0-0-rc-10.vercel.app/assets/manifest.json', intentionCheckDisabled: true, scopeCheckDisabled: true},
+    {symbolicName: 'devtools', manifestUrl: 'https://scion-microfrontend-platform-devtools-v1-0-0-rc-11.vercel.app/assets/manifest.json', intentionCheckDisabled: true, scopeCheckDisabled: true},
   ],
   properties: {
     'workbench-client-testing-app1': {
diff --git a/apps/workbench-testing-app/src/environments/environment.vercel.ts b/apps/workbench-testing-app/src/environments/environment.vercel.ts
index 592dad864..7ff7a777a 100644
--- a/apps/workbench-testing-app/src/environments/environment.vercel.ts
+++ b/apps/workbench-testing-app/src/environments/environment.vercel.ts
@@ -22,7 +22,7 @@ const microfrontendPlatformConfig: MicrofrontendPlatformConfig = {
   applications: [
     {symbolicName: 'workbench-client-testing-app1', manifestUrl: 'https://scion-workbench-client-testing-app1.vercel.app/assets/manifest-app1.json', intentionRegisterApiDisabled: false},
     {symbolicName: 'workbench-client-testing-app2', manifestUrl: 'https://scion-workbench-client-testing-app2.vercel.app/assets/manifest-app2.json', intentionRegisterApiDisabled: false},
-    {symbolicName: 'devtools', manifestUrl: 'https://scion-microfrontend-platform-devtools-v1-0-0-rc-10.vercel.app/assets/manifest.json', intentionCheckDisabled: true, scopeCheckDisabled: true},
+    {symbolicName: 'devtools', manifestUrl: 'https://scion-microfrontend-platform-devtools-v1-0-0-rc-11.vercel.app/assets/manifest.json', intentionCheckDisabled: true, scopeCheckDisabled: true},
   ],
   properties: {
     'workbench-client-testing-app1': {
diff --git a/package-lock.json b/package-lock.json
index 7c3ffb6c1..1bbe7f9e0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,7 @@
         "@angular/router": "14.2.3",
         "@scion/components": "14.0.2",
         "@scion/components.internal": "14.0.1",
-        "@scion/microfrontend-platform": "1.0.0-rc.10",
+        "@scion/microfrontend-platform": "1.0.0-rc.11",
         "@scion/toolkit": "1.3.1",
         "rxjs": "7.5.7",
         "tslib": "2.4.0",
@@ -3305,9 +3305,9 @@
       }
     },
     "node_modules/@scion/microfrontend-platform": {
-      "version": "1.0.0-rc.10",
-      "resolved": "https://registry.npmjs.org/@scion/microfrontend-platform/-/microfrontend-platform-1.0.0-rc.10.tgz",
-      "integrity": "sha512-+dMddXkRVhSZBBM9faQsCTxKsG1W8E35+h4EWcEilin5BYKoWeasENQXqf2ZHZcFc+Xlt4IDF5Iig+47vkvX1g==",
+      "version": "1.0.0-rc.11",
+      "resolved": "https://registry.npmjs.org/@scion/microfrontend-platform/-/microfrontend-platform-1.0.0-rc.11.tgz",
+      "integrity": "sha512-hLVlBdVtsl5daTo+3yhKWwdOaDWFCrbAN/KUyRoMaaT4bVV2cxgQoWW2yf3zijBxZ9WsSnwMQPe9ZUzb4cX+yw==",
       "dependencies": {
         "tslib": "^2.3.0"
       },
@@ -20015,9 +20015,9 @@
       }
     },
     "@scion/microfrontend-platform": {
-      "version": "1.0.0-rc.10",
-      "resolved": "https://registry.npmjs.org/@scion/microfrontend-platform/-/microfrontend-platform-1.0.0-rc.10.tgz",
-      "integrity": "sha512-+dMddXkRVhSZBBM9faQsCTxKsG1W8E35+h4EWcEilin5BYKoWeasENQXqf2ZHZcFc+Xlt4IDF5Iig+47vkvX1g==",
+      "version": "1.0.0-rc.11",
+      "resolved": "https://registry.npmjs.org/@scion/microfrontend-platform/-/microfrontend-platform-1.0.0-rc.11.tgz",
+      "integrity": "sha512-hLVlBdVtsl5daTo+3yhKWwdOaDWFCrbAN/KUyRoMaaT4bVV2cxgQoWW2yf3zijBxZ9WsSnwMQPe9ZUzb4cX+yw==",
       "requires": {
         "tslib": "^2.3.0"
       }
diff --git a/package.json b/package.json
index 62856a67a..9453a5f6e 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
     "@angular/router": "14.2.3",
     "@scion/components": "14.0.2",
     "@scion/components.internal": "14.0.1",
-    "@scion/microfrontend-platform": "1.0.0-rc.10",
+    "@scion/microfrontend-platform": "1.0.0-rc.11",
     "@scion/toolkit": "1.3.1",
     "rxjs": "7.5.7",
     "tslib": "2.4.0",
diff --git a/projects/scion/workbench-client/package.json b/projects/scion/workbench-client/package.json
index 82d5a704f..591237317 100644
--- a/projects/scion/workbench-client/package.json
+++ b/projects/scion/workbench-client/package.json
@@ -20,7 +20,7 @@
   "peerDependencies": {
     "rxjs": "^7.5.0",
     "@scion/toolkit": "^1.0.0-0",
-    "@scion/microfrontend-platform": "^1.0.0-0"
+    "@scion/microfrontend-platform": "^1.0.0-rc.11"
   },
   "keywords": [
     "scion",
diff --git a/projects/scion/workbench/package.json b/projects/scion/workbench/package.json
index de1cfbd83..bd188c2d6 100644
--- a/projects/scion/workbench/package.json
+++ b/projects/scion/workbench/package.json
@@ -31,7 +31,7 @@
     "@angular/router": "^14.0.0",
     "@scion/components": "^14.0.0",
     "@scion/toolkit": "^1.3.0",
-    "@scion/microfrontend-platform": "^1.0.0-rc.9",
+    "@scion/microfrontend-platform": "^1.0.0-rc.11",
     "@scion/workbench-client": "^1.0.0-0",
     "rxjs": "^7.5.0"
   },
diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts b/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts
index cae3d1605..cbac74742 100644
--- a/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts
+++ b/projects/scion/workbench/src/lib/microfrontend-platform/initialization/microfrontend-platform-initializer.service.ts
@@ -9,10 +9,10 @@
  */
 
 import {Injectable, Injector, NgZone, OnDestroy} from '@angular/core';
-import {CapabilityInterceptor, HostManifestInterceptor, IntentClient, IntentInterceptor, MessageClient, MicrofrontendPlatform, MicrofrontendPlatformConfig, Runlevel} from '@scion/microfrontend-platform';
+import {CapabilityInterceptor, HostManifestInterceptor, IntentInterceptor, MicrofrontendPlatform, MicrofrontendPlatformConfig, ObservableDecorator, Runlevel} from '@scion/microfrontend-platform';
 import {Beans} from '@scion/toolkit/bean-manager';
 import {Logger, LoggerNames} from '../../logging';
-import {NgZoneIntentClientDecorator, NgZoneMessageClientDecorator} from './ng-zone-decorators';
+import {NgZoneObservableDecorator} from './ng-zone-observable-decorator';
 import {MICROFRONTEND_PLATFORM_POST_STARTUP, MICROFRONTEND_PLATFORM_PRE_STARTUP, runWorkbenchInitializers, WorkbenchInitializer} from '../../startup/workbench-initializer';
 import {MicrofrontendPlatformConfigLoader} from '../microfrontend-platform-config-loader';
 import {MicrofrontendViewIntentInterceptor} from '../routing/microfrontend-view-intent-interceptor.service';
@@ -29,8 +29,7 @@ export class MicrofrontendPlatformInitializer implements WorkbenchInitializer, O
 
   constructor(private _microfrontendPlatformConfigLoader: MicrofrontendPlatformConfigLoader,
               private _hostManifestInterceptor: WorkbenchHostManifestInterceptor,
-              private _ngZoneMessageClientDecorator: NgZoneMessageClientDecorator,
-              private _ngZoneIntentClientDecorator: NgZoneIntentClientDecorator,
+              private _ngZoneObservableDecorator: NgZoneObservableDecorator,
               private _microfrontendViewIntentInterceptor: MicrofrontendViewIntentInterceptor,
               private _microfrontendPopupIntentInterceptor: MicrofrontendPopupIntentInterceptor,
               private _microfrontendViewCapabilityInterceptor: MicrofrontendViewCapabilityInterceptor,
@@ -61,9 +60,8 @@ export class MicrofrontendPlatformInitializer implements WorkbenchInitializer, O
     // Register host manifest interceptor for the workbench to register workbench-specific intentions and capabilities.
     Beans.register(HostManifestInterceptor, {useValue: this._hostManifestInterceptor, multi: true});
 
-    // Synchronize emissions of messaging Observables with the Angular zone.
-    Beans.registerDecorator(MessageClient, {useValue: this._ngZoneMessageClientDecorator});
-    Beans.registerDecorator(IntentClient, {useValue: this._ngZoneIntentClientDecorator});
+    // Synchronize emissions of Observables exposed by the SCION Microfrontend Platform with the Angular zone.
+    Beans.register(ObservableDecorator, {useValue: this._ngZoneObservableDecorator});
 
     // Register view intent interceptor to translate view intents into workbench router commands.
     Beans.register(IntentInterceptor, {useValue: this._microfrontendViewIntentInterceptor, multi: true});
diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-decorators.ts b/projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-decorators.ts
deleted file mode 100644
index d74272b3f..000000000
--- a/projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-decorators.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (c) 2018-2022 Swiss Federal Railways
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-
-import {BeanDecorator} from '@scion/toolkit/bean-manager';
-import {Intent, IntentClient, IntentMessage, IntentSelector, MessageClient, PublishOptions, RequestOptions, TopicMessage} from '@scion/microfrontend-platform';
-import {Injectable, NgZone} from '@angular/core';
-import {MonoTypeOperatorFunction, Observable, pipe, Subscription} from 'rxjs';
-import {observeInside, subscribeInside} from '@scion/toolkit/operators';
-
-/**
- * Synchronizes Observable emissions of the {@link MessageClient} with the Angular zone.
- *
- * Refer to https://scion-microfrontend-platform-developer-guide.vercel.app/#chapter:angular_integration_guide for more information.
- */
-@Injectable()
-export class NgZoneMessageClientDecorator implements BeanDecorator<MessageClient> {
-
-  constructor(private _zone: NgZone) {
-  }
-
-  public decorate(messageClient: MessageClient): MessageClient {
-    const zone = this._zone;
-    return new class implements MessageClient {
-
-      public publish<T = any>(topic: string, message?: T, options?: PublishOptions): Promise<void> {
-        return messageClient.publish(topic, message, options);
-      }
-
-      public request$<T>(topic: string, request?: any, options?: RequestOptions): Observable<TopicMessage<T>> {
-        return messageClient.request$<T>(topic, request, options).pipe(synchronizeWithAngular(zone));
-      }
-
-      public observe$<T>(topic: string): Observable<TopicMessage<T>> {
-        return messageClient.observe$<T>(topic).pipe(synchronizeWithAngular(zone));
-      }
-
-      public onMessage<IN = any, OUT = any>(topic: string, callback: (message: TopicMessage<IN>) => Observable<OUT> | Promise<OUT> | OUT | void): Subscription {
-        return messageClient.onMessage(topic, callback);
-      }
-
-      public subscriberCount$(topic: string): Observable<number> {
-        return messageClient.subscriberCount$(topic).pipe(synchronizeWithAngular(zone));
-      }
-    };
-  }
-}
-
-/**
- * Synchronizes Observable emissions of the {@link IntentClient} with the Angular zone.
- *
- * Refer to https://scion-microfrontend-platform-developer-guide.vercel.app/#chapter:angular_integration_guide for more information.
- */
-@Injectable()
-export class NgZoneIntentClientDecorator implements BeanDecorator<IntentClient> {
-
-  constructor(private _zone: NgZone) {
-  }
-
-  public decorate(intentClient: IntentClient): IntentClient {
-    const zone = this._zone;
-    return new class implements IntentClient {
-
-      public publish<T = any>(intent: Intent, body?: T, options?: PublishOptions): Promise<void> {
-        return intentClient.publish(intent, body, options);
-      }
-
-      public request$<T>(intent: Intent, body?: any, options?: RequestOptions): Observable<TopicMessage<T>> {
-        return intentClient.request$<T>(intent, body, options).pipe(synchronizeWithAngular(zone));
-      }
-
-      public observe$<T>(selector?: Intent): Observable<IntentMessage<T>> {
-        return intentClient.observe$<T>(selector).pipe(synchronizeWithAngular(zone));
-      }
-
-      public onIntent<IN = any, OUT = any>(selector: IntentSelector, callback: (intentMessage: IntentMessage<IN>) => Observable<OUT> | Promise<OUT> | OUT | void): Subscription {
-        return intentClient.onIntent(selector, callback);
-      }
-    };
-  }
-}
-
-function synchronizeWithAngular<T>(zone: NgZone): MonoTypeOperatorFunction<T> {
-  return pipe(
-    subscribeInside(continueFn => zone.runOutsideAngular(continueFn)),
-    observeInside(continueFn => zone.run(continueFn)),
-  );
-}
diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-observable-decorator.ts b/projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-observable-decorator.ts
new file mode 100644
index 000000000..c85445256
--- /dev/null
+++ b/projects/scion/workbench/src/lib/microfrontend-platform/initialization/ng-zone-observable-decorator.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018-2022 Swiss Federal Railways
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+import {Observable} from 'rxjs';
+import {Injectable, NgZone} from '@angular/core';
+import {observeInside, subscribeInside} from '@scion/toolkit/operators';
+import {ObservableDecorator} from '@scion/microfrontend-platform';
+
+/**
+ * Mirrors the source, but ensures subscription and emission {@link NgZone} to be identical.
+ */
+@Injectable()
+export class NgZoneObservableDecorator implements ObservableDecorator {
+
+  constructor(private _zone: NgZone) {
+  }
+
+  public decorate$<T>(source$: Observable<T>): Observable<T> {
+    return new Observable<T>(observer => {
+      const insideAngular = NgZone.isInAngularZone();
+      const subscription = source$
+        .pipe(
+          subscribeInside(fn => this._zone.runOutsideAngular(fn)),
+          observeInside(fn => insideAngular ? this._zone.run(fn) : this._zone.runOutsideAngular(fn)),
+        )
+        .subscribe(observer);
+      return () => subscription.unsubscribe();
+    });
+  }
+}
diff --git a/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts b/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts
index 600eb9bc0..776e1b2a6 100644
--- a/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts
+++ b/projects/scion/workbench/src/lib/microfrontend-platform/workbench-microfrontend-support.ts
@@ -15,7 +15,7 @@ import {APP_IDENTITY, IntentClient, ManifestService, MessageClient, Microfronten
 import {MICROFRONTEND_PLATFORM_POST_STARTUP, WORKBENCH_STARTUP} from '../startup/workbench-initializer';
 import {Beans} from '@scion/toolkit/bean-manager';
 import {WorkbenchMessageBoxService, WorkbenchNotificationService, WorkbenchPopupService, WorkbenchRouter, ɵMicrofrontendRouteParams} from '@scion/workbench-client';
-import {NgZoneIntentClientDecorator, NgZoneMessageClientDecorator} from './initialization/ng-zone-decorators';
+import {NgZoneObservableDecorator} from './initialization/ng-zone-observable-decorator';
 import {WorkbenchModuleConfig} from '../workbench-module-config';
 import {MicrofrontendViewCommandHandler} from './microfrontend-view/microfrontend-view-command-handler.service';
 import {MicrofrontendMessageBoxIntentHandler} from './microfrontend-message-box/microfrontend-message-box-intent-handler.service';
@@ -71,8 +71,7 @@ export function provideWorkbenchMicrofrontendSupport(workbenchModuleConfig: Work
       WorkbenchPopupService,
       WorkbenchMessageBoxService,
       WorkbenchNotificationService,
-      NgZoneMessageClientDecorator,
-      NgZoneIntentClientDecorator,
+      NgZoneObservableDecorator,
       WorkbenchHostManifestInterceptor,
       provideMicrofrontendRoutes(),
       provideMicrofrontendPlatformBeans(),