From 1537791f06df5d85dd27e763836fd99bb1530991 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 22 Aug 2019 19:24:00 -0700 Subject: [PATCH] perf(core): Make `PlatformLocation` tree-shakable (#32154) Convert `PlatformLocation` into a tree-shakable provider. PR Close #32154 --- integration/_payload-limits.json | 6 +- integration/ngcc/test.sh | 2 +- .../side-effects/snapshots/common/esm5.js | 4 +- .../side-effects/snapshots/forms/esm2015.js | 2 +- .../side-effects/snapshots/forms/esm5.js | 2 +- .../side-effects/snapshots/router/esm2015.js | 2 - .../side-effects/snapshots/router/esm5.js | 2 - packages/common/src/dom_adapter.ts | 1 - .../src/location/hash_location_strategy.ts | 11 +-- packages/common/src/location/index.ts | 9 +- packages/common/src/location/location.ts | 56 ++++------- .../common/src/location/location_strategy.ts | 93 +++++++++++++++++- .../src/location/path_location_strategy.ts | 97 ------------------- .../common/src/location/platform_location.ts | 91 ++++++++++++++++- packages/common/src/location/util.ts | 67 +++++++++++++ packages/common/src/private_export.ts | 1 + packages/core/src/application_ref.ts | 6 +- packages/core/src/di/util.ts | 5 +- packages/platform-browser/src/browser.ts | 3 - .../location/browser_platform_location.ts | 78 --------------- .../src/browser/location/history.ts | 11 --- .../platform-browser/src/private_export.ts | 2 +- packages/platform-server/src/server.ts | 4 +- .../src/web_workers/ui/location_providers.ts | 3 +- .../src/web_workers/ui/platform_location.ts | 3 +- .../web_workers/worker/location_providers.ts | 6 +- packages/platform-webworker/src/worker_app.ts | 11 +-- tools/public_api_guard/common/common.d.ts | 6 +- .../platform-webworker.d.ts | 20 +--- 29 files changed, 307 insertions(+), 297 deletions(-) delete mode 100644 packages/common/src/location/path_location_strategy.ts create mode 100644 packages/common/src/location/util.ts delete mode 100644 packages/platform-browser/src/browser/location/browser_platform_location.ts delete mode 100644 packages/platform-browser/src/browser/location/history.ts diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 58fb24e623f43..fb86788fb6ab8 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime": 1497, - "main": 161490, + "main": 158490, "polyfills": 45399 } } @@ -21,7 +21,7 @@ "master": { "uncompressed": { "runtime": 1440, - "main": 128448, + "main": 125448, "polyfills": 45340 } } @@ -34,4 +34,4 @@ } } } -} +} \ No newline at end of file diff --git a/integration/ngcc/test.sh b/integration/ngcc/test.sh index 3f31155677c97..8c0fd2011ec67 100755 --- a/integration/ngcc/test.sh +++ b/integration/ngcc/test.sh @@ -68,7 +68,7 @@ grep "_MatMenuBase.ngBaseDef = ɵngcc0.ɵɵdefineBase({ inputs: {" node_modules/ if [[ $? != 0 ]]; then exit 1; fi # Did it handle namespace imported decorators in UMD using `__decorate` syntax? -grep "type: core.Injectable" node_modules/@angular/common/bundles/common.umd.js +grep "type: i0.Injectable" node_modules/@angular/common/bundles/common.umd.js # (and ensure the @angular/common package is indeed using `__decorate` syntax) grep "JsonPipe = __decorate(" node_modules/@angular/common/bundles/common.umd.js.__ivy_ngcc_bak diff --git a/integration/side-effects/snapshots/common/esm5.js b/integration/side-effects/snapshots/common/esm5.js index 783048bfcf7b2..05d504f1d48d5 100644 --- a/integration/side-effects/snapshots/common/esm5.js +++ b/integration/side-effects/snapshots/common/esm5.js @@ -1,3 +1,3 @@ -import "@angular/core"; - import "tslib"; + +import "@angular/core"; diff --git a/integration/side-effects/snapshots/forms/esm2015.js b/integration/side-effects/snapshots/forms/esm2015.js index 7b26ecaa69ea6..fe5d46d469a35 100644 --- a/integration/side-effects/snapshots/forms/esm2015.js +++ b/integration/side-effects/snapshots/forms/esm2015.js @@ -1,6 +1,6 @@ import "@angular/core"; -import "@angular/platform-browser"; +import "@angular/common"; import "rxjs"; diff --git a/integration/side-effects/snapshots/forms/esm5.js b/integration/side-effects/snapshots/forms/esm5.js index 70eeb745ff8b6..a314b72fb4b63 100644 --- a/integration/side-effects/snapshots/forms/esm5.js +++ b/integration/side-effects/snapshots/forms/esm5.js @@ -2,7 +2,7 @@ import "tslib"; import "@angular/core"; -import "@angular/platform-browser"; +import "@angular/common"; import "rxjs"; diff --git a/integration/side-effects/snapshots/router/esm2015.js b/integration/side-effects/snapshots/router/esm2015.js index 0f9f04eb6f6ea..829a5bdc8cf9d 100644 --- a/integration/side-effects/snapshots/router/esm2015.js +++ b/integration/side-effects/snapshots/router/esm2015.js @@ -5,5 +5,3 @@ import "@angular/core"; import "rxjs"; import "rxjs/operators"; - -import "@angular/platform-browser"; diff --git a/integration/side-effects/snapshots/router/esm5.js b/integration/side-effects/snapshots/router/esm5.js index 22e1dbe38a6e8..ff225d142ae84 100644 --- a/integration/side-effects/snapshots/router/esm5.js +++ b/integration/side-effects/snapshots/router/esm5.js @@ -7,5 +7,3 @@ import "@angular/core"; import "rxjs"; import "rxjs/operators"; - -import "@angular/platform-browser"; diff --git a/packages/common/src/dom_adapter.ts b/packages/common/src/dom_adapter.ts index 5cf0f1af91b78..ba24fbaf10657 100644 --- a/packages/common/src/dom_adapter.ts +++ b/packages/common/src/dom_adapter.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - let _DOM: DomAdapter = null !; export function getDOM(): DomAdapter { diff --git a/packages/common/src/location/hash_location_strategy.ts b/packages/common/src/location/hash_location_strategy.ts index c182f244c21ed..b25601127e320 100644 --- a/packages/common/src/location/hash_location_strategy.ts +++ b/packages/common/src/location/hash_location_strategy.ts @@ -7,11 +7,9 @@ */ import {Inject, Injectable, Optional} from '@angular/core'; - - -import {Location} from './location'; import {APP_BASE_HREF, LocationStrategy} from './location_strategy'; import {LocationChangeListener, PlatformLocation} from './platform_location'; +import {joinWithSlash, normalizeQueryParams} from './util'; @@ -62,13 +60,12 @@ export class HashLocationStrategy extends LocationStrategy { } prepareExternalUrl(internal: string): string { - const url = Location.joinWithSlash(this._baseHref, internal); + const url = joinWithSlash(this._baseHref, internal); return url.length > 0 ? ('#' + url) : url; } pushState(state: any, title: string, path: string, queryParams: string) { - let url: string|null = - this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); + let url: string|null = this.prepareExternalUrl(path + normalizeQueryParams(queryParams)); if (url.length == 0) { url = this._platformLocation.pathname; } @@ -76,7 +73,7 @@ export class HashLocationStrategy extends LocationStrategy { } replaceState(state: any, title: string, path: string, queryParams: string) { - let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); + let url = this.prepareExternalUrl(path + normalizeQueryParams(queryParams)); if (url.length == 0) { url = this._platformLocation.pathname; } diff --git a/packages/common/src/location/index.ts b/packages/common/src/location/index.ts index 5707ccd33fde1..55fd8274484c9 100644 --- a/packages/common/src/location/index.ts +++ b/packages/common/src/location/index.ts @@ -6,8 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -export * from './platform_location'; -export * from './location_strategy'; -export * from './hash_location_strategy'; -export * from './path_location_strategy'; -export * from './location'; +export {HashLocationStrategy} from './hash_location_strategy'; +export {Location, PopStateEvent} from './location'; +export {APP_BASE_HREF, LocationStrategy, PathLocationStrategy} from './location_strategy'; +export {LOCATION_INITIALIZED, LocationChangeEvent, LocationChangeListener, PlatformLocation} from './platform_location'; diff --git a/packages/common/src/location/location.ts b/packages/common/src/location/location.ts index a74540cba9de8..a161654e6d6ea 100644 --- a/packages/common/src/location/location.ts +++ b/packages/common/src/location/location.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {EventEmitter, Injectable} from '@angular/core'; +import {EventEmitter, Injectable, ɵɵinject} from '@angular/core'; import {SubscriptionLike} from 'rxjs'; - import {LocationStrategy} from './location_strategy'; import {PlatformLocation} from './platform_location'; +import {joinWithSlash, normalizeQueryParams, stripTrailingSlash} from './util'; /** @publicApi */ export interface PopStateEvent { @@ -48,7 +48,11 @@ export interface PopStateEvent { * * @publicApi */ -@Injectable() +@Injectable({ + providedIn: 'root', + // See #23917 + useFactory: createLocation, +}) export class Location { /** @internal */ _subject: EventEmitter = new EventEmitter(); @@ -65,7 +69,7 @@ export class Location { this._platformStrategy = platformStrategy; const browserBaseHref = this._platformStrategy.getBaseHref(); this._platformLocation = platformLocation; - this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref)); + this._baseHref = stripTrailingSlash(_stripIndexHtml(browserBaseHref)); this._platformStrategy.onPopState((ev) => { this._subject.emit({ 'url': this.path(true), @@ -105,7 +109,7 @@ export class Location { * otherwise. */ isCurrentPathEqualTo(path: string, query: string = ''): boolean { - return this.path() == this.normalize(path + Location.normalizeQueryParams(query)); + return this.path() == this.normalize(path + normalizeQueryParams(query)); } /** @@ -149,7 +153,7 @@ export class Location { go(path: string, query: string = '', state: any = null): void { this._platformStrategy.pushState(state, '', path, query); this._notifyUrlChangeListeners( - this.prepareExternalUrl(path + Location.normalizeQueryParams(query)), state); + this.prepareExternalUrl(path + normalizeQueryParams(query)), state); } /** @@ -163,7 +167,7 @@ export class Location { replaceState(path: string, query: string = '', state: any = null): void { this._platformStrategy.replaceState(state, '', path, query); this._notifyUrlChangeListeners( - this.prepareExternalUrl(path + Location.normalizeQueryParams(query)), state); + this.prepareExternalUrl(path + normalizeQueryParams(query)), state); } /** @@ -213,9 +217,7 @@ export class Location { * * @returns The normalized URL parameters string. */ - public static normalizeQueryParams(params: string): string { - return params && params[0] !== '?' ? '?' + params : params; - } + public static normalizeQueryParams: (params: string) => string = normalizeQueryParams; /** * Joins two parts of a URL with a slash if needed. @@ -226,28 +228,7 @@ export class Location { * * @returns The joined URL string. */ - public static joinWithSlash(start: string, end: string): string { - if (start.length == 0) { - return end; - } - if (end.length == 0) { - return start; - } - let slashes = 0; - if (start.endsWith('/')) { - slashes++; - } - if (end.startsWith('/')) { - slashes++; - } - if (slashes == 2) { - return start + end.substring(1); - } - if (slashes == 1) { - return start + end; - } - return start + '/' + end; - } + public static joinWithSlash: (start: string, end: string) => string = joinWithSlash; /** * Removes a trailing slash from a URL string if needed. @@ -258,12 +239,11 @@ export class Location { * * @returns The URL string, modified if needed. */ - public static stripTrailingSlash(url: string): string { - const match = url.match(/#|\?|$/); - const pathEndIdx = match && match.index || url.length; - const droppedSlashIdx = pathEndIdx - (url[pathEndIdx - 1] === '/' ? 1 : 0); - return url.slice(0, droppedSlashIdx) + url.slice(pathEndIdx); - } + public static stripTrailingSlash: (url: string) => string = stripTrailingSlash; +} + +export function createLocation() { + return new Location(ɵɵinject(LocationStrategy as any), ɵɵinject(PlatformLocation as any)); } function _stripBaseHref(baseHref: string, url: string): string { diff --git a/packages/common/src/location/location_strategy.ts b/packages/common/src/location/location_strategy.ts index 7a37ce7af9b10..a4e732c20ad8d 100644 --- a/packages/common/src/location/location_strategy.ts +++ b/packages/common/src/location/location_strategy.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {InjectionToken} from '@angular/core'; -import {LocationChangeListener} from './platform_location'; +import {Inject, Injectable, InjectionToken, Optional, ɵɵinject} from '@angular/core'; +import {DOCUMENT} from '../dom_tokens'; +import {LocationChangeListener, PlatformLocation} from './platform_location'; +import {joinWithSlash, normalizeQueryParams} from './util'; /** * Enables the `Location` service to read route state from the browser's URL. @@ -26,6 +28,7 @@ import {LocationChangeListener} from './platform_location'; * * @publicApi */ +@Injectable({providedIn: 'root', useFactory: provideLocationStrategy}) export abstract class LocationStrategy { abstract path(includeHash?: boolean): string; abstract prepareExternalUrl(internal: string): string; @@ -37,6 +40,13 @@ export abstract class LocationStrategy { abstract getBaseHref(): string; } +export function provideLocationStrategy(platformLocation: PlatformLocation) { + // See #23917 + const location = ɵɵinject(DOCUMENT).location; + return new PathLocationStrategy( + ɵɵinject(PlatformLocation as any), location && location.origin || ''); +} + /** * A predefined [DI token](guide/glossary#di-token) for the base href @@ -62,3 +72,82 @@ export abstract class LocationStrategy { * @publicApi */ export const APP_BASE_HREF = new InjectionToken('appBaseHref'); + +/** + * @description + * A {@link LocationStrategy} used to configure the {@link Location} service to + * represent its state in the + * [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the + * browser's URL. + * + * If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF} + * or add a base element to the document. This URL prefix that will be preserved + * when generating and recognizing URLs. + * + * For instance, if you provide an `APP_BASE_HREF` of `'/my/app'` and call + * `location.go('/foo')`, the browser's URL will become + * `example.com/my/app/foo`. + * + * Similarly, if you add `` to the document and call + * `location.go('/foo')`, the browser's URL will become + * `example.com/my/app/foo`. + * + * @usageNotes + * + * ### Example + * + * {@example common/location/ts/path_location_component.ts region='LocationComponent'} + * + * @publicApi + */ +@Injectable() +export class PathLocationStrategy extends LocationStrategy { + private _baseHref: string; + + constructor( + private _platformLocation: PlatformLocation, + @Optional() @Inject(APP_BASE_HREF) href?: string) { + super(); + + if (href == null) { + href = this._platformLocation.getBaseHrefFromDOM(); + } + + if (href == null) { + throw new Error( + `No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`); + } + + this._baseHref = href; + } + + onPopState(fn: LocationChangeListener): void { + this._platformLocation.onPopState(fn); + this._platformLocation.onHashChange(fn); + } + + getBaseHref(): string { return this._baseHref; } + + prepareExternalUrl(internal: string): string { return joinWithSlash(this._baseHref, internal); } + + path(includeHash: boolean = false): string { + const pathname = + this._platformLocation.pathname + normalizeQueryParams(this._platformLocation.search); + const hash = this._platformLocation.hash; + return hash && includeHash ? `${pathname}${hash}` : pathname; + } + + pushState(state: any, title: string, url: string, queryParams: string) { + const externalUrl = this.prepareExternalUrl(url + normalizeQueryParams(queryParams)); + this._platformLocation.pushState(state, title, externalUrl); + } + + replaceState(state: any, title: string, url: string, queryParams: string) { + const externalUrl = this.prepareExternalUrl(url + normalizeQueryParams(queryParams)); + this._platformLocation.replaceState(state, title, externalUrl); + } + + forward(): void { this._platformLocation.forward(); } + + back(): void { this._platformLocation.back(); } +} diff --git a/packages/common/src/location/path_location_strategy.ts b/packages/common/src/location/path_location_strategy.ts deleted file mode 100644 index e518978e4d1ba..0000000000000 --- a/packages/common/src/location/path_location_strategy.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @license - * Copyright Google Inc. 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 {Inject, Injectable, Optional} from '@angular/core'; - - -import {Location} from './location'; -import {APP_BASE_HREF, LocationStrategy} from './location_strategy'; -import {LocationChangeListener, PlatformLocation} from './platform_location'; - - - -/** - * @description - * A {@link LocationStrategy} used to configure the {@link Location} service to - * represent its state in the - * [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the - * browser's URL. - * - * If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF} - * or add a base element to the document. This URL prefix that will be preserved - * when generating and recognizing URLs. - * - * For instance, if you provide an `APP_BASE_HREF` of `'/my/app'` and call - * `location.go('/foo')`, the browser's URL will become - * `example.com/my/app/foo`. - * - * Similarly, if you add `` to the document and call - * `location.go('/foo')`, the browser's URL will become - * `example.com/my/app/foo`. - * - * @usageNotes - * - * ### Example - * - * {@example common/location/ts/path_location_component.ts region='LocationComponent'} - * - * @publicApi - */ -@Injectable() -export class PathLocationStrategy extends LocationStrategy { - private _baseHref: string; - - constructor( - private _platformLocation: PlatformLocation, - @Optional() @Inject(APP_BASE_HREF) href?: string) { - super(); - - if (href == null) { - href = this._platformLocation.getBaseHrefFromDOM(); - } - - if (href == null) { - throw new Error( - `No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`); - } - - this._baseHref = href; - } - - onPopState(fn: LocationChangeListener): void { - this._platformLocation.onPopState(fn); - this._platformLocation.onHashChange(fn); - } - - getBaseHref(): string { return this._baseHref; } - - prepareExternalUrl(internal: string): string { - return Location.joinWithSlash(this._baseHref, internal); - } - - path(includeHash: boolean = false): string { - const pathname = this._platformLocation.pathname + - Location.normalizeQueryParams(this._platformLocation.search); - const hash = this._platformLocation.hash; - return hash && includeHash ? `${pathname}${hash}` : pathname; - } - - pushState(state: any, title: string, url: string, queryParams: string) { - const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); - this._platformLocation.pushState(state, title, externalUrl); - } - - replaceState(state: any, title: string, url: string, queryParams: string) { - const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); - this._platformLocation.replaceState(state, title, externalUrl); - } - - forward(): void { this._platformLocation.forward(); } - - back(): void { this._platformLocation.back(); } -} diff --git a/packages/common/src/location/platform_location.ts b/packages/common/src/location/platform_location.ts index 8a95db1a904f7..4139d768153c1 100644 --- a/packages/common/src/location/platform_location.ts +++ b/packages/common/src/location/platform_location.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {InjectionToken} from '@angular/core'; +import {Inject, Injectable, InjectionToken, ɵɵinject} from '@angular/core'; +import {getDOM} from '../dom_adapter'; +import {DOCUMENT} from '../dom_tokens'; + /** * This class should not be used directly by an application developer. Instead, use * {@link Location}. @@ -29,6 +32,11 @@ import {InjectionToken} from '@angular/core'; * * @publicApi */ +@Injectable({ + providedIn: 'platform', + // See #23917 + useFactory: useBrowserPlatformLocation +}) export abstract class PlatformLocation { abstract getBaseHrefFromDOM(): string; abstract getState(): unknown; @@ -52,6 +60,10 @@ export abstract class PlatformLocation { abstract back(): void; } +export function useBrowserPlatformLocation() { + return ɵɵinject(BrowserPlatformLocation); +} + /** * @description * Indicates when a location is initialized. @@ -75,3 +87,80 @@ export interface LocationChangeEvent { * @publicApi */ export interface LocationChangeListener { (event: LocationChangeEvent): any; } + + + +/** + * `PlatformLocation` encapsulates all of the direct calls to platform APIs. + * This class should not be used directly by an application developer. Instead, use + * {@link Location}. + */ +@Injectable({ + providedIn: 'platform', + // See #23917 + useFactory: createBrowserPlatformLocation, +}) +export class BrowserPlatformLocation extends PlatformLocation { + public readonly location !: Location; + private _history !: History; + + constructor(@Inject(DOCUMENT) private _doc: any) { + super(); + this._init(); + } + + // This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it + /** @internal */ + _init() { + (this as{location: Location}).location = getDOM().getLocation(); + this._history = getDOM().getHistory(); + } + + getBaseHrefFromDOM(): string { return getDOM().getBaseHref(this._doc) !; } + + onPopState(fn: LocationChangeListener): void { + getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('popstate', fn, false); + } + + onHashChange(fn: LocationChangeListener): void { + getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('hashchange', fn, false); + } + + get href(): string { return this.location.href; } + get protocol(): string { return this.location.protocol; } + get hostname(): string { return this.location.hostname; } + get port(): string { return this.location.port; } + get pathname(): string { return this.location.pathname; } + get search(): string { return this.location.search; } + get hash(): string { return this.location.hash; } + set pathname(newPath: string) { this.location.pathname = newPath; } + + pushState(state: any, title: string, url: string): void { + if (supportsState()) { + this._history.pushState(state, title, url); + } else { + this.location.hash = url; + } + } + + replaceState(state: any, title: string, url: string): void { + if (supportsState()) { + this._history.replaceState(state, title, url); + } else { + this.location.hash = url; + } + } + + forward(): void { this._history.forward(); } + + back(): void { this._history.back(); } + + getState(): unknown { return this._history.state; } +} + +export function supportsState(): boolean { + return !!window.history.pushState; +} +export function createBrowserPlatformLocation() { + return new BrowserPlatformLocation(ɵɵinject(DOCUMENT)); +} \ No newline at end of file diff --git a/packages/common/src/location/util.ts b/packages/common/src/location/util.ts new file mode 100644 index 0000000000000..43415510bd4c9 --- /dev/null +++ b/packages/common/src/location/util.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright Google Inc. 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 + */ + + +/** + * Joins two parts of a URL with a slash if needed. + * + * @param start URL string + * @param end URL string + * + * + * @returns The joined URL string. + */ +export function joinWithSlash(start: string, end: string): string { + if (start.length == 0) { + return end; + } + if (end.length == 0) { + return start; + } + let slashes = 0; + if (start.endsWith('/')) { + slashes++; + } + if (end.startsWith('/')) { + slashes++; + } + if (slashes == 2) { + return start + end.substring(1); + } + if (slashes == 1) { + return start + end; + } + return start + '/' + end; +} + +/** + * Removes a trailing slash from a URL string if needed. + * Looks for the first occurrence of either `#`, `?`, or the end of the + * line as `/` characters and removes the trailing slash if one exists. + * + * @param url URL string. + * + * @returns The URL string, modified if needed. + */ +export function stripTrailingSlash(url: string): string { + const match = url.match(/#|\?|$/); + const pathEndIdx = match && match.index || url.length; + const droppedSlashIdx = pathEndIdx - (url[pathEndIdx - 1] === '/' ? 1 : 0); + return url.slice(0, droppedSlashIdx) + url.slice(pathEndIdx); +} + +/** + * Normalizes URL parameters by prepending with `?` if needed. + * + * @param params String of URL parameters. + * + * @returns The normalized URL parameters string. + */ +export function normalizeQueryParams(params: string): string { + return params && params[0] !== '?' ? '?' + params : params; +} diff --git a/packages/common/src/private_export.ts b/packages/common/src/private_export.ts index 18d7c9ff476be..7801e395d0868 100644 --- a/packages/common/src/private_export.ts +++ b/packages/common/src/private_export.ts @@ -11,3 +11,4 @@ export {NgClassImpl as ɵNgClassImpl, NgClassImplProvider__POST_R3__ as ɵNgClas export {ngStyleDirectiveDef__POST_R3__ as ɵngStyleDirectiveDef__POST_R3__, ngStyleFactoryDef__POST_R3__ as ɵngStyleFactoryDef__POST_R3__} from './directives/ng_style'; export {NgStyleImpl as ɵNgStyleImpl, NgStyleImplProvider__POST_R3__ as ɵNgStyleImplProvider__POST_R3__, NgStyleR2Impl as ɵNgStyleR2Impl} from './directives/ng_style_impl'; export {DomAdapter as ɵDomAdapter, getDOM as ɵgetDOM, setRootDomAdapter as ɵsetRootDomAdapter} from './dom_adapter'; +export {BrowserPlatformLocation as ɵBrowserPlatformLocation} from './location/platform_location'; \ No newline at end of file diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index 0ae872145b2cd..4de809d509ade 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -13,6 +13,7 @@ import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens import {getCompilerFacade} from './compiler/compiler_facade'; import {Console} from './console'; import {Injectable, InjectionToken, Injector, StaticProvider} from './di'; +import {INJECTOR_SCOPE} from './di/scope'; import {ErrorHandler} from './error_handler'; import {DEFAULT_LOCALE_ID} from './i18n/localization'; import {LOCALE_ID} from './i18n/tokens'; @@ -140,7 +141,10 @@ export function createPlatformFactory( providers.concat(extraProviders).concat({provide: marker, useValue: true})); } else { const injectedProviders: StaticProvider[] = - providers.concat(extraProviders).concat({provide: marker, useValue: true}); + providers.concat(extraProviders).concat({provide: marker, useValue: true}, { + provide: INJECTOR_SCOPE, + useValue: 'platform' + }); createPlatform(Injector.create({providers: injectedProviders, name: desc})); } } diff --git a/packages/core/src/di/util.ts b/packages/core/src/di/util.ts index ccc78def560d7..32b5bc595365a 100644 --- a/packages/core/src/di/util.ts +++ b/packages/core/src/di/util.ts @@ -10,6 +10,7 @@ import {Type} from '../interface/type'; import {ReflectionCapabilities} from '../reflection/reflection_capabilities'; import {getClosureSafeProperty} from '../util/property'; +import {resolveForwardRef} from './forward_ref'; import {injectArgs, ɵɵinject} from './injector_compatibility'; import {ClassSansProvider, ConstructorSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './interface/provider'; @@ -32,7 +33,7 @@ export function convertInjectableProviderToFactory( return () => valueProvider.useValue; } else if ((provider as ExistingSansProvider).useExisting) { const existingProvider = (provider as ExistingSansProvider); - return () => ɵɵinject(existingProvider.useExisting); + return () => ɵɵinject(resolveForwardRef(existingProvider.useExisting)); } else if ((provider as FactorySansProvider).useFactory) { const factoryProvider = (provider as FactorySansProvider); return () => factoryProvider.useFactory(...injectArgs(factoryProvider.deps || EMPTY_ARRAY)); @@ -43,7 +44,7 @@ export function convertInjectableProviderToFactory( const reflectionCapabilities = new ReflectionCapabilities(); deps = reflectionCapabilities.parameters(type); } - return () => new classProvider.useClass(...injectArgs(deps)); + return () => new (resolveForwardRef(classProvider.useClass))(...injectArgs(deps)); } else { let deps = (provider as ConstructorSansProvider).deps; if (!deps) { diff --git a/packages/platform-browser/src/browser.ts b/packages/platform-browser/src/browser.ts index c35e32d4851bb..bad1d64f90b16 100644 --- a/packages/platform-browser/src/browser.ts +++ b/packages/platform-browser/src/browser.ts @@ -8,9 +8,7 @@ import {CommonModule, DOCUMENT, PlatformLocation, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common'; import {APP_ID, ApplicationModule, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore, ɵConsole as Console, ɵINJECTOR_SCOPE as INJECTOR_SCOPE} from '@angular/core'; - import {BrowserDomAdapter} from './browser/browser_adapter'; -import {BrowserPlatformLocation} from './browser/location/browser_platform_location'; import {SERVER_TRANSITION_PROVIDERS, TRANSITION_ID} from './browser/server-transition'; import {BrowserGetTestability} from './browser/testability'; import {ELEMENT_PROBE_PROVIDERS} from './dom/debug/ng_probe'; @@ -25,7 +23,6 @@ import {DomSanitizer, DomSanitizerImpl} from './security/dom_sanitization_servic export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [ {provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID}, {provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true}, - {provide: PlatformLocation, useClass: BrowserPlatformLocation, deps: [DOCUMENT]}, {provide: DOCUMENT, useFactory: _document, deps: []}, ]; diff --git a/packages/platform-browser/src/browser/location/browser_platform_location.ts b/packages/platform-browser/src/browser/location/browser_platform_location.ts deleted file mode 100644 index c9fe0d49c445a..0000000000000 --- a/packages/platform-browser/src/browser/location/browser_platform_location.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright Google Inc. 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 {DOCUMENT, LocationChangeListener, PlatformLocation, ɵgetDOM as getDOM} from '@angular/common'; -import {Inject, Injectable} from '@angular/core'; -import {supportsState} from './history'; - - -/** - * `PlatformLocation` encapsulates all of the direct calls to platform APIs. - * This class should not be used directly by an application developer. Instead, use - * {@link Location}. - */ -@Injectable() -export class BrowserPlatformLocation extends PlatformLocation { - // TODO(issue/24571): remove '!'. - public readonly location !: Location; - // TODO(issue/24571): remove '!'. - private _history !: History; - - constructor(@Inject(DOCUMENT) private _doc: any) { - super(); - this._init(); - } - - // This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it - /** @internal */ - _init() { - (this as{location: Location}).location = getDOM().getLocation(); - this._history = getDOM().getHistory(); - } - - getBaseHrefFromDOM(): string { return getDOM().getBaseHref(this._doc) !; } - - onPopState(fn: LocationChangeListener): void { - getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('popstate', fn, false); - } - - onHashChange(fn: LocationChangeListener): void { - getDOM().getGlobalEventTarget(this._doc, 'window').addEventListener('hashchange', fn, false); - } - - get href(): string { return this.location.href; } - get protocol(): string { return this.location.protocol; } - get hostname(): string { return this.location.hostname; } - get port(): string { return this.location.port; } - get pathname(): string { return this.location.pathname; } - get search(): string { return this.location.search; } - get hash(): string { return this.location.hash; } - set pathname(newPath: string) { this.location.pathname = newPath; } - - pushState(state: any, title: string, url: string): void { - if (supportsState()) { - this._history.pushState(state, title, url); - } else { - this.location.hash = url; - } - } - - replaceState(state: any, title: string, url: string): void { - if (supportsState()) { - this._history.replaceState(state, title, url); - } else { - this.location.hash = url; - } - } - - forward(): void { this._history.forward(); } - - back(): void { this._history.back(); } - - getState(): unknown { return this._history.state; } -} diff --git a/packages/platform-browser/src/browser/location/history.ts b/packages/platform-browser/src/browser/location/history.ts deleted file mode 100644 index b7b3f3f067d01..0000000000000 --- a/packages/platform-browser/src/browser/location/history.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @license - * Copyright Google Inc. 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 - */ - -export function supportsState(): boolean { - return !!window.history.pushState; -} \ No newline at end of file diff --git a/packages/platform-browser/src/private_export.ts b/packages/platform-browser/src/private_export.ts index f81b8ee196e33..f6159130c3e23 100644 --- a/packages/platform-browser/src/private_export.ts +++ b/packages/platform-browser/src/private_export.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ +export {ɵgetDOM} from '@angular/common'; export {BROWSER_SANITIZATION_PROVIDERS as ɵBROWSER_SANITIZATION_PROVIDERS, BROWSER_SANITIZATION_PROVIDERS__POST_R3__ as ɵBROWSER_SANITIZATION_PROVIDERS__POST_R3__, INTERNAL_BROWSER_PLATFORM_PROVIDERS as ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS, initDomAdapter as ɵinitDomAdapter} from './browser'; export {BrowserDomAdapter as ɵBrowserDomAdapter} from './browser/browser_adapter'; -export {BrowserPlatformLocation as ɵBrowserPlatformLocation} from './browser/location/browser_platform_location'; export {TRANSITION_ID as ɵTRANSITION_ID} from './browser/server-transition'; export {BrowserGetTestability as ɵBrowserGetTestability} from './browser/testability'; export {escapeHtml as ɵescapeHtml} from './browser/transfer_state'; diff --git a/packages/platform-server/src/server.ts b/packages/platform-server/src/server.ts index 448cd48a52543..db9118aa62231 100644 --- a/packages/platform-server/src/server.ts +++ b/packages/platform-server/src/server.ts @@ -27,8 +27,6 @@ function notSupported(feature: string): Error { throw new Error(`platform-server does not support '${feature}'.`); } -type __retain_for_correct_d_ts_generation__ = [PlatformRef]; - export const INTERNAL_SERVER_PLATFORM_PROVIDERS: StaticProvider[] = [ {provide: DOCUMENT, useFactory: _document, deps: [Injector]}, {provide: PLATFORM_ID, useValue: PLATFORM_SERVER_ID}, @@ -93,7 +91,7 @@ function _document(injector: Injector) { /** * @publicApi */ -export const platformServer = +export const platformServer: (extraProviders?: StaticProvider[] | undefined) => PlatformRef = createPlatformFactory(platformCore, 'server', INTERNAL_SERVER_PLATFORM_PROVIDERS); /** diff --git a/packages/platform-webworker/src/web_workers/ui/location_providers.ts b/packages/platform-webworker/src/web_workers/ui/location_providers.ts index ac35c15f4acfd..6470bd99433bc 100644 --- a/packages/platform-webworker/src/web_workers/ui/location_providers.ts +++ b/packages/platform-webworker/src/web_workers/ui/location_providers.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {DOCUMENT} from '@angular/common'; +import {DOCUMENT, ɵBrowserPlatformLocation as BrowserPlatformLocation} from '@angular/common'; import {Injector, NgZone, PLATFORM_INITIALIZER, StaticProvider} from '@angular/core'; -import {ɵBrowserPlatformLocation as BrowserPlatformLocation} from '@angular/platform-browser'; import {MessageBus} from '../shared/message_bus'; import {Serializer} from '../shared/serializer'; diff --git a/packages/platform-webworker/src/web_workers/ui/platform_location.ts b/packages/platform-webworker/src/web_workers/ui/platform_location.ts index 2e0811e55f65f..0c97fe04647d6 100644 --- a/packages/platform-webworker/src/web_workers/ui/platform_location.ts +++ b/packages/platform-webworker/src/web_workers/ui/platform_location.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {LocationChangeListener} from '@angular/common'; +import {LocationChangeListener, ɵBrowserPlatformLocation as BrowserPlatformLocation} from '@angular/common'; import {EventEmitter, Injectable} from '@angular/core'; -import {ɵBrowserPlatformLocation as BrowserPlatformLocation} from '@angular/platform-browser'; import {MessageBus} from '../shared/message_bus'; import {ROUTER_CHANNEL} from '../shared/messaging_api'; import {LocationType, Serializer, SerializerTypes} from '../shared/serializer'; diff --git a/packages/platform-webworker/src/web_workers/worker/location_providers.ts b/packages/platform-webworker/src/web_workers/worker/location_providers.ts index e61ce5cf5b41a..c896f7e69d584 100644 --- a/packages/platform-webworker/src/web_workers/worker/location_providers.ts +++ b/packages/platform-webworker/src/web_workers/worker/location_providers.ts @@ -7,7 +7,7 @@ */ import {LOCATION_INITIALIZED, PlatformLocation} from '@angular/common'; -import {APP_INITIALIZER, InjectionToken, NgZone} from '@angular/core'; +import {APP_INITIALIZER, NgZone, StaticProvider} from '@angular/core'; import {WebWorkerPlatformLocation} from './platform_location'; @@ -19,8 +19,8 @@ import {WebWorkerPlatformLocation} from './platform_location'; * @publicApi * @deprecated platform-webworker is deprecated in Angular and will be removed in version 10 */ -export const WORKER_APP_LOCATION_PROVIDERS = [ - {provide: PlatformLocation, useClass: WebWorkerPlatformLocation}, { +export const WORKER_APP_LOCATION_PROVIDERS: StaticProvider[] = [ + { provide: PlatformLocation, useClass: WebWorkerPlatformLocation} as any as StaticProvider, { provide: APP_INITIALIZER, useFactory: appInitFnFactory, multi: true, diff --git a/packages/platform-webworker/src/worker_app.ts b/packages/platform-webworker/src/worker_app.ts index d467680b4041d..f7033bcf000e8 100644 --- a/packages/platform-webworker/src/worker_app.ts +++ b/packages/platform-webworker/src/worker_app.ts @@ -7,7 +7,7 @@ */ import {CommonModule, DOCUMENT, ViewportScroller, ɵNullViewportScroller as NullViewportScroller, ɵPLATFORM_WORKER_APP_ID as PLATFORM_WORKER_APP_ID} from '@angular/common'; -import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PLATFORM_ID, PlatformRef, RendererFactory2, RootRenderer, StaticProvider, createPlatformFactory, platformCore, ɵAPP_ROOT as APP_ROOT} from '@angular/core'; +import {APP_INITIALIZER, ApplicationModule, ErrorHandler, NgModule, NgZone, PLATFORM_ID, PlatformRef, RendererFactory2, StaticProvider, createPlatformFactory, platformCore, ɵINJECTOR_SCOPE as INJECTOR_SCOPE} from '@angular/core'; import {ɵBROWSER_SANITIZATION_PROVIDERS as BROWSER_SANITIZATION_PROVIDERS} from '@angular/platform-browser'; import {ON_WEB_WORKER} from './web_workers/shared/api'; @@ -20,14 +20,13 @@ import {ServiceMessageBrokerFactory} from './web_workers/shared/service_message_ import {WebWorkerRendererFactory2} from './web_workers/worker/renderer'; import {WorkerDomAdapter} from './web_workers/worker/worker_adapter'; - - /** * @publicApi * @deprecated platform-webworker is deprecated in Angular and will be removed in version 10 */ -export const platformWorkerApp = createPlatformFactory( - platformCore, 'workerApp', [{provide: PLATFORM_ID, useValue: PLATFORM_WORKER_APP_ID}]); +export const platformWorkerApp: (extraProviders?: StaticProvider[] | undefined) => PlatformRef = + createPlatformFactory( + platformCore, 'workerApp', [{provide: PLATFORM_ID, useValue: PLATFORM_WORKER_APP_ID}]); export function errorHandler(): ErrorHandler { return new ErrorHandler(); @@ -62,7 +61,7 @@ export function setupWebWorker(): void { @NgModule({ providers: [ BROWSER_SANITIZATION_PROVIDERS, - {provide: APP_ROOT, useValue: true}, + {provide: INJECTOR_SCOPE, useValue: 'root'}, Serializer, {provide: DOCUMENT, useValue: null}, ClientMessageBrokerFactory, diff --git a/tools/public_api_guard/common/common.d.ts b/tools/public_api_guard/common/common.d.ts index 98ea978376104..24cc338f19770 100644 --- a/tools/public_api_guard/common/common.d.ts +++ b/tools/public_api_guard/common/common.d.ts @@ -178,9 +178,9 @@ export declare class Location { prepareExternalUrl(url: string): string; replaceState(path: string, query?: string, state?: any): void; subscribe(onNext: (value: PopStateEvent) => void, onThrow?: ((exception: any) => void) | null, onReturn?: (() => void) | null): SubscriptionLike; - static joinWithSlash(start: string, end: string): string; - static normalizeQueryParams(params: string): string; - static stripTrailingSlash(url: string): string; + static joinWithSlash: (start: string, end: string) => string; + static normalizeQueryParams: (params: string) => string; + static stripTrailingSlash: (url: string) => string; } export declare const LOCATION_INITIALIZED: InjectionToken>; diff --git a/tools/public_api_guard/platform-webworker/platform-webworker.d.ts b/tools/public_api_guard/platform-webworker/platform-webworker.d.ts index 8293c24d7af82..2a3f72ab587ea 100644 --- a/tools/public_api_guard/platform-webworker/platform-webworker.d.ts +++ b/tools/public_api_guard/platform-webworker/platform-webworker.d.ts @@ -77,25 +77,7 @@ export declare class UiArguments { export declare const VERSION: Version; /** @deprecated */ -export declare const WORKER_APP_LOCATION_PROVIDERS: ({ - provide: typeof PlatformLocation; - useClass: typeof WebWorkerPlatformLocation; - useFactory?: undefined; - multi?: undefined; - deps?: undefined; -} | { - provide: InjectionToken<(() => void)[]>; - useFactory: typeof appInitFnFactory; - multi: boolean; - deps: (typeof NgZone | typeof PlatformLocation)[]; - useClass?: undefined; -} | { - provide: InjectionToken>; - useFactory: typeof locationInitialized; - deps: (typeof PlatformLocation)[]; - useClass?: undefined; - multi?: undefined; -})[]; +export declare const WORKER_APP_LOCATION_PROVIDERS: StaticProvider[]; /** @deprecated */ export declare const WORKER_UI_LOCATION_PROVIDERS: StaticProvider[];