feat(common): add ability to watch for AngularJS URL updates through `onUrlChange` hook (#30466)
The LocationShim (replacement for `$location`) was added to centralize dealing with the browser URL. Additionally, an `onUrlChange` method was added to Angular's Location service. This PR adds a corresponding method to the LocationShim so updates from AngularJS can be tracked in Angular. PR Close #30466
This commit is contained in:
parent
077809398c
commit
1aff524b63
|
@ -39,9 +39,16 @@ export class $locationShim {
|
|||
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;
|
||||
|
||||
|
||||
|
||||
constructor(
|
||||
$injector: any, private location: Location, private platformLocation: PlatformLocation,
|
||||
private urlCodec: UrlCodec, private locationStrategy: LocationStrategy) {
|
||||
|
@ -313,6 +320,32 @@ export class $locationShim {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register URL change listeners. This API can be used to catch updates performed by the
|
||||
* AngularJS framework. These changes are a subset of the `$locationChangeStart/Success` events
|
||||
* as those events fire when AngularJS updates it's 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).
|
||||
*/
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$$parse(url: string) {
|
||||
let pathUrl: string|undefined;
|
||||
if (url.startsWith('/')) {
|
||||
|
@ -363,6 +396,7 @@ export class $locationShim {
|
|||
// 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();
|
||||
this.$$notifyChangeListeners(url, state, oldUrl, oldState);
|
||||
} catch (e) {
|
||||
// Restore old values if pushState fails
|
||||
this.url(oldUrl);
|
||||
|
|
|
@ -624,6 +624,87 @@ describe('New URL Parsing', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('$location.onChange()', () => {
|
||||
|
||||
let $location: $locationShim;
|
||||
let upgradeModule: UpgradeModule;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
LocationUpgradeTestModule.config({useHash: false, startUrl: 'http://host.com/'}),
|
||||
],
|
||||
providers: [UpgradeModule],
|
||||
});
|
||||
|
||||
upgradeModule = TestBed.get(UpgradeModule);
|
||||
upgradeModule.$injector = {get: injectorFactory()};
|
||||
});
|
||||
|
||||
beforeEach(inject([$locationShim], (loc: $locationShim) => { $location = loc; }));
|
||||
|
||||
it('should have onChange method', () => { expect(typeof $location.onChange).toBe('function'); });
|
||||
|
||||
it('should add registered functions to changeListeners', () => {
|
||||
|
||||
function changeListener(url: string, state: unknown) { return undefined; }
|
||||
function errorHandler(e: Error) {}
|
||||
|
||||
expect(($location as any).$$changeListeners.length).toBe(0);
|
||||
|
||||
$location.onChange(changeListener, errorHandler);
|
||||
|
||||
expect(($location as any).$$changeListeners.length).toBe(1);
|
||||
expect(($location as any).$$changeListeners[0][0]).toEqual(changeListener);
|
||||
expect(($location as any).$$changeListeners[0][1]).toEqual(errorHandler);
|
||||
});
|
||||
|
||||
it('should call changeListeners when URL is updated', () => {
|
||||
|
||||
const onChangeVals =
|
||||
{url: 'url', state: 'state' as unknown, oldUrl: 'oldUrl', oldState: 'oldState' as unknown};
|
||||
|
||||
function changeListener(url: string, state: unknown, oldUrl: string, oldState: unknown) {
|
||||
onChangeVals.url = url;
|
||||
onChangeVals.state = state;
|
||||
onChangeVals.oldUrl = oldUrl;
|
||||
onChangeVals.oldState = oldState;
|
||||
}
|
||||
|
||||
$location.onChange(changeListener);
|
||||
|
||||
// Mock out setting browserUrl
|
||||
($location as any).browserUrl = (url: string, replace: boolean, state: unknown) => {};
|
||||
|
||||
const newState = {foo: 'bar'};
|
||||
($location as any).setBrowserUrlWithFallback('/newUrl', false, newState);
|
||||
expect(onChangeVals.url).toBe('/newUrl');
|
||||
expect(onChangeVals.state).toBe(newState);
|
||||
expect(onChangeVals.oldUrl).toBe('/');
|
||||
expect(onChangeVals.oldState).toBe(null);
|
||||
});
|
||||
|
||||
it('should call forward errors to error handler', () => {
|
||||
|
||||
let error !: Error;
|
||||
|
||||
function changeListener(url: string, state: unknown, oldUrl: string, oldState: unknown) {
|
||||
throw new Error('Handle error');
|
||||
}
|
||||
function errorHandler(e: Error) { error = e; }
|
||||
|
||||
$location.onChange(changeListener, errorHandler);
|
||||
|
||||
// Mock out setting browserUrl
|
||||
($location as any).browserUrl = (url: string, replace: boolean, state: unknown) => {};
|
||||
|
||||
($location as any).setBrowserUrlWithFallback('/newUrl');
|
||||
expect(error.message).toBe('Handle error');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function parseLinkAndReturn(location: $locationShim, toUrl: string, relHref?: string) {
|
||||
const resetUrl = location.$$parseLinkUrl(toUrl, relHref);
|
||||
return resetUrl && location.absUrl() || undefined;
|
||||
|
|
|
@ -6,6 +6,7 @@ export declare class $locationShim {
|
|||
hash(hash: string | number | null): this;
|
||||
hash(): string;
|
||||
host(): string;
|
||||
onChange(fn: (url: string, state: unknown, oldUrl: string, oldState: unknown) => void, err?: (e: Error) => void): void;
|
||||
path(): string;
|
||||
path(path: string | number | null): this;
|
||||
port(): number | null;
|
||||
|
|
Loading…
Reference in New Issue