
Update the license headers throughout the repository to reference Google LLC rather than Google Inc, for the required license headers. PR Close #37205
744 lines
24 KiB
TypeScript
744 lines
24 KiB
TypeScript
/**
|
|
* @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 {Location, LocationStrategy, PlatformLocation} from '@angular/common';
|
|
import {UpgradeModule} from '@angular/upgrade/static';
|
|
import {ReplaySubject} from 'rxjs';
|
|
|
|
import {UrlCodec} from './params';
|
|
import {deepEqual, isAnchor, isPromise} from './utils';
|
|
|
|
const PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/;
|
|
const DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
|
|
const IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
|
const DEFAULT_PORTS: {[key: string]: number} = {
|
|
'http:': 80,
|
|
'https:': 443,
|
|
'ftp:': 21
|
|
};
|
|
|
|
/**
|
|
* Location service that provides a drop-in replacement for the $location service
|
|
* provided in AngularJS.
|
|
*
|
|
* @see [Using the Angular Unified Location Service](guide/upgrade#using-the-unified-angular-location-service)
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export class $locationShim {
|
|
private initalizing = true;
|
|
private updateBrowser = false;
|
|
private $$absUrl: string = '';
|
|
private $$url: string = '';
|
|
private $$protocol: string;
|
|
private $$host: string = '';
|
|
private $$port: number|null;
|
|
private $$replace: boolean = false;
|
|
private $$path: string = '';
|
|
private $$search: any = '';
|
|
private $$hash: string = '';
|
|
private $$state: unknown;
|
|
private $$changeListeners: [
|
|
((url: string, state: unknown, oldUrl: string, oldState: unknown, err?: (e: Error) => void) =>
|
|
void),
|
|
(e: Error) => void
|
|
][] = [];
|
|
|
|
private cachedState: unknown = null;
|
|
|
|
private urlChanges = new ReplaySubject<{newUrl: string, newState: unknown}>(1);
|
|
|
|
constructor(
|
|
$injector: any, private location: Location, private platformLocation: PlatformLocation,
|
|
private urlCodec: UrlCodec, private locationStrategy: LocationStrategy) {
|
|
const initialUrl = this.browserUrl();
|
|
|
|
let parsedUrl = this.urlCodec.parse(initialUrl);
|
|
|
|
if (typeof parsedUrl === 'string') {
|
|
throw 'Invalid URL';
|
|
}
|
|
|
|
this.$$protocol = parsedUrl.protocol;
|
|
this.$$host = parsedUrl.hostname;
|
|
this.$$port = parseInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
|
|
|
|
this.$$parseLinkUrl(initialUrl, initialUrl);
|
|
this.cacheState();
|
|
this.$$state = this.browserState();
|
|
|
|
this.location.onUrlChange((newUrl, newState) => {
|
|
this.urlChanges.next({newUrl, newState});
|
|
});
|
|
|
|
if (isPromise($injector)) {
|
|
$injector.then($i => this.initialize($i));
|
|
} else {
|
|
this.initialize($injector);
|
|
}
|
|
}
|
|
|
|
private initialize($injector: any) {
|
|
const $rootScope = $injector.get('$rootScope');
|
|
const $rootElement = $injector.get('$rootElement');
|
|
|
|
$rootElement.on('click', (event: any) => {
|
|
if (event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 ||
|
|
event.button === 2) {
|
|
return;
|
|
}
|
|
|
|
let elm: (Node&ParentNode)|null = event.target;
|
|
|
|
// traverse the DOM up to find first A tag
|
|
while (elm && elm.nodeName.toLowerCase() !== 'a') {
|
|
// ignore rewriting if no A tag (reached root element, or no parent - removed from document)
|
|
if (elm === $rootElement[0] || !(elm = elm.parentNode)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!isAnchor(elm)) {
|
|
return;
|
|
}
|
|
|
|
const absHref = elm.href;
|
|
const relHref = elm.getAttribute('href');
|
|
|
|
// Ignore when url is started with javascript: or mailto:
|
|
if (IGNORE_URI_REGEXP.test(absHref)) {
|
|
return;
|
|
}
|
|
|
|
if (absHref && !elm.getAttribute('target') && !event.isDefaultPrevented()) {
|
|
if (this.$$parseLinkUrl(absHref, relHref)) {
|
|
// We do a preventDefault for all urls that are part of the AngularJS application,
|
|
// in html5mode and also without, so that we are able to abort navigation without
|
|
// getting double entries in the location history.
|
|
event.preventDefault();
|
|
// update location manually
|
|
if (this.absUrl() !== this.browserUrl()) {
|
|
$rootScope.$apply();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.urlChanges.subscribe(({newUrl, newState}) => {
|
|
const oldUrl = this.absUrl();
|
|
const oldState = this.$$state;
|
|
this.$$parse(newUrl);
|
|
newUrl = this.absUrl();
|
|
this.$$state = newState;
|
|
const defaultPrevented =
|
|
$rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, newState, oldState)
|
|
.defaultPrevented;
|
|
|
|
// if the location was changed by a `$locationChangeStart` handler then stop
|
|
// processing this location change
|
|
if (this.absUrl() !== newUrl) return;
|
|
|
|
// If default was prevented, set back to old state. This is the state that was locally
|
|
// cached in the $location service.
|
|
if (defaultPrevented) {
|
|
this.$$parse(oldUrl);
|
|
this.state(oldState);
|
|
this.setBrowserUrlWithFallback(oldUrl, false, oldState);
|
|
this.$$notifyChangeListeners(this.url(), this.$$state, oldUrl, oldState);
|
|
} else {
|
|
this.initalizing = false;
|
|
$rootScope.$broadcast('$locationChangeSuccess', newUrl, oldUrl, newState, oldState);
|
|
this.resetBrowserUpdate();
|
|
}
|
|
if (!$rootScope.$$phase) {
|
|
$rootScope.$digest();
|
|
}
|
|
});
|
|
|
|
// update browser
|
|
$rootScope.$watch(() => {
|
|
if (this.initalizing || this.updateBrowser) {
|
|
this.updateBrowser = false;
|
|
|
|
const oldUrl = this.browserUrl();
|
|
const newUrl = this.absUrl();
|
|
const oldState = this.browserState();
|
|
let currentReplace = this.$$replace;
|
|
|
|
const urlOrStateChanged =
|
|
!this.urlCodec.areEqual(oldUrl, newUrl) || oldState !== this.$$state;
|
|
|
|
// Fire location changes one time to on initialization. This must be done on the
|
|
// next tick (thus inside $evalAsync()) in order for listeners to be registered
|
|
// before the event fires. Mimicing behavior from $locationWatch:
|
|
// https://github.com/angular/angular.js/blob/master/src/ng/location.js#L983
|
|
if (this.initalizing || urlOrStateChanged) {
|
|
this.initalizing = false;
|
|
|
|
$rootScope.$evalAsync(() => {
|
|
// Get the new URL again since it could have changed due to async update
|
|
const newUrl = this.absUrl();
|
|
const defaultPrevented =
|
|
$rootScope
|
|
.$broadcast('$locationChangeStart', newUrl, oldUrl, this.$$state, oldState)
|
|
.defaultPrevented;
|
|
|
|
// if the location was changed by a `$locationChangeStart` handler then stop
|
|
// processing this location change
|
|
if (this.absUrl() !== newUrl) return;
|
|
|
|
if (defaultPrevented) {
|
|
this.$$parse(oldUrl);
|
|
this.$$state = oldState;
|
|
} else {
|
|
// This block doesn't run when initalizing because it's going to perform the update to
|
|
// the URL which shouldn't be needed when initalizing.
|
|
if (urlOrStateChanged) {
|
|
this.setBrowserUrlWithFallback(
|
|
newUrl, currentReplace, oldState === this.$$state ? null : this.$$state);
|
|
this.$$replace = false;
|
|
}
|
|
$rootScope.$broadcast(
|
|
'$locationChangeSuccess', newUrl, oldUrl, this.$$state, oldState);
|
|
if (urlOrStateChanged) {
|
|
this.$$notifyChangeListeners(this.url(), this.$$state, oldUrl, oldState);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
this.$$replace = false;
|
|
});
|
|
}
|
|
|
|
private resetBrowserUpdate() {
|
|
this.$$replace = false;
|
|
this.$$state = this.browserState();
|
|
this.updateBrowser = false;
|
|
this.lastBrowserUrl = this.browserUrl();
|
|
}
|
|
|
|
private lastHistoryState: unknown;
|
|
private lastBrowserUrl: string = '';
|
|
private browserUrl(): string;
|
|
private browserUrl(url: string, replace?: boolean, state?: unknown): this;
|
|
private browserUrl(url?: string, replace?: boolean, state?: unknown) {
|
|
// In modern browsers `history.state` is `null` by default; treating it separately
|
|
// from `undefined` would cause `$browser.url('/foo')` to change `history.state`
|
|
// to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
|
|
if (typeof state === 'undefined') {
|
|
state = null;
|
|
}
|
|
|
|
// setter
|
|
if (url) {
|
|
let sameState = this.lastHistoryState === state;
|
|
|
|
// Normalize the inputted URL
|
|
url = this.urlCodec.parse(url).href;
|
|
|
|
// Don't change anything if previous and current URLs and states match.
|
|
if (this.lastBrowserUrl === url && sameState) {
|
|
return this;
|
|
}
|
|
this.lastBrowserUrl = url;
|
|
this.lastHistoryState = state;
|
|
|
|
// Remove server base from URL as the Angular APIs for updating URL require
|
|
// it to be the path+.
|
|
url = this.stripBaseUrl(this.getServerBase(), url) || url;
|
|
|
|
// Set the URL
|
|
if (replace) {
|
|
this.locationStrategy.replaceState(state, '', url, '');
|
|
} else {
|
|
this.locationStrategy.pushState(state, '', url, '');
|
|
}
|
|
|
|
this.cacheState();
|
|
|
|
return this;
|
|
// getter
|
|
} else {
|
|
return this.platformLocation.href;
|
|
}
|
|
}
|
|
|
|
// This variable should be used *only* inside the cacheState function.
|
|
private lastCachedState: unknown = null;
|
|
private cacheState() {
|
|
// This should be the only place in $browser where `history.state` is read.
|
|
this.cachedState = this.platformLocation.getState();
|
|
if (typeof this.cachedState === 'undefined') {
|
|
this.cachedState = null;
|
|
}
|
|
|
|
// Prevent callbacks fo fire twice if both hashchange & popstate were fired.
|
|
if (deepEqual(this.cachedState, this.lastCachedState)) {
|
|
this.cachedState = this.lastCachedState;
|
|
}
|
|
|
|
this.lastCachedState = this.cachedState;
|
|
this.lastHistoryState = this.cachedState;
|
|
}
|
|
|
|
/**
|
|
* This function emulates the $browser.state() function from AngularJS. It will cause
|
|
* history.state to be cached unless changed with deep equality check.
|
|
*/
|
|
private browserState(): unknown {
|
|
return this.cachedState;
|
|
}
|
|
|
|
private stripBaseUrl(base: string, url: string) {
|
|
if (url.startsWith(base)) {
|
|
return url.substr(base.length);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
private getServerBase() {
|
|
const {protocol, hostname, port} = this.platformLocation;
|
|
const baseHref = this.locationStrategy.getBaseHref();
|
|
let url = `${protocol}//${hostname}${port ? ':' + port : ''}${baseHref || '/'}`;
|
|
return url.endsWith('/') ? url : url + '/';
|
|
}
|
|
|
|
private parseAppUrl(url: string) {
|
|
if (DOUBLE_SLASH_REGEX.test(url)) {
|
|
throw new Error(`Bad Path - URL cannot start with double slashes: ${url}`);
|
|
}
|
|
|
|
let prefixed = (url.charAt(0) !== '/');
|
|
if (prefixed) {
|
|
url = '/' + url;
|
|
}
|
|
let match = this.urlCodec.parse(url, this.getServerBase());
|
|
if (typeof match === 'string') {
|
|
throw new Error(`Bad URL - Cannot parse URL: ${url}`);
|
|
}
|
|
let path =
|
|
prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname;
|
|
this.$$path = this.urlCodec.decodePath(path);
|
|
this.$$search = this.urlCodec.decodeSearch(match.search);
|
|
this.$$hash = this.urlCodec.decodeHash(match.hash);
|
|
|
|
// make sure path starts with '/';
|
|
if (this.$$path && this.$$path.charAt(0) !== '/') {
|
|
this.$$path = '/' + this.$$path;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers listeners for URL changes. This API is used to catch updates performed by the
|
|
* AngularJS framework. These changes are a subset of the `$locationChangeStart` and
|
|
* `$locationChangeSuccess` events which fire when AngularJS updates its internally-referenced
|
|
* version of the browser URL.
|
|
*
|
|
* It's possible for `$locationChange` events to happen, but for the browser URL
|
|
* (window.location) to remain unchanged. This `onChange` callback will fire only when AngularJS
|
|
* actually updates the browser URL (window.location).
|
|
*
|
|
* @param fn The callback function that is triggered for the listener when the URL changes.
|
|
* @param err The callback function that is triggered when an error occurs.
|
|
*/
|
|
onChange(
|
|
fn: (url: string, state: unknown, oldUrl: string, oldState: unknown) => void,
|
|
err: (e: Error) => void = (e: Error) => {}) {
|
|
this.$$changeListeners.push([fn, err]);
|
|
}
|
|
|
|
/** @internal */
|
|
$$notifyChangeListeners(
|
|
url: string = '', state: unknown, oldUrl: string = '', oldState: unknown) {
|
|
this.$$changeListeners.forEach(([fn, err]) => {
|
|
try {
|
|
fn(url, state, oldUrl, oldState);
|
|
} catch (e) {
|
|
err(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Parses the provided URL, and sets the current URL to the parsed result.
|
|
*
|
|
* @param url The URL string.
|
|
*/
|
|
$$parse(url: string) {
|
|
let pathUrl: string|undefined;
|
|
if (url.startsWith('/')) {
|
|
pathUrl = url;
|
|
} else {
|
|
// Remove protocol & hostname if URL starts with it
|
|
pathUrl = this.stripBaseUrl(this.getServerBase(), url);
|
|
}
|
|
if (typeof pathUrl === 'undefined') {
|
|
throw new Error(`Invalid url "${url}", missing path prefix "${this.getServerBase()}".`);
|
|
}
|
|
|
|
this.parseAppUrl(pathUrl);
|
|
|
|
if (!this.$$path) {
|
|
this.$$path = '/';
|
|
}
|
|
this.composeUrls();
|
|
}
|
|
|
|
/**
|
|
* Parses the provided URL and its relative URL.
|
|
*
|
|
* @param url The full URL string.
|
|
* @param relHref A URL string relative to the full URL string.
|
|
*/
|
|
$$parseLinkUrl(url: string, relHref?: string|null): boolean {
|
|
// When relHref is passed, it should be a hash and is handled separately
|
|
if (relHref && relHref[0] === '#') {
|
|
this.hash(relHref.slice(1));
|
|
return true;
|
|
}
|
|
let rewrittenUrl;
|
|
let appUrl = this.stripBaseUrl(this.getServerBase(), url);
|
|
if (typeof appUrl !== 'undefined') {
|
|
rewrittenUrl = this.getServerBase() + appUrl;
|
|
} else if (this.getServerBase() === url + '/') {
|
|
rewrittenUrl = this.getServerBase();
|
|
}
|
|
// Set the URL
|
|
if (rewrittenUrl) {
|
|
this.$$parse(rewrittenUrl);
|
|
}
|
|
return !!rewrittenUrl;
|
|
}
|
|
|
|
private setBrowserUrlWithFallback(url: string, replace: boolean, state: unknown) {
|
|
const oldUrl = this.url();
|
|
const oldState = this.$$state;
|
|
try {
|
|
this.browserUrl(url, replace, state);
|
|
|
|
// Make sure $location.state() returns referentially identical (not just deeply equal)
|
|
// state object; this makes possible quick checking if the state changed in the digest
|
|
// loop. Checking deep equality would be too expensive.
|
|
this.$$state = this.browserState();
|
|
} catch (e) {
|
|
// Restore old values if pushState fails
|
|
this.url(oldUrl);
|
|
this.$$state = oldState;
|
|
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private composeUrls() {
|
|
this.$$url = this.urlCodec.normalize(this.$$path, this.$$search, this.$$hash);
|
|
this.$$absUrl = this.getServerBase() + this.$$url.substr(1); // remove '/' from front of URL
|
|
this.updateBrowser = true;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the full URL representation with all segments encoded according to
|
|
* rules specified in
|
|
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
|
|
*
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
* let absUrl = $location.absUrl();
|
|
* // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
|
|
* ```
|
|
*/
|
|
absUrl(): string {
|
|
return this.$$absUrl;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current URL, or sets a new URL. When setting a URL,
|
|
* changes the path, search, and hash, and returns a reference to its own instance.
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
* let url = $location.url();
|
|
* // => "/some/path?foo=bar&baz=xoxo"
|
|
* ```
|
|
*/
|
|
url(): string;
|
|
url(url: string): this;
|
|
url(url?: string): string|this {
|
|
if (typeof url === 'string') {
|
|
if (!url.length) {
|
|
url = '/';
|
|
}
|
|
|
|
const match = PATH_MATCH.exec(url);
|
|
if (!match) return this;
|
|
if (match[1] || url === '') this.path(this.urlCodec.decodePath(match[1]));
|
|
if (match[2] || match[1] || url === '') this.search(match[3] || '');
|
|
this.hash(match[5] || '');
|
|
|
|
// Chainable method
|
|
return this;
|
|
}
|
|
|
|
return this.$$url;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the protocol of the current URL.
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
* let protocol = $location.protocol();
|
|
* // => "http"
|
|
* ```
|
|
*/
|
|
protocol(): string {
|
|
return this.$$protocol;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the protocol of the current URL.
|
|
*
|
|
* In contrast to the non-AngularJS version `location.host` which returns `hostname:port`, this
|
|
* returns the `hostname` portion only.
|
|
*
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
* let host = $location.host();
|
|
* // => "example.com"
|
|
*
|
|
* // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
|
|
* host = $location.host();
|
|
* // => "example.com"
|
|
* host = location.host;
|
|
* // => "example.com:8080"
|
|
* ```
|
|
*/
|
|
host(): string {
|
|
return this.$$host;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the port of the current URL.
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
* let port = $location.port();
|
|
* // => 80
|
|
* ```
|
|
*/
|
|
port(): number|null {
|
|
return this.$$port;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the path of the current URL, or changes the path and returns a reference to its own
|
|
* instance.
|
|
*
|
|
* Paths should always begin with forward slash (/). This method adds the forward slash
|
|
* if it is missing.
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
* let path = $location.path();
|
|
* // => "/some/path"
|
|
* ```
|
|
*/
|
|
path(): string;
|
|
path(path: string|number|null): this;
|
|
path(path?: string|number|null): string|this {
|
|
if (typeof path === 'undefined') {
|
|
return this.$$path;
|
|
}
|
|
|
|
// null path converts to empty string. Prepend with "/" if needed.
|
|
path = path !== null ? path.toString() : '';
|
|
path = path.charAt(0) === '/' ? path : '/' + path;
|
|
|
|
this.$$path = path;
|
|
|
|
this.composeUrls();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a map of the search parameters of the current URL, or changes a search
|
|
* part and returns a reference to its own instance.
|
|
*
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
|
|
* let searchObject = $location.search();
|
|
* // => {foo: 'bar', baz: 'xoxo'}
|
|
*
|
|
* // set foo to 'yipee'
|
|
* $location.search('foo', 'yipee');
|
|
* // $location.search() => {foo: 'yipee', baz: 'xoxo'}
|
|
* ```
|
|
*
|
|
* @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
|
|
* hash object.
|
|
*
|
|
* When called with a single argument the method acts as a setter, setting the `search` component
|
|
* of `$location` to the specified value.
|
|
*
|
|
* If the argument is a hash object containing an array of values, these values will be encoded
|
|
* as duplicate search parameters in the URL.
|
|
*
|
|
* @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number,
|
|
* then `paramValue`
|
|
* will override only a single search property.
|
|
*
|
|
* If `paramValue` is an array, it will override the property of the `search` component of
|
|
* `$location` specified via the first argument.
|
|
*
|
|
* If `paramValue` is `null`, the property specified via the first argument will be deleted.
|
|
*
|
|
* If `paramValue` is `true`, the property specified via the first argument will be added with no
|
|
* value nor trailing equal sign.
|
|
*
|
|
* @return {Object} The parsed `search` object of the current URL, or the changed `search` object.
|
|
*/
|
|
search(): {[key: string]: unknown};
|
|
search(search: string|number|{[key: string]: unknown}): this;
|
|
search(
|
|
search: string|number|{[key: string]: unknown},
|
|
paramValue: null|undefined|string|number|boolean|string[]): this;
|
|
search(
|
|
search?: string|number|{[key: string]: unknown},
|
|
paramValue?: null|undefined|string|number|boolean|string[]): {[key: string]: unknown}|this {
|
|
switch (arguments.length) {
|
|
case 0:
|
|
return this.$$search;
|
|
case 1:
|
|
if (typeof search === 'string' || typeof search === 'number') {
|
|
this.$$search = this.urlCodec.decodeSearch(search.toString());
|
|
} else if (typeof search === 'object' && search !== null) {
|
|
// Copy the object so it's never mutated
|
|
search = {...search};
|
|
// remove object undefined or null properties
|
|
for (const key in search) {
|
|
if (search[key] == null) delete search[key];
|
|
}
|
|
|
|
this.$$search = search;
|
|
} else {
|
|
throw new Error(
|
|
'LocationProvider.search(): First argument must be a string or an object.');
|
|
}
|
|
break;
|
|
default:
|
|
if (typeof search === 'string') {
|
|
const currentSearch = this.search();
|
|
if (typeof paramValue === 'undefined' || paramValue === null) {
|
|
delete currentSearch[search];
|
|
return this.search(currentSearch);
|
|
} else {
|
|
currentSearch[search] = paramValue;
|
|
return this.search(currentSearch);
|
|
}
|
|
}
|
|
}
|
|
this.composeUrls();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current hash fragment, or changes the hash fragment and returns a reference to
|
|
* its own instance.
|
|
*
|
|
* ```js
|
|
* // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
|
|
* let hash = $location.hash();
|
|
* // => "hashValue"
|
|
* ```
|
|
*/
|
|
hash(): string;
|
|
hash(hash: string|number|null): this;
|
|
hash(hash?: string|number|null): string|this {
|
|
if (typeof hash === 'undefined') {
|
|
return this.$$hash;
|
|
}
|
|
|
|
this.$$hash = hash !== null ? hash.toString() : '';
|
|
|
|
this.composeUrls();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Changes to `$location` during the current `$digest` will replace the current
|
|
* history record, instead of adding a new one.
|
|
*/
|
|
replace(): this {
|
|
this.$$replace = true;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the history state object when called without any parameter.
|
|
*
|
|
* Change the history state object when called with one parameter and return `$location`.
|
|
* The state object is later passed to `pushState` or `replaceState`.
|
|
*
|
|
* This method is supported only in HTML5 mode and only in browsers supporting
|
|
* the HTML5 History API methods such as `pushState` and `replaceState`. If you need to support
|
|
* older browsers (like IE9 or Android < 4.0), don't use this method.
|
|
*
|
|
*/
|
|
state(): unknown;
|
|
state(state: unknown): this;
|
|
state(state?: unknown): unknown|this {
|
|
if (typeof state === 'undefined') {
|
|
return this.$$state;
|
|
}
|
|
|
|
this.$$state = state;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The factory function used to create an instance of the `$locationShim` in Angular,
|
|
* and provides an API-compatiable `$locationProvider` for AngularJS.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export class $locationShimProvider {
|
|
constructor(
|
|
private ngUpgrade: UpgradeModule, private location: Location,
|
|
private platformLocation: PlatformLocation, private urlCodec: UrlCodec,
|
|
private locationStrategy: LocationStrategy) {}
|
|
|
|
/**
|
|
* Factory method that returns an instance of the $locationShim
|
|
*/
|
|
$get() {
|
|
return new $locationShim(
|
|
this.ngUpgrade.$injector, this.location, this.platformLocation, this.urlCodec,
|
|
this.locationStrategy);
|
|
}
|
|
|
|
/**
|
|
* Stub method used to keep API compatible with AngularJS. This setting is configured through
|
|
* the LocationUpgradeModule's `config` method in your Angular app.
|
|
*/
|
|
hashPrefix(prefix?: string) {
|
|
throw new Error('Configure LocationUpgrade through LocationUpgradeModule.config method.');
|
|
}
|
|
|
|
/**
|
|
* Stub method used to keep API compatible with AngularJS. This setting is configured through
|
|
* the LocationUpgradeModule's `config` method in your Angular app.
|
|
*/
|
|
html5Mode(mode?: any) {
|
|
throw new Error('Configure LocationUpgrade through LocationUpgradeModule.config method.');
|
|
}
|
|
}
|