Angular 4.x LocationStrategy
在介绍 LocationStrategy 策略之前,我们先来了解以下相关知识:
History 对象属性length只读的,其值为一个整数,标志包括当前页面在内的会话历史中的记录数量,比如我们通常打开一个空白窗口,length 为 0,再访问一个页面,其 length 变为 1。 scrollRestoration允许 Web 应用在会话历史导航时显式地设置默认滚动复原,其值为 auto 或 manual。 state只读,返回代表会话历史堆栈顶部记录的任意可序列化类型数据值,我们可以以此来区别不同会话历史纪录。 方法back()返回会话历史记录中的上一个页面,等价于 window.history.go(-1) 和点击浏览器的后退按钮。 forward()进入会话历史记录中的下一个页面,等价于 window.history.go(1) 和点击浏览器的前进按钮。 go()加载会话历史记录中的某一个页面,通过该页面与当前页面在会话历史中的相对位置定位,如, pushState()在会话历史堆栈顶部插入一条记录,该方法接收三个参数,一个 state 对象,一个页面标题,一个 URL:
replaceState()更新会话历史堆栈顶部记录信息,支持的参数信息与 pushState() 与 replaceState() 的区别:pushState()是在 history 栈中添加一个新的条目,replaceState() 是替换当前的记录值。此外这两个方法改变的只是浏览器关于当前页面的标题和 URL 的记录情况,并不会刷新或改变页面展示。 onpopstate 事件window.onpopstate 是 调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击后退、前进按钮 (或者在 JavaScript 中调用 history.back()、history.forward()、history.go() 方法)。 当网页加载时,各浏览器对 popstate 事件是否触发有不同的表现,Chrome 和 Safari 会触发 popstate 事件,而 Firefox 不会。 Hash 模式和 HTML 5 模式Hash 模式Hash 模式是基于锚点定位的内部链接机制,在 URL 加上 https://segmentfault.com/u/angular4#user 开启 Hash 模式导入 HashLocationStrategy 及 HashLocationStrategy import { LocationStrategy,HashLocationStrategy } from '@angular/common'; 配置 NgModule - providers @NgModule({ imports: [ BrowserModule,RouterModule.forRoot(routes) ],...,providers: [ { provide: LocationStrategy,useClass: HashLocationStrategy } ] })
HTML 5 模式HTML 5 模式则直接使用跟"真实"的 URL 一样,如上面的路径,在 HTML 5 模式地址如下: https://segmentfault.com/u/angular4/user HTML 5 模式下 URL 有两种访问方式:
在 HTML 5 模式下,Angular 使用了 HTML 5 的 开启 HTML 5 模式导入 APP_BASE_HREF、LocationStrategy、PathLocationStrategy import { APP_BASE_HREF,LocationStrategy,PathLocationStrategy } from '@angular/common'; 配置 NgModule - providers @NgModule({ imports: [ BrowserModule,..,useClass: PathLocationStrategy },{ provide: APP_BASE_HREF,useValue: '/' } ] }) 示例代码中的
<base href="/"> LocationStrategyLocationStrategy 用于从浏览器 URL 中读取路由状态。Angular 中提供两种 LocationStrategy 策略:
以上两种策略都是继承于 LocationStrategy 抽象类,该类的具体定义如下: LocationStrategy 抽象类export abstract class LocationStrategy { // 获取path路径 abstract path(includeHash?: boolean): string; // 生成完整的外部链接 abstract prepareExternalUrl(internal: string): string; // 添加会话历史状态 abstract pushState(state: any,title: string,url: string,queryParams: string): void; // 修改会话历史状态 abstract replaceState(state: any,queryParams: string): void; // 进入会话历史记录中的下一个页面 abstract forward(): void; // 返回会话历史记录中的上一个页面 abstract back(): void; // 设置popstate监听 abstract onPopState(fn: LocationChangeListener): void; // 获取base地址信息 abstract getBaseHref(): string; } 了解完 LocationStrategy 抽象类,接下来我们先来介绍 HashLocationStrategy 策略。 HashLocationStrategyHashLocationStrategy 类继承于 LocationStrategy 抽象类,它的构造函数如下: export class HashLocationStrategy extends LocationStrategy { constructor( private _platformLocation: PlatformLocation,@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) { super(); if (_baseHref != null) { this._baseHref = _baseHref; } } } 该构造函数依赖 PlatformLocation 及 APP_BASE_HREF 关联的对象。 PlatformLocation// angular2/packages/platform-browser/src/browser.ts export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [ ...,{provide: PlatformLocation,useClass: BrowserPlatformLocation},]; 通过以上代码,我们可以知道在浏览器环境中,HashLocationStrategy 构造函数中注入的 PlatformLocation 对象是 BrowserPlatformLocation 类的实例。我们也先来看一下 BrowserPlatformLocation 类的构造函数: // angular2/packages/platform-browser/src/browser/location/browser_platform_location.ts export class BrowserPlatformLocation extends PlatformLocation { private _location: Location; private _history: History; constructor(@Inject(DOCUMENT) private _doc: any) { super(); this._init(); } _init() { this._location = getDOM().getLocation(); // 获取浏览器平台下Location对象 this._history = getDOM().getHistory(); // 获取浏览器平台下的History对象 } } 在 BrowserPlatformLocation 构造函数中,我们调用 DomAdapterlet _DOM: DomAdapter = null !; export function getDOM() { return _DOM; } export function setDOM(adapter: DomAdapter) { _DOM = adapter; } export function setRootDomAdapter(adapter: DomAdapter) { if (!_DOM) { _DOM = adapter; } } 那什么时候会调用 export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [ {provide: PLATFORM_INITIALIZER,useValue: initDomAdapter,multi: true},... ]; initDomAdapter() 方法export function initDomAdapter() { BrowserDomAdapter.makeCurrent(); BrowserGetTestability.init(); } 从上面代码中,可以看出在 initDomAdapter() 方法中,我们又调用了 BrowserDomAdapter 类提供的静态方法 export class BrowserDomAdapter extends GenericBrowserDomAdapter { static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); } } 现在我们已经知道调用 getHistory(): History { return window.history; } getLocation(): Location { return window.location; } 此外该对象中还包含一个 getBaseHref(doc: Document): string|null { const href = getBaseElementHref(); return href == null ? null : relativePath(href); } // 获取入口文件中base元素的href属性值 function getBaseElementHref(): string|null { if (!baseElement) { baseElement = document.querySelector('base') !; if (!baseElement) { return null; } } return baseElement.getAttribute('href'); } 分析完 BrowserPlatformLocation 类的构造函数,我们再来分析该类中几个重要的方法: getBaseHrefFromDOM()// 用于获取base元素的href属性 getBaseHrefFromDOM(): string { return getDOM().getBaseHref(this._doc) !; } onPopState()// 设置popstate事件的监听函数 onPopState(fn: LocationChangeListener): void { getDOM().getGlobalEventTarget(this._doc,'window') .addEventListener('popstate',fn,false); } interface LocationChangeListener { (e: LocationChangeEvent): any; } interface LocationChangeEvent { type: string; } onHashChange()// 设置hashchange事件的监听函数 onHashChange(fn: LocationChangeListener): void { getDOM().getGlobalEventTarget(this._doc,'window') .addEventListener('hashchange',false); } pushState()// 添加会话历史状态 pushState(state: any,url: string): void { if (supportsState()) { this._history.pushState(state,title,url); } else { this._location.hash = url; } } // 判断是否支持state相关API export function supportsState(): boolean { return !!window.history.pushState; } replaceState()// 修改会话历史状态 replaceState(state: any,url: string): void { if (supportsState()) { this._history.replaceState(state,url); } else { this._location.hash = url; } } forward()// 进入会话历史记录中的下一个页面 forward(): void { this._history.forward(); } back()// 进入会话历史记录中的上一个页面 back(): void { this._history.back(); } 现在终于介绍完 // angular2/packages/common/src/location/hash_location_strategy.ts export class HashLocationStrategy extends LocationStrategy { private _baseHref: string = ''; // 用于保存base URL地址 onPopState(fn: LocationChangeListener): void { this._platformLocation.onPopState(fn); this._platformLocation.onHashChange(fn); } // 获取基础路径 getBaseHref(): string { return this._baseHref; } // 获取hash路径 path(includeHash: boolean = false): string { // the hash value is always prefixed with a `#` // and if it is empty then it will stay empty let path = this._platformLocation.hash; if (path == null) path = '#'; return path.length > 0 ? path.substring(1) : path; } // 基于_baseHref及internal值,生成完整的URL地址 prepareExternalUrl(internal: string): string { // joinWithSlash():该方法会判断_baseHref和internal是否含有'/' // 字符,然后自动帮我们拼接成合法的URL地址 const url = Location.joinWithSlash(this._baseHref,internal); return url.length > 0 ? ('#' + url) : url; } // 添加会话历史状态 pushState(state: any,path: string,queryParams: string) { // normalizeQueryParams():该方法会判断queryParams是否包含'?' // 字符,若不包含,则自动添加'?'字符。 let url: string|null = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); if (url.length == 0) { url = this._platformLocation.pathname; } this._platformLocation.pushState(state,url); } // 更新会话历史状态 replaceState(state: any,queryParams: string) { let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); if (url.length == 0) { url = this._platformLocation.pathname; } this._platformLocation.replaceState(state,url); } // 进入会话历史记录中的下一个页面 forward(): void { this._platformLocation.forward(); } // 进入会话历史记录中的上一个页面 back(): void { this._platformLocation.back(); } } 到现在为止,我们已经完整分析了 HashLocationStrategy 策略。最后我们来分析 PathLocationStrategy 策略。 PathLocationStrategyPathLocationStrategy 类也是继承于 LocationStrategy 抽象类,如果使用该策略,我们必须设置 // angular2/packages/common/src/location/path_location_strategy.ts export class PathLocationStrategy extends LocationStrategy { private _baseHref: string; constructor( private _platformLocation: PlatformLocation,@Optional() @Inject(APP_BASE_HREF) href?: string) { super(); if (href == null) { // 若未设置APP_BASE_HREF的值,则从base元素中 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; } } PathLocationStrategy 类其它的方法: export class PathLocationStrategy extends LocationStrategy { // ... onPopState(fn: LocationChangeListener): void { this._platformLocation.onPopState(fn); this._platformLocation.onHashChange(fn); } // 获取基础路径 getBaseHref(): string { return this._baseHref; } // 基于_baseHref及internal值,生成完整的URL地址 prepareExternalUrl(internal: string): string { return Location.joinWithSlash(this._baseHref,internal); } // 根据传递的参数值,返回path(包含或不包含hash值)的路径 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,queryParams: string) { // normalizeQueryParams():该方法会判断queryParams是否包含'?' // 字符,若不包含,则自动添加'?'字符。 const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); this._platformLocation.pushState(state,externalUrl); } // 更新会话历史状态 replaceState(state: any,queryParams: string) { const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); this._platformLocation.replaceState(state,externalUrl); } // 进入会话历史记录中的下一个页面 forward(): void { this._platformLocation.forward(); } // 进入会话历史记录中的上一个页面 back(): void { this._platformLocation.back(); } } 终于介绍完 HashLocationStrategy 和 PathLocationStrategy 策略,后续的文章,我们会基于该基础,深入分析 Angular 的路由模块。 参考文章
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |