From 3932f61c11530933999f118cb7d4a947b522ed81 Mon Sep 17 00:00:00 2001 From: Chris Thielen Date: Mon, 11 Apr 2016 19:42:22 -0500 Subject: [PATCH] feat(ng2.location): Add initial HTML5 pushState support `provide(UrlStrategy, { useClass: HTML5UrlStrategy })` Closes #2688 --- src/ng2.ts | 1 + src/ng2/providers.ts | 11 +++--- src/ng2/services.ts | 86 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/ng2.ts b/src/ng2.ts index 9a4169692..d24097185 100644 --- a/src/ng2.ts +++ b/src/ng2.ts @@ -9,6 +9,7 @@ import "./justjs"; export * from "./ng2/interface"; export * from "./ng2/providers"; +export * from "./ng2/services"; export * from "./ng2/directives"; export * from "./ng2/viewsBuilder"; export * from "./ng2/uiRouterConfig"; diff --git a/src/ng2/providers.ts b/src/ng2/providers.ts index 6efc88b71..494769cf2 100644 --- a/src/ng2/providers.ts +++ b/src/ng2/providers.ts @@ -60,10 +60,13 @@ import {ng2ViewsBuilder, Ng2ViewConfig} from "./viewsBuilder"; import {Ng2ViewDeclaration} from "./interface"; import {UIRouterConfig} from "./uiRouterConfig"; import {UIRouterGlobals} from "../globals"; +import {UrlStrategy, HashUrlStrategy} from "./services"; -let uiRouterFactory = (routerConfig: UIRouterConfig) => { +let uiRouterFactory = (routerConfig: UIRouterConfig, urlStrategy: UrlStrategy) => { let router = new UIRouter(); + urlStrategy.init(); + router.viewService.viewConfigFactory("ng2", (node: Node, config: Ng2ViewDeclaration) => new Ng2ViewConfig(node, config)); router.stateRegistry.decorator('views', ng2ViewsBuilder); @@ -93,8 +96,7 @@ let uiRouterFactory = (routerConfig: UIRouterConfig) => { * ``` */ export const UIROUTER_PROVIDERS: Provider[] = [ - - provide(UIRouter, { useFactory: uiRouterFactory, deps: [UIRouterConfig] }), + provide(UIRouter, { useFactory: uiRouterFactory, deps: [UIRouterConfig, UrlStrategy] }), provide(StateService, { useFactory: (r: UIRouter) => { return r.stateService; }, deps: [UIRouter]}), @@ -110,7 +112,8 @@ export const UIROUTER_PROVIDERS: Provider[] = [ provide(UIRouterGlobals, { useFactory: (r: UIRouter) => { return r.globals; }, deps: [UIRouter]}), - provide(UiView.PARENT_INJECT, { useFactory: (r: StateRegistry) => { return { fqn: null, context: r.root() } }, deps: [StateRegistry]} ) + provide(UiView.PARENT_INJECT, { useFactory: (r: StateRegistry) => { return { fqn: null, context: r.root() } }, deps: [StateRegistry]} ), + provide(UrlStrategy, { useClass: HashUrlStrategy }) ]; diff --git a/src/ng2/services.ts b/src/ng2/services.ts index 7aa7f0e59..17dd33a45 100644 --- a/src/ng2/services.ts +++ b/src/ng2/services.ts @@ -1,38 +1,50 @@ +import {DOM} from "angular2/src/platform/dom/dom_adapter"; import {Injectable} from "angular2/core"; -import {PlatformLocation, LocationStrategy, PathLocationStrategy} from "angular2/router"; import {services} from "../common/coreservices"; import {isDefined} from "../common/predicates"; +const beforeAfterSubstr = char => str => { + if (!str) return ["", ""]; + let idx = str.indexOf(char); + if (idx === -1) return [str, ""]; + return [str.substr(0, idx), str.substr(idx + 1)]; +}; + +const splitHash = beforeAfterSubstr("#"); +const trimHashVal = (str) => str ? str.replace(/^#/, "") : ""; + + +export abstract class UrlStrategy { + abstract init(); +} + @Injectable() -export class UiLocationStrategy { +export class HashUrlStrategy extends UrlStrategy { private hashPrefix: string = '!'; - constructor(private locationStrategy: LocationStrategy, private _platformLocation: PlatformLocation) { - } - init() { let loc = services.location; + let location: Location = DOM.getLocation(); - loc.hash = () => this._platformLocation.hash; - loc.path = this.locationStrategy.path; + loc.hash = () => splitHash(trimHashVal(location.hash))[1]; + loc.path = () => splitHash(trimHashVal(location.hash))[0]; loc.search = () => location.search; loc.url = (url) => { - if(url) { + if(isDefined(url)) { location.hash = url; } return loc.path(); }; - loc.replace = this.locationStrategy.replaceState; - // should we use location.onPopState instead ? https://github.com/angular/angular/blob/d272f96e23f379e1b565435b3af010138e710ab9/modules/angular2/src/router/location/hash_location_strategy.ts#L61 - loc.onChange = this._platformLocation.onHashChange; + loc.replace = () => { console.log(new Error('$location.replace() not impl'))}; + loc.onChange = cb => window.addEventListener("hashchange", cb, false); let locCfg = services.locationConfig; locCfg.port = () => location.port; locCfg.protocol = () => location.protocol; locCfg.host = () => location.host; - locCfg.baseHref = this.locationStrategy.getBaseHref; - locCfg.html5Mode = () => this.locationStrategy instanceof PathLocationStrategy; // does it work ? + locCfg.baseHref = () => null; + locCfg.html5Mode = () => false; locCfg.hashPrefix = (newprefix: string): string => { if(isDefined(newprefix)) { this.hashPrefix = newprefix; @@ -41,3 +53,51 @@ export class UiLocationStrategy { }; } } + + +@Injectable() +export class HTML5UrlStrategy extends UrlStrategy { + baseHref: string; + + init() { + let loc = services.location; + let location: Location = DOM.getLocation(); + let history: History = DOM.getHistory(); + this.baseHref = DOM.getBaseHref() || ""; + + loc.hash = () => + trimHashVal(location.hash); + loc.path = () => { + let path = location.pathname; + let idx = path.indexOf(this.baseHref); + if (idx !== 0) throw new Error(`current url: ${path} does not start with tag ${this.baseHref}`); + return path.substr(this.baseHref.length); + }; + + loc.search = () => + location.search; + loc.url = (url) => { + if(isDefined(url) && url !== loc.url()) { + history.pushState(null, null, this.baseHref + url); + } + let hash = loc.hash(); + return loc.path() + (hash ? "#" + hash : ""); + }; + loc.replace = () => { console.log(new Error('$location.replace() not impl'))}; + loc.onChange = cb => window.addEventListener("popstate", cb, false); + + let locCfg = services.locationConfig; + + locCfg.port = () => location.port; + locCfg.protocol = () => location.protocol; + locCfg.host = () => location.host; + locCfg.baseHref = (baseHref?) => { + if (isDefined(baseHref)) { + this.baseHref = baseHref; + } + return this.baseHref + }; + locCfg.html5Mode = () => true; + locCfg.hashPrefix = () => null; + } +}