From e36b988aae9da95755e2d8f7450e7e1b3b897503 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Thu, 27 Oct 2022 08:39:23 +0000 Subject: [PATCH] fix(platform-server): call `onSerialize` when state is empty Commit https://github.com/angular/angular/commit/a0b2d364156eed0d33831c37b00ea5c58ff4bbec#diff-3975e0ee5aa3e06ecbcd76f5fa5134612f7fd2e6802ca7d370973bd410aab55cR25-R31 changed the serialization phase logic so that when the state is empty the script tag is not added to the document. As a side effect, this caused the `toJson` not called which caused the `onSerialize` callbacks also not to be called. These callbacks are used to provide the value for a key when `toJson` is called. Example: https://github.com/ngrx/platform/issues/101#issuecomment-351998548 Closes #47172 --- .../platform-server/src/transfer_state.ts | 7 +- .../platform-server/test/integration_spec.ts | 105 +----------------- .../test/transfer_state_spec.ts | 86 ++++++++++++++ .../platform-server/testing/src/server.ts | 2 +- 4 files changed, 96 insertions(+), 104 deletions(-) create mode 100644 packages/platform-server/test/transfer_state_spec.ts diff --git a/packages/platform-server/src/transfer_state.ts b/packages/platform-server/src/transfer_state.ts index 26e8b3fed67a2..8796b02e78778 100644 --- a/packages/platform-server/src/transfer_state.ts +++ b/packages/platform-server/src/transfer_state.ts @@ -21,15 +21,20 @@ export const TRANSFER_STATE_SERIALIZATION_PROVIDERS: Provider[] = [{ function serializeTransferStateFactory(doc: Document, appId: string, transferStore: TransferState) { return () => { + // The `.toJSON` here causes the `onSerialize` callbacks to be called. + // These callbacks can be used to provide the value for a given key. + const content = transferStore.toJson(); + if (transferStore.isEmpty) { // The state is empty, nothing to transfer, // avoid creating an extra `'; - - beforeEach(() => { - called = false; - }); - afterEach(() => { - expect(called).toBe(true); - }); - - it('adds transfer script tag when using renderModule', waitForAsync(() => { - renderModule(TransferStoreModule, {document: ''}).then(output => { - expect(output).toBe(defaultExpectedOutput); - called = true; - }); - })); - - it('adds transfer script tag when using renderModuleFactory', - waitForAsync(inject([PlatformRef], (defaultPlatform: PlatformRef) => { - const compilerFactory: CompilerFactory = - defaultPlatform.injector.get(CompilerFactory, null)!; - const moduleFactory = - compilerFactory.createCompiler().compileModuleSync(TransferStoreModule); - renderModuleFactory(moduleFactory, {document: ''}).then(output => { - expect(output).toBe(defaultExpectedOutput); - called = true; - }); - }))); - - it('cannot break out of '); - called = true; - }); - })); - }); }); })(); diff --git a/packages/platform-server/test/transfer_state_spec.ts b/packages/platform-server/test/transfer_state_spec.ts new file mode 100644 index 0000000000000..22bc54a1c220c --- /dev/null +++ b/packages/platform-server/test/transfer_state_spec.ts @@ -0,0 +1,86 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, NgModule,} from '@angular/core'; +import {BrowserModule, makeStateKey, TransferState} from '@angular/platform-browser'; +import {renderModule, ServerModule} from '@angular/platform-server'; + +describe('transfer_state', () => { + const defaultExpectedOutput = + 'Works!'; + + it('adds transfer script tag when using renderModule', async () => { + const STATE_KEY = makeStateKey('test'); + + @Component({selector: 'app', template: 'Works!'}) + class TransferComponent { + constructor(private transferStore: TransferState) { + this.transferStore.set(STATE_KEY, 10); + } + } + + @NgModule({ + bootstrap: [TransferComponent], + declarations: [TransferComponent], + imports: [BrowserModule.withServerTransition({appId: 'transfer'}), ServerModule], + }) + class TransferStoreModule { + } + + const output = await renderModule(TransferStoreModule, {document: ''}); + expect(output).toBe(defaultExpectedOutput); + }); + + it('cannot break out of '); + }); + + it('adds transfer script tag when setting state during onSerialize', async () => { + const STATE_KEY = makeStateKey('test'); + + @Component({selector: 'app', template: 'Works!'}) + class TransferComponent { + constructor(private transferStore: TransferState) { + this.transferStore.onSerialize(STATE_KEY, () => 10); + } + } + + @NgModule({ + bootstrap: [TransferComponent], + declarations: [TransferComponent], + imports: [BrowserModule.withServerTransition({appId: 'transfer'}), ServerModule], + }) + class TransferStoreModule { + } + + const output = await renderModule(TransferStoreModule, {document: ''}); + expect(output).toBe(defaultExpectedOutput); + }); +}); diff --git a/packages/platform-server/testing/src/server.ts b/packages/platform-server/testing/src/server.ts index 67fc2bc853e87..6b5e49809fc30 100644 --- a/packages/platform-server/testing/src/server.ts +++ b/packages/platform-server/testing/src/server.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {createPlatformFactory, NgModule, PlatformRef, StaticProvider} from '@angular/core'; +import {createPlatformFactory, NgModule} from '@angular/core'; import {BrowserDynamicTestingModule, ɵplatformCoreDynamicTesting as platformCoreDynamicTesting} from '@angular/platform-browser-dynamic/testing'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {ɵINTERNAL_SERVER_PLATFORM_PROVIDERS as INTERNAL_SERVER_PLATFORM_PROVIDERS, ɵSERVER_RENDER_PROVIDERS as SERVER_RENDER_PROVIDERS} from '@angular/platform-server';