迈向angularjs2系列(6):路由机制
目录
1.angular-seed的路由
2.路由机制的探索
3.懒加载
一:angular-seed的路由
step1:安装种子项目
$ git clone --depth 1 https://github.com/AngularClass/angular2-seed.git developer-hub
//git cmd进入到项目目录后运行此命令,我的项目的名称为developer-hub
$ cd developer-hub //webstorm命令行进入项目目录
$ npm install //json文件修改好之后再运行install命令
$ npm start
"compilerOptions": { "lib": ["es6", "dom"] }
● dist放最后生产用的文件
● node-modules放node模块
● src 源代码
● .gitignore文件是git用于配置不需要加入版本管理的文件,比如最后两行是
.awcache
.DS_Store
●LICENSE,我的是
Apache License
Version 2.0, January 2004
●package.json
{ "name": "angular2-seed", "version": "0.0.0", "description": "A simple Angular 2 Seed featuring Angular 2 and Webpack 2", "author": "PatrickJS <github@gdi2290.com>",//作者 "main": "index.js", //主入口 "files": [ "dist", "src" ], "scripts": {//npm脚本 "test": "echo \"Error: no test specified\" && exit 1", "start": "npm run server:dev", "start:hmr": "npm run server:dev -- --env.HMR", "server:dev": "webpack-dev-server --env.ENV development", "debug:start": "node-nightly --inspect --debug-brk node_modules/webpack-dev-server/bin/webpack-dev-server.js --env.ENV development", "debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.ENV development", "predll": "rimraf dist/dll/*.*", "dll": "webpack --config webpack.dll.ts --env.ENV development", "prebuild": "rimraf dist/*.*", "build": "webpack --env.ENV development", "build:hmr": "npm run build -- --env.HMR" }, "dependencies": { //项目 "@angular/common": "~2.0.1", "@angular/compiler": "~2.0.1", "@angular/compiler-cli": "~0.6.3", "@angular/core": "~2.0.1", "@angular/http": "~2.0.1", "@angular/forms": "~2.0.1", "@angular/router": "~3.0.1", "@angular/platform-browser": "~2.0.1", "@angular/platform-browser-dynamic": "~2.0.1", "@angular/platform-server": "~2.0.1", "@angularclass/conventions-loader": "^1.0.12", "@angularclass/form-validators": "^1.0.11", "@angularclass/resolve-angular-routes": "^1.0.8", "@angularclass/hmr-loader": "~3.0.1", "@angularclass/hmr": "~1.2.0", "core-js": "^2.4.1", "rxjs": "5.0.0-beta.12", "zone.js": "~0.6.25" }, "devDependencies": { //dev项目依赖 "@types/core-js": "^0.9.28", "@types/node": "^4.0.30", "assets-webpack-plugin": "^3.4.0", "awesome-typescript-loader": "^2.2.1", "cross-spawn": "^4.0.0", "es6-promise": "^3.1.2", "es6-shim": "^0.35.0", "ie-shim": "^0.1.0", "ignore-loader": "^0.1.1", "json-loader": "^0.5.4", "raw-loader": "^0.5.1", "rimraf": "^2.5.4", "string-replace-loader": "github:gdi2290/string-replace-loader", "to-string-loader": "^1.1.4", "ts-helpers": "github:gdi2290/ts-helpers", "ts-loader": "^0.8.2", "ts-node": "^1.2.2", "typescript": "2.0.2", "webpack": "~2.1.0-beta.25", "webpack-dev-middleware": "^1.6.1", "webpack-dev-server": "^2.1.0-beta.8" }, "license": "Apache-2.0", //许可证 "bugs": { "url": "https://github.com/gdi2290/angular2-webpack2-starter/issues" }, "homepage": "https://github.com/gdi2290/angular2-webpack2-starter#readme", //主页 "repository": { //仓库 "type": "git", "url": "git+https://github.com/gdi2290/angular2-webpack2-starter.git" } }
说明1:main选项是主入口模块的ID
说明2:files选项是项目包含的两个目录。
说明3:scripts选项是npm script(npm脚本)
scripts也是对象,它的属性包含了很多,比如 "start": "npm run server:dev", 的意思是start命令对应的脚本是npm run server:dev。
说明4:dependencies选项是项目依赖。
比如@angular之类的依赖可以在node_modules找到对应的目录
说明5:devDependencies是项目开发依赖,跟开发环境、调试打包有关咯。
第二看src源代码目录了。
●app目录
—about组件
—home组件
app.html文件
●main.browser.ts文件负责使用根模块(NgModule)去启动应用。
●index.html是src目录下默认打开的文件。里面做了app组件的使用和一些文件的引入。可以测试一下,先把原文内容注释掉,等测试完毕之后再放开。
index.html我的内容:
<!DOCTYPE html> <html 🆖> <head> <title>Angular 2 Webpack 2 starter by @AngularClass</title> <base href="/"> </head> <body> <div style="color:red;">冰雪奇缘</div> </body> </html>
打开localhost:3000,那么显示如下
OK!记得再退回到之间的内容里哦。
●dll.ts文件
// Polyfills就像验光师为近视患者配的眼镜,它可以让浏览器支持还不能支持的功能 export function polyfills(env?: any) { return [ // 'ie-shim', 'core-js/es6/symbol', 'core-js/es6/object', 'core-js/es6/function', 'core-js/es6/parse-int', 'core-js/es6/parse-float', 'core-js/es6/number', 'core-js/es6/math', 'core-js/es6/string', 'core-js/es6/date', 'core-js/es6/array', 'core-js/es6/regexp', 'core-js/es6/map', 'core-js/es6/set', 'core-js/es6/weak-map', 'core-js/es6/weak-set', 'core-js/es6/typed', 'core-js/es6/reflect', // 'core-js/es6/promise', // problem with firefox 'core-js/es7/reflect', // zone.js 'zone.js/dist/zone', 'zone.js/dist/long-stack-trace-zone', // typescript helpers 'ts-helpers', ]; } // Angular 2 and other Vendor imports //本意是供应商,提供angular2或者其他导入 export function vendors(env?: any) { return [ '@angular/platform-browser', '@angular/platform-browser-dynamic', '@angular/compiler', '@angular/router', '@angular/forms', '@angular/common', '@angular/core', '@angular/http', '@angularclass/form-validators', '@angularclass/hmr', ]; } // RxJS是javascript的扩展 export function rxjs(env?: any) { return [ 'rxjs/Observable', 'rxjs/Subscription', 'rxjs/Subject', 'rxjs/BehaviorSubject', 'rxjs/add/operator/map', 'rxjs/add/operator/mergeMap', 'rxjs/add/operator/distinctUntilChanged', ]; }
打开localhost:3000,查看源代码可以看到像下面的脚本引入,就是他们咯
●custom-typings.d.ts
使用typings启动只能提示,这里是可以定制的。
step3:创建一个contact组件
以此熟悉一下源代码的单页逻辑结构。app目录下新建contact目录:
我直接把+about目录删掉,接下来填充如下代码。
app/app.html:
<div> <h1>Hello Angular 2 and Webpack 2</h1> <a href="#" routerLink="">Home</a> <a href="#" routerLink="contact">Contact</a> <!--contact路由的跳转链接,也就是路由使用咯--> </div> <main> <router-outlet></router-outlet> <!--这里是组件内容投掷的地方--> </main> <!-- <footer> <pre>appStore = {{ appStore.getState() | json }}</pre> AngularClass </footer> 我不喜欢这块代码,直接删掉咯。 -->
app/index.ts:
mport { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; import HomeModule from './home'; export const ROUTER_CONFIG = [ { path: '', loadChildren: () => HomeModule }, { path: 'contact', loadChildren: () => System.import('./contact')}, //组件代码外的路由配置 ]; @NgModule({ providers: [ ], declarations: [ // Components / Directives/ Pipes ], imports: [ RouterModule.forChild(ROUTER_CONFIG), ], }) export default class AppModule { static routes = ROUTER_CONFIG; }
组件app/contact/index.ts:
import { CommonModule } from '@angular/common' import { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ANGULARCLASS_FORM_VALIDATOR_DIRECTIVES } from '@angularclass/form-validators'; import { Contact } from './contact'; //引入contact.ts的模块 export const ROUTER_CONFIG = [ { path: '', component: Contact, pathMatch: 'full' } ]; @NgModule({ declarations: [ // Components / Directives/ Pipes Contact, //模块定义Contact ...ANGULARCLASS_FORM_VALIDATOR_DIRECTIVES ], imports: [ RouterModule.forChild(ROUTER_CONFIG), FormsModule, CommonModule ] }) export default class ContactModule { static routes = ROUTER_CONFIG; }
组件app/contact/contact.ts:
import { Component } from '@angular/core'; @Component({ templateUrl:"./contact.html" }) export class Contact { localState = { email: '' }; constructor() { } }
app/contact/contact.html:
<div>我是contact页面</div>
最终可以看到contact路由的显示结果
二: 探索angular2的路由
这里的构建系统使用另一个脚手架。
1.搭建脚手架
git clone https://github.com/mgechev/switching-to-angular2.git router-steps
npm install
npm start
非常顺利,它直接打开浏览器可以看到结果
把多余的代码删掉,只剩下目录。如图。
打开,http://localhost:5555/dist/dev/ch6/ts/step-2/可以看到浏览器的结果
接下来关注step-1的内容,其目录为
2.启动应用的根组件app.ts
除了定义组件的Component、启动应用的bootstrap函数、本组件的其他内容,还引入了路由和路径策略。
import {APP_BASE_HREF, LocationStrategy, HashLocationStrategy} from '@angular/common'; //LocationStrategy是一个抽象类,定义了基于哈希HashLocationStrategy的路由和基于 //HTML5的路由 //HashLocationStrategy不支持服务端渲染 import {Route, Redirect, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig} from '@angular/router-deprecated'; //ROUTER_PROVIDERS包含了路由的一组PROVIDER //RouteConfig是一个装饰器 //Route用来定义单独的路由 //用来定义转发规则,从而定义路由的层级结构,所以ng2支持嵌套路由
之后的app.ts使用component装饰器定义组件,并使用bootstrap启动应用。
bootstrap(App, [ ROUTER_PROVIDERS, { provide: LocationStrategy, useClass: HashLocationStrategy } ]); //第二个参数是provider列表,共整个APP访问 //名称为LocationStrategy的provider,设置为Hash策略,默认值是基于HTML5的策略
基于HTML5的PathLocationStrategy必须提供APP_BASE_HREF。
3.使用@RouteConfig配置路由
@RouteConfig([ new Route({ component: Home, name: 'Home', path: '/' }), new Route({ component: AddDeveloper, name: 'AddDeveloper', path: '/dev-add' }), // new Route({ component: DeveloperDetails, name: 'DeveloperDetails', path: '/dev-details/:id/...' }), new Redirect({ path: '/add-dev', redirectTo: ['/dev-add'] }) //当用户打开/add-dev时,会被转发到dev-add ]) class App {}
@RouteConfig装饰器接收路由数组作为参数。有两种类型的路由,Route和Redirect。
Route必须定义3个属性。
●component,路由关联的是哪个组件
●name,路由名称,模板使用它。
●path ,路径,浏览器地址栏使用它。
转发器只有两个属性
●path需要被转发的路径。
●redirectTo目标路径
4.routerLink和router-outlet的用法
ch6/ts/step-1/app.ts模板代码:
<nav class="navbar navbar-default"> <ul class="nav navbar-nav"> <li><a [routerLink]="['/Home']">Home</a></li> <li><a [routerLink]="['/AddDeveloper']">Add developer</a></li> </ul> </nav> <router-outlet></router-outlet>
●routerLink用来给指定路由添加链接。接收数组,包含名称和值。
●routeroutlet用来定义路由的渲染容器。
那么打开http://localhost:5555/dist/dev/ch6/ts/step-1/#/dev-add,可看到浏览器结果。可跳转的哦。
总体上分为3个部分,boostrap启动的provider列表、路由配置和路由的使用。
三: AsyncRoute懒加载
好棒呢,轻松实现懒加载。
只有一步: 引入AsyncRoute类
替换掉@RouteConfig里面的Route就行了。
@RouteConfig([ new AsyncRoute({ loader: () => System.import('./home') .then(m => m.Home), name: 'Home', path: '/' }), new AsyncRoute({ loader: () => System.import('./add_developer') .then(m => m.AddDeveloper), name: 'AddDeveloper', path: '/dev-add' }), // new Route({ component: DeveloperDetails, name: 'DeveloperDetails', path: '/dev-details/:id/...' }), new Redirect({ path: '/add-dev', redirectTo: ['/dev-add'] }) ])
完整的代码是
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import {APP_BASE_HREF, LocationStrategy, HashLocationStrategy} from '@angular/common'; import {AsyncRoute, Redirect, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig} from '@angular/router-deprecated'; import {DeveloperCollection} from './developer_collection'; import {Developer} from './developer'; @Component({ selector: 'app', template: ` <nav class="navbar navbar-default"> <ul class="nav navbar-nav"> <li><a [routerLink]="['/Home']">Home</a></li> <li><a [routerLink]="['/AddDeveloper']">Add developer</a></li> </ul> </nav> <router-outlet></router-outlet> `, providers: [DeveloperCollection], directives: [ROUTER_DIRECTIVES] }) @RouteConfig([ new AsyncRoute({ loader: () => System.import('./home') .then(m => m.Home), name: 'Home', path: '/' }), new AsyncRoute({ loader: () => System.import('./add_developer') .then(m => m.AddDeveloper), name: 'AddDeveloper', path: '/dev-add' }), // new Route({ component: DeveloperDetails, name: 'DeveloperDetails', path: '/dev-details/:id/...' }), new Redirect({ path: '/add-dev', redirectTo: ['/dev-add'] }) ]) class App {} bootstrap(App, [ ROUTER_PROVIDERS, { provide: LocationStrategy, useClass: HashLocationStrategy } ]);
AsyncRoute类构造函数接受一个对象作为参数
●loader:一个函数,返回一个需要兑现的promise。
●name:路由的名字
●path路由所对应的请求路径。
注意:这里使用的System类,只是其中一种方式而已,使用require.js也能实现相同的功能。
到底是不是懒加载,我们可以查看一下。
打开http://localhost:5555/dist/dev/ch6/ts/step-1-async网址,375个请求,并没有把所有路由组件都加载了
点击另外一个路由,这时375个请求变成了376个请求,也就是第二个路由。