该系列用于探索一些 Angular 中灵活或者新的用法。本文记录启用路由 Router,以及添加静态资源的过程。
# 使用路由
# 有关路由
现在流行的前端框架,像 React/Vue/Angular 等,都不可避免地自带路由模块。
要说为什么呢,本骚年是这么认为的(个人意见仅供参考):
- 单页应用已经十分流行了,尤其在 PC 端,而使用前端路由,配合模块化,便使得开发单页应用得心应手。
- 路由的设计很简单,事件的监听配合框架自行封装的一些
[router-link]
等组件,便在开发效率锦上添花。
(本骚年也不知道为啥突然懂得这么多的成语,开心逃
- Angular 的 Router
Angular 的 Router 借鉴了浏览器的导航模型。它把浏览器中的 URL 看做一个操作指南, 据此导航到一个由客户端生成的视图,并可以把参数传给支撑视图的相应组件,帮它决定具体该展现哪些内容。
路由器还在浏览器的历史日志中记录下这些活动,这样浏览器的前进和后退按钮也能照常工作。
# 在视图中跳转
这里只介绍常用的两种,它们都需要在 A 标签上使用:
- [routerLink]属性
不用说,一看就知道[routerLink]
属性是用来定位到具体的路由的,当然我们同样可以使用 angular 中常用的绑定来传入需要的变量。
- [routerLinkActive]属性
该属性可配置在带[routerLink]
属性的 A 标签上,则当[routerLink]
对应的路由被触发了,则[routerLinkActive]
绑定的 Class 样式将被激活。
# 注册路由模块
在 Angular 的 release 版本开始,加入了 NgModule 这样一个模块设计,首先我们需注册这样一个路由模块:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
... // 引入其他Component
const appRoutes: Routes = [
{ path: 'one-path', component: OneComponent },
{ path: 'other-path', component: OtherComponent },
{ path: '**', redirectTo: 'one-path' }
];
@NgModule({
imports: [
RouterModule.forRoot(appRoutes)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
- 根路由注册使用
RouterModule.forRoot()
- 子路由注册使用
RouterModule.forChild()
对于根路由和子路由,一个应用有一个根路由,但可以有多个子路由,像我们的 DOM 树结构一样,层层向下伸展。
这样的设计思想现在也很流行了,像还有 Angular 中的依赖注入 DI,或者是 React 的虚拟 DOM 设计等。
# 使用 Router
- 在 js 中跳转
一般来说,我们使用router.navigate()
来进行跳转,像我们的登陆页面:
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
})
export class LoginComponent implements OnInit, OnDestroy {
// 注入router
constructor(private router: Router) {}
// 登录事件
onSubmit() {
this.router.navigate(['/home']);
}
}
当然,router.navigate()
还可以传入其他的 params,使用方法大家可以查阅文档。
- 监听路由
在 Angular2 的 release 版本之后,路由进行了调整,我们需要通过subscribe()
这样的方式对事件进行订阅:
// 监听导航事件变更
// NavigationEnd表示导航事件变更完毕
// 通过对事件进行filter过滤,我们可以订阅想要的路由事件消息
router.events
.filter(event => event instanceof NavigationEnd)
.subscribe(event => {});
若要写面包屑功能,可参考该文章Angular2 Breadcrumb using Router (opens new window)。
# 获取路由信息
- ActivatedRoute:一站式获取路由信息
路由的路径和参数可以通过注入ActivatedRoute
的路由服务来获取,包括:
- url: 该路由路径的 Observable 对象。它的值是一个由路径中各个部件组成的字符串数组
- data: 该路由提供的 data 对象的一个 Observable 对象。还包含从 resolve 中解析出来的值
- params: 包含该路由的必选参数和可选参数的 Observable 对象
- queryParams: 一个包含对所有路由都有效的查询参数的 Observable 对象
- fragment: 一个包含对所有路由都有效的片段值的 Observable 对象
- outlet: RouterOutlet 的名字,用于指示渲染该路由的位置
- routeConfig: 与该路由的原始路径对应的配置信息
- firstChild: 包含子路由列表中的第一个 ActivatedRoute 对象
- children: 包含当前路由下激活的全部子路由
通常我们可能这样使用:
import { ActivatedRoute } from '@angular/router';
... // 其余代码
// 注入route
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.params
.subscribe((params) => {
// 获取名为id的param参数
this.id = parseInt(params['id']);
... // 其余代码
});
}
... // 其余代码
# lazyload
对于一些路由子模块,我们可以通过懒加载来定义,这样就可以只在进入到该路由时进行加载,也算是首屏加载优化的一种方法吧。
一般我们在路由中这样定义:
export const AppRoutes: Routes = [
{
path: "",
component: AppComponent,
children: [{ path: "home", loadChildren: "lazy/lazy.module#LazyModule" }]
}
];
就可以对该部分进行懒加载了,当然懒加载还需要其他依赖或者一些另外的配置支持,其实安装一个angular-router-loader
就好了:
npm install --save-dev angular-router-loader
然后在 webpack 的 rules 中调整:
{
test: /\.ts$/,
use: ["babel-loader", "ts-loader", "angular2-template-loader", "angular-router-loader"],
exclude: /node_modules/
}
打包的时候我们会发现,除了主文件bundle.js
,每一个使用 lazyload 的文件都会单独打包成一个n.bundle.js
文件(n 为数字),用于异步请求和加载。
# 添加资源加载
一般来说,项目多业务杂,我们总不能一个个插件都自己实现,故很多时候我们需要使用到像 jQuery 或者 Bootstrap 这样很常用的库。
# 全局注入 js
本骚年使用了一种最蠢的方式来实现[捂脸]:
// jquery
window["$"] = window["jQuery"] = require("./assets/js/jquery.min.js");
// boootstrap
require("../node_modules/bootstrap/dist/js/bootstrap.min.js");
当然,身为懒癌晚期的我还使用了 typescript 的杀手锏[捂脸+1]:
// declation.d.ts
declare var $: any;
declare var JQuery: any;
其实在 typescript 中使用 jQuery 也有很多现成的@types
库,大家可以一键安装使用就好了:
npm install @types/jquery
完整的项目还是需要完善的,像本骚年这种业余玩玩的小项目就另当其说了,而且说不定后面我也会完善的吧[摊手]。
# 全局注入 css
像import 'someStyle.css'
这样的方法竟然没有成功,可能是本骚年的 webpack 配置或者是 loader 哪里出了问题吧。
然后就找了一个懒方法,利用的是ViewEncapsulation
,在组件中将styleUrls
定义为全局生效,如下:
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-root',
template: ' <router-outlet></router-outlet>',
styleUrls: [
'../../../node_modules/bootstrap/dist/css/bootstrap.min.css',
'../../assets/css/common.css',
'../../assets/css/custom.css'
],
encapsulation: ViewEncapsulation.None
})
# 项目结构
# 目录
如图:
# 其他配置文件调整
- package.json
移除@angular/compiler-cli
,该依赖为 AOT 使用,而目前本骚年还没做这方面的配置,光一个compiler-cli
也是不够的,后面需要的时候再加吧
- tsconfig.json
这个不得不说了,项目一直起不来提示如下:
compiler.es5.js:15291
Uncaught Error: Can't resolve all parameters for LoginComponent: (?).
at syntaxError (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:1724:34)
at CompileMetadataResolver._getDependenciesMetadata (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:15061:35)
at CompileMetadataResolver._getTypeMetadata (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:14929:26)
at CompileMetadataResolver.getNonNormalizedDirectiveMetadata (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:14538:24)
at CompileMetadataResolver._getEntryComponentMetadata (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:15182:45)
at eval (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:15168:48)
at Array.forEach (native)
at CompileMetadataResolver._getEntryComponentsFromProvider (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:15167:30)
at eval (webpack:///./~/@angular/compiler/@angular/compiler.es5.js?:15131:83)
at Array.forEach (native)
哭死我了,后面是改了一个tsconfig.json
配置解决的:
"emitDecoratorMetadata": true, <= 就是这家伙
大家也可以尽情嘲笑我,本骚年有时候就是转不过来。
- webpack.config.js
// 加了个插件,关闭之前的黄色warning
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)@angular/,
path.resolve(__dirname, "src"),
{}
);
登录页面也还是我们熟悉的:
# 结束语
这节主要讲了路由相关的介绍和使用,以及添加静态资源,和调整一些配置使得项目得以启动的过程。项目里代码讲得不多,大家可以自行查看。
此处查看项目代码 (opens new window)
此处查看页面效果 (opens new window)