diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json index 6f517e3ba56..e6952dd0d6a 100644 --- a/goldens/size-tracking/integration-payloads.json +++ b/goldens/size-tracking/integration-payloads.json @@ -2,7 +2,7 @@ "cli-hello-world": { "uncompressed": { "runtime": 908, - "main": 129295, + "main": 134468, "polyfills": 33792 } }, @@ -24,14 +24,14 @@ "forms": { "uncompressed": { "runtime": 888, - "main": 160778, + "main": 166256, "polyfills": 33772 } }, "animations": { "uncompressed": { "runtime": 898, - "main": 159461, + "main": 164757, "polyfills": 33782 } }, @@ -56,4 +56,4 @@ "polyfills": 33802 } } -} \ No newline at end of file +} diff --git a/integration/platform-server/e2e/src/http-transferstate-lazy-on-init-spec.ts b/integration/platform-server/e2e/src/http-transferstate-lazy-on-init-spec.ts new file mode 100644 index 00000000000..af7081933d9 --- /dev/null +++ b/integration/platform-server/e2e/src/http-transferstate-lazy-on-init-spec.ts @@ -0,0 +1,44 @@ +/** + * @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 {browser, by, element} from 'protractor'; +import {bootstrapClientApp, navigateTo, verifyNoBrowserErrors} from './util'; + +describe('Http TransferState Lazy On Init', () => { + beforeEach(async () => { + // Don't wait for Angular since it is not bootstrapped automatically. + await browser.waitForAngularEnabled(false); + + // Load the page without waiting for Angular since it is not bootstrapped automatically. + await navigateTo('http-transferstate-lazy-on-init'); + }); + + afterEach(async () => { + // Make sure there were no client side errors. + await verifyNoBrowserErrors(); + }); + + it('should transfer http state in lazy component', async () => { + // Test the contents from the server. + expect(await element(by.css('div.one')).getText()).toBe('API 1 response'); + + // Bootstrap the client side app and retest the contents + await bootstrapClientApp(); + expect(await element(by.css('div.one')).getText()).toBe('API 1 response'); + + // Validate that there were no HTTP calls to '/api'. + const requests = await browser.executeScript(() => { + return performance.getEntriesByType('resource'); + }); + const apiRequests = (requests as {name: string}[]) + .filter(({name}) => name.includes('/api')) + .map(({name}) => name); + + expect(apiRequests).toEqual([]); + }); +}); diff --git a/integration/platform-server/projects/ngmodule/src/app/app-routing.module.ts b/integration/platform-server/projects/ngmodule/src/app/app-routing.module.ts index 701e7bc35a7..15b321b453c 100644 --- a/integration/platform-server/projects/ngmodule/src/app/app-routing.module.ts +++ b/integration/platform-server/projects/ngmodule/src/app/app-routing.module.ts @@ -19,6 +19,13 @@ const routes: Routes = [ (m) => m.HttpTransferStateModule ), }, + { + path: 'http-transferstate-lazy-on-init', + loadChildren: () => + import('./http-transferstate-lazy-on-init/http-transferstate-lazy-on-init.module').then( + (m) => m.HttpTransferStateOnInitModule + ), + }, ]; @NgModule({ diff --git a/integration/platform-server/projects/ngmodule/src/app/http-transferstate-lazy-on-init/http-transferstate-lazy-on-init.component.ts b/integration/platform-server/projects/ngmodule/src/app/http-transferstate-lazy-on-init/http-transferstate-lazy-on-init.component.ts new file mode 100644 index 00000000000..be88110e919 --- /dev/null +++ b/integration/platform-server/projects/ngmodule/src/app/http-transferstate-lazy-on-init/http-transferstate-lazy-on-init.component.ts @@ -0,0 +1,27 @@ +/** + * @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 {HttpClient} from '@angular/common/http'; +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector: 'transfer-state-http-on-init', + template: `
{{ responseOne }}
`, +}) +export class TransferStateComponentOnInit implements OnInit { + responseOne: string = ''; + + constructor(private readonly httpClient: HttpClient) {} + + ngOnInit(): void { + // Test that HTTP cache works when HTTP call is made in a lifecycle hook. + this.httpClient.get('http://localhost:4206/api').subscribe((response) => { + this.responseOne = response.data; + }); + } +} diff --git a/integration/platform-server/projects/ngmodule/src/app/http-transferstate-lazy-on-init/http-transferstate-lazy-on-init.module.ts b/integration/platform-server/projects/ngmodule/src/app/http-transferstate-lazy-on-init/http-transferstate-lazy-on-init.module.ts new file mode 100644 index 00000000000..46fa2fd3ca0 --- /dev/null +++ b/integration/platform-server/projects/ngmodule/src/app/http-transferstate-lazy-on-init/http-transferstate-lazy-on-init.module.ts @@ -0,0 +1,18 @@ +import {CommonModule} from '@angular/common'; +import {HttpClientModule} from '@angular/common/http'; +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {TransferStateComponentOnInit} from './http-transferstate-lazy-on-init.component'; + +const routes: Routes = [ + { + path: '', + component: TransferStateComponentOnInit, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes), HttpClientModule, CommonModule], + declarations: [TransferStateComponentOnInit], +}) +export class HttpTransferStateOnInitModule {} diff --git a/integration/platform-server/projects/standalone/src/app/app.routes.ts b/integration/platform-server/projects/standalone/src/app/app.routes.ts index 851ce59d373..697c7ecb730 100644 --- a/integration/platform-server/projects/standalone/src/app/app.routes.ts +++ b/integration/platform-server/projects/standalone/src/app/app.routes.ts @@ -18,4 +18,11 @@ export const routes: Routes = [ (c) => c.TransferStateComponent ), }, + { + path: 'http-transferstate-lazy-on-init', + loadComponent: () => + import('./http-transferstate-lazy-on-init/http-transfer-state-on-init.component').then( + (c) => c.TransferStateOnInitComponent + ), + }, ]; diff --git a/integration/platform-server/projects/standalone/src/app/http-transferstate-lazy-on-init/http-transfer-state-on-init.component.ts b/integration/platform-server/projects/standalone/src/app/http-transferstate-lazy-on-init/http-transfer-state-on-init.component.ts new file mode 100644 index 00000000000..6801b8c7708 --- /dev/null +++ b/integration/platform-server/projects/standalone/src/app/http-transferstate-lazy-on-init/http-transfer-state-on-init.component.ts @@ -0,0 +1,29 @@ +/** + * @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 {HttpClient} from '@angular/common/http'; +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector: 'transfer-state-http', + standalone: true, + template: `
{{ responseOne }}
`, + providers: [HttpClient], +}) +export class TransferStateOnInitComponent implements OnInit { + responseOne: string = ''; + + constructor(private readonly httpClient: HttpClient) {} + + ngOnInit(): void { + // Test that HTTP cache works when HTTP call is made in a lifecycle hook. + this.httpClient.get('http://localhost:4206/api').subscribe((response) => { + this.responseOne = response.data; + }); + } +} diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index ec9b181f648..7ee09d94ac6 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -9,7 +9,7 @@ import './util/ng_jit_mode'; import {Observable, of, Subscription} from 'rxjs'; -import {distinctUntilChanged, mergeMap, share} from 'rxjs/operators'; +import {distinctUntilChanged, share, switchMap} from 'rxjs/operators'; import {ApplicationInitStatus} from './application_init'; import {PLATFORM_INITIALIZER} from './application_tokens'; @@ -847,7 +847,7 @@ export class ApplicationRef { public readonly isStable: Observable = inject(InitialRenderPendingTasks) .hasPendingTasks.pipe( - mergeMap(hasPendingTasks => hasPendingTasks ? of(false) : this.zoneIsStable), + switchMap(hasPendingTasks => hasPendingTasks ? of(false) : this.zoneIsStable), distinctUntilChanged(), share(), ); diff --git a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json index 806146da292..2fa35c77d53 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -500,6 +500,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -851,6 +857,9 @@ { "name": "forwardRef" }, + { + "name": "from" + }, { "name": "fromArray" }, @@ -1034,6 +1043,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -1175,6 +1187,9 @@ { "name": "makeTimingAst" }, + { + "name": "map" + }, { "name": "markAsComponentHost" }, @@ -1187,6 +1202,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeHostAttribute" }, @@ -1415,6 +1433,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" }, diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index 11f9c76ae7d..47f7c9f0ad5 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -542,6 +542,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -914,6 +920,9 @@ { "name": "forwardRef" }, + { + "name": "from" + }, { "name": "fromArray" }, @@ -1100,6 +1109,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -1241,6 +1253,9 @@ { "name": "makeTimingAst" }, + { + "name": "map" + }, { "name": "markAsComponentHost" }, @@ -1253,6 +1268,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeHostAttribute" }, @@ -1490,6 +1508,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" }, diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 3fe3ae56328..c0ec83f40c1 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -407,6 +407,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -689,6 +695,9 @@ { "name": "forwardRef" }, + { + "name": "from" + }, { "name": "fromArray" }, @@ -866,6 +875,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -980,6 +992,9 @@ { "name": "makeRecord" }, + { + "name": "map" + }, { "name": "markAsComponentHost" }, @@ -992,6 +1007,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeHostAttribute" }, @@ -1181,6 +1199,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" }, diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 2230f1ecee7..ebc2528263d 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -548,6 +548,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -1199,6 +1205,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -1379,6 +1388,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeErrors" }, @@ -1646,6 +1658,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" }, diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 749d251eb7f..d0e0a387bb1 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -536,6 +536,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -1163,6 +1169,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -1337,6 +1346,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeErrors" }, @@ -1622,6 +1634,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index ed1d8f45fb9..c25234c0e57 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -314,6 +314,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -536,6 +542,9 @@ { "name": "forwardRef" }, + { + "name": "from" + }, { "name": "fromArray" }, @@ -686,6 +695,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -767,6 +779,9 @@ { "name": "makeRecord" }, + { + "name": "map" + }, { "name": "markViewDirty" }, @@ -776,6 +791,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeHostAttribute" }, @@ -929,6 +947,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" }, diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index 6cede02c989..9b6f035cba0 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -458,6 +458,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -746,6 +752,9 @@ { "name": "forwardRef" }, + { + "name": "from" + }, { "name": "fromArray" }, @@ -926,6 +935,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -1046,6 +1058,9 @@ { "name": "makeRecord" }, + { + "name": "map" + }, { "name": "markViewDirty" }, @@ -1055,6 +1070,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeHostAttribute" }, @@ -1241,6 +1259,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwIfEmpty" }, diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index 03dd4c90869..a967b543010 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -374,6 +374,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -614,6 +620,9 @@ { "name": "forwardRef" }, + { + "name": "from" + }, { "name": "fromArray" }, @@ -770,6 +779,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -860,6 +872,9 @@ { "name": "makeRecord" }, + { + "name": "map" + }, { "name": "markViewDirty" }, @@ -869,6 +884,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeHostAttribute" }, @@ -1031,6 +1049,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index e290dc33458..86a119e9294 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -437,6 +437,12 @@ { "name": "Subscription" }, + { + "name": "SwitchMapOperator" + }, + { + "name": "SwitchMapSubscriber" + }, { "name": "TESTABILITY" }, @@ -812,6 +818,9 @@ { "name": "forwardRef" }, + { + "name": "from" + }, { "name": "fromArray" }, @@ -1037,6 +1046,9 @@ { "name": "injectableDefOrInjectorDefFactory" }, + { + "name": "innerSubscribe" + }, { "name": "insertBloom" }, @@ -1175,6 +1187,9 @@ { "name": "makeRecord" }, + { + "name": "map" + }, { "name": "markAsComponentHost" }, @@ -1190,6 +1205,9 @@ { "name": "maybeWrapInNotSelector" }, + { + "name": "mergeAll" + }, { "name": "mergeHostAttribute" }, @@ -1406,6 +1424,9 @@ { "name": "subscribeToArray" }, + { + "name": "switchMap" + }, { "name": "throwProviderNotFoundError" },