在介绍 LocationStrategy 策略以前,咱们先来了解如下相关知识:javascript
只读的,其值为一个整数,标志包括当前页面在内的会话历史中的记录数量,好比咱们一般打开一个空白窗口,length 为 0,再访问一个页面,其 length 变为 1。html
容许 Web 应用在会话历史导航时显式地设置默认滚动复原,其值为 auto 或 manual。java
只读,返回表明会话历史堆栈顶部记录的任意可序列化类型数据值,咱们能够以此来区别不一样会话历史纪录。typescript
返回会话历史记录中的上一个页面,等价于 window.history.go(-1) 和点击浏览器的后退按钮。segmentfault
进入会话历史记录中的下一个页面,等价于 window.history.go(1) 和点击浏览器的前进按钮。浏览器
加载会话历史记录中的某一个页面,经过该页面与当前页面在会话历史中的相对位置定位,如,-1
表明当前页面的上一个记录,1
表明当前页面的下一个页面。若不传参数或传入0,则会从新加载当前页面;若参数超出当前会话历史纪录数,则不进行操做。服务器
在会话历史堆栈顶部插入一条记录,该方法接收三个参数,一个 state 对象,一个页面标题,一个 URL:angular2
更新会话历史堆栈顶部记录信息,支持的参数信息与 pushState()
一致。dom
pushState() 与 replaceState() 的区别:pushState()是在 history 栈中添加一个新的条目,replaceState() 是替换当前的记录值。此外这两个方法改变的只是浏览器关于当前页面的标题和 URL 的记录状况,并不会刷新或改变页面展现。angular4
window.onpopstate 是 popstate
事件在 window 对象上的事件句柄。每当处于激活状态的历史记录条目发生变化时,popstate 事件就会在对应 window 对象上触发。若是当前处于激活状态的历史记录条目是由 history.pushState() 方法建立,或者由 history.replaceState() 方法修改过的,则 popstate 事件对象的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。
调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,好比点击后退、前进按钮 (或者在 JavaScript 中调用 history.back()、history.forward()、history.go() 方法)。
当网页加载时,各浏览器对 popstate 事件是否触发有不一样的表现,Chrome 和 Safari 会触发 popstate 事件,而 Firefox 不会。
Hash 模式是基于锚点定位的内部连接机制,在 URL 加上 #
,而后在 #
后面加上 hash 标签,根据不一样的标签作定位。示例以下:
https://segmentfault.com/u/angular4#user复制代码
导入 HashLocationStrategy 及 HashLocationStrategy
import { LocationStrategy, HashLocationStrategy } from '@angular/common';复制代码
配置 NgModule - providers
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routes)
],
...,
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy }
]
})复制代码
友情提示:URL 中包含的 hash 信息是不会提交到服务端,因此若要使用 SSR (Server-Side Rendered) ,就不能使用 Hash 模式即不能使用 HashLocationStrategy 策略。
HTML 5 模式则直接使用跟"真实"的 URL 同样,如上面的路径,在 HTML 5 模式地址以下:
https://segmentfault.com/u/angular4/user复制代码
HTML 5 模式下 URL 有两种访问方式:
在 HTML 5 模式下,Angular 使用了 HTML 5 的 pushState()
API 来动态改变浏览器的 URL 而不用从新刷新页面。
导入 APP_BASE_HREF、LocationStrategy、PathLocationStrategy
import { APP_BASE_HREF, LocationStrategy, PathLocationStrategy } from '@angular/common';复制代码
配置 NgModule - providers
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routes)
],
..,
providers: [
{ provide: LocationStrategy, useClass: PathLocationStrategy },
{ provide: APP_BASE_HREF, useValue: '/' }
]
})复制代码
示例代码中的 APP_BASE_HREF
,用于设置资源 (图片、脚本、样式) 加载的基础路径。除了在 NgModule 中配置 provider
外,咱们也能够在入口文件,如 index.html
文件 <base>
标签中设置基础路径。
<base>
标签为页面上的全部连接规定默认地址或默认目标。一般状况下,浏览器会从当前文档的 URL 中提取相应的路径来补全相对 URL 中缺失的部分。使用 <base>
标签能够改变这一点。浏览器随后将再也不使用当前文档的 URL,而使用指定的基本 URL 来解析全部的相对 URL。这其中包括<a>
、<img>
、<link>
、<form>
标签中的 URL。具体使用示例以下:
<base href="/">复制代码
LocationStrategy 用于从浏览器 URL 中读取路由状态。Angular 中提供两种 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, title: string, url: string,
queryParams: string): void;
// 进入会话历史记录中的下一个页面
abstract forward(): void;
// 返回会话历史记录中的上一个页面
abstract back(): void;
// 设置popstate监听
abstract onPopState(fn: LocationChangeListener): void;
// 获取base地址信息
abstract getBaseHref(): string;
}复制代码
了解完 LocationStrategy 抽象类,接下来咱们先来介绍 HashLocationStrategy 策略。
HashLocationStrategy 类继承于 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 关联的对象。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 构造函数中,咱们调用 _init()
方法,在方法体中,咱们调用 getDOM()
方法返回对象中的 getLocation()
和 getHistory()
方法,分别获取 Location 对象和 History 对象。那 getDOM() 方法返回的是什么对象呢?其实该方法返回的是 DomAdapter
对象。
let _DOM: DomAdapter = null !;
export function getDOM() {
return _DOM;
}
export function setDOM(adapter: DomAdapter) {
_DOM = adapter;
}
export function setRootDomAdapter(adapter: DomAdapter) {
if (!_DOM) {
_DOM = adapter;
}
}复制代码
那何时会调用 setDOM()
或 setRootDomAdapter()
方法呢?经过查看 Angular 源码,咱们发如今浏览器平台初始化时,会调用 setRootDomAdapter()
方法。具体以下:
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[] = [
{provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true},
...
];复制代码
export function initDomAdapter() {
BrowserDomAdapter.makeCurrent();
BrowserGetTestability.init();
}复制代码
从上面代码中,能够看出在 initDomAdapter() 方法中,咱们又调用了 BrowserDomAdapter 类提供的静态方法 makeCurrent()
,该方法的实现以下:
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); }
}复制代码
如今咱们已经知道调用 getDom()
方法后,咱们得到的是 BrowserDomAdapter 对象。该对象为咱们提供 getLocation()
和 getHistory()
方法,用于获取 Location 和 History 对象。以上两个方法的具体实现以下:
getHistory(): History { return window.history; }
getLocation(): Location { return window.location; }复制代码
此外该对象中还包含一个 getBaseHref()
方法,用于获取基础路径:
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 类的构造函数,咱们再来分析该类中几个重要的方法:
// 用于获取base元素的href属性
getBaseHrefFromDOM(): string { return getDOM().getBaseHref(this._doc) !; }复制代码
// 设置popstate事件的监听函数
onPopState(fn: LocationChangeListener): void {
getDOM().getGlobalEventTarget(this._doc, 'window')
.addEventListener('popstate', fn, false);
}
interface LocationChangeListener { (e: LocationChangeEvent): any; }
interface LocationChangeEvent { type: string; }复制代码
// 设置hashchange事件的监听函数
onHashChange(fn: LocationChangeListener): void {
getDOM().getGlobalEventTarget(this._doc, 'window')
.addEventListener('hashchange', fn, false);
}复制代码
// 添加会话历史状态
pushState(state: any, title: string, 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(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(); }复制代码
如今终于介绍完 PlatformLocation
对象,让咱们回过头来继续分析咱们的主角 - HashLocationStrategy 类。前面咱们已经分析了该类的构造函数,咱们再来看一下该类其它的方法:
// 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, title: string, 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, title, url);
}
// 更新会话历史状态
replaceState(state: any, title: string, path: string, queryParams: string) {
let url = this.prepareExternalUrl(path +
Location.normalizeQueryParams(queryParams));
if (url.length == 0) {
url = this._platformLocation.pathname;
}
this._platformLocation.replaceState(state, title, url);
}
// 进入会话历史记录中的下一个页面
forward(): void { this._platformLocation.forward(); }
// 进入会话历史记录中的上一个页面
back(): void { this._platformLocation.back(); }
}复制代码
到如今为止,咱们已经完整分析了 HashLocationStrategy 策略。最后咱们来分析 PathLocationStrategy 策略。
PathLocationStrategy 类也是继承于 LocationStrategy 抽象类,若是使用该策略,咱们必须设置 APP_BASE_HREF
或在入口文件如 (index.html) 文件中设置 <base>
元素的 href 属性。咱们也先来分析该类的构造函数:
// 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, title: string, url: string, queryParams: string) {
// normalizeQueryParams():该方法会判断queryParams是否包含'?'
// 字符,若不包含,则自动添加'?'字符。
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(); }
}复制代码
终于介绍完 HashLocationStrategy 和 PathLocationStrategy 策略,后续的文章,咱们会基于该基础,深刻分析 Angular 的路由模块。