力争国内 Angular 5 第一篇轮子
Github:github.com/OrangeXC/ud…
Link: incompetent-plantation.surge.sh/
最近轮子造的比较多,意在给初学者一个参考例子,目前反馈来看,如果技术栈不符,很少有人会点进来读,以后可以考虑转换博文类型了。
之前写过一篇 Angular2 从搭建环境到开发,在 segmentfault 上得到了 2016 年第四季度的 top writer 文章表里第四名,如今已经 angular 5
给大家的常规印象就是,大版本跳跃会带来 breaking change,因为 angular 从 1.x 到 2.x 简直是两个框架,不对,就是两个框架。
angular 1.x 叫 angular.js 而 angular 2.x 以后就叫 angular,两个版本分别托管在两个 github repo。
更让我比较惊讶的是 angular.js 的 star 近乎 angular 的 double,而且社区更繁荣,本文苦在找能配合 angular 5 使用的组件,因为框架刚升级,相应的组件都还未更新,下文会告诉大家一个小技巧。
angular 2 到 4 到 5,的组件数成幂指数递减,但是好在可以轻松向后扩展,如果熟悉 angular 2 那么本项目完全可以看懂。
其实写轮子看文档谁都会写,我尽量多说些坑点,让开发者少踩坑。
撸起袖子开整。
- npm install - g@angular / cli@1.5.0
这里直接把版本指向 1.5.0
- ng new PROJECT-NAME
- cd PROJECT-NAME
这时依赖已经安装完成,执行
,可以看到如下
- ng -v
- ng serve
默认 4200 端口,就可以看到初始化页面了。
安装过程可能较长,建议本地先安装 yarn,安装依赖的时候 cli 会自动使用 yarn 装依赖,会快不少。
到这就可以开发了。
udao 词典的公开接口已经废弃,这里拿来的接口是非官方的,支持的功能有限
这里明确要用的 UI 库是 ng-bootstrap,loading 用的是 ngx-loading
安装时会有依赖版本不符的警告,如下
但是勉强能用,前面说想找到合适的组件库比较困难,这里讲个小技巧,去 google 搜
基本都是 angular 1.x 的组件,那么根据历史分析组件命名有
- angular [some component]
- ng-
,到了 4 大家感觉心累所以干脆叫
- ng2-
,搜索直接搜
- ngx-
。
- ngx-[some component]
这里为什么是
而没选
- ng-bootstrap
呢,这里真的有
- ngx-bootstrap
,因为
- ngx-bootstrap
只支持
- ng-bootstrap
,后者支持 3 和 4,为了避免版本纠纷,直接用了
- bootstrap4
。
- ng-bootstrap
说到这远远不能证明 angular 5 可以用这个库,我的评判标准是 angular 4,如果支持 angular 4,那么 90% 支持 angular 5,因为改动确实不大。
目前此项目只涉及到 4 个路由。
主页
- /
翻译
- /translate
模糊搜索
- /search
单词详情
- /detail/:word
在
下面定义路由
- app.module.ts
- const routes: Routes = [{
- path: '',
- component: HomeComponent
- },
- {
- path: 'translate',
- component: TranslateComponent
- },
- {
- path: 'search',
- component: SearchComponent
- },
- {
- path: 'detail/:word',
- component: DetailComponent
- }]
这里说下路由跳转相关的问题,在 angular 5 里依然分为 a 标签的跳转和 js 跳转
- @Directive({ selector: ':not(a)[routerLink]' })
- class RouterLink {
- queryParams: {[k: string]: any}
- fragment: string
- queryParamsHandling: QueryParamsHandling
- preserveFragment: boolean
- skipLocationChange: boolean
- replaceUrl: boolean
- set routerLink: any[]|string
- set preserveQueryParams: boolean
- onClick(): boolean
- get urlTree: UrlTree
- }
本项目例子:
- <li class="nav-item">
- <
- a
- class
- =
- "nav-link"
- routerLink
- =
- "/"
- routerLinkActive
- =
- "active"
- [
- routerLinkActiveOptions
- ]=
- "{exact: true}"
- >
- 主页</a>
- </li>
- <li class="nav-item">
- <
- a
- class
- =
- "nav-link"
- routerLink
- =
- "/translate"
- routerLinkActive
- =
- "active"
- >
- 翻译</a>
- </li>
- <li class="nav-item">
- <
- a
- class
- =
- "nav-link"
- routerLink
- =
- "/search"
- routerLinkActive
- =
- "active"
- >
- 搜索</a>
- </li>
这里注意一个坑,第一个
标签,多了
- li
,如果不加的话,会导致
- [routerLinkActiveOptions]="{exact: true}"
路由下,active 不触发的情况。
- /
- class Router {
- constructor(rootComponentType: Type<any>|null, urlSerializer: UrlSerializer, rootContexts: ChildrenOutletContexts, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes)
- get events: Observable<Event>
- get routerState: RouterState
- errorHandler: ErrorHandler
- navigated: boolean
- urlHandlingStrategy: UrlHandlingStrategy
- routeReuseStrategy: RouteReuseStrategy
- onSameUrlNavigation: 'reload'|'ignore'
- config: Routes
- initialNavigation(): void
- setUpLocationChangeListener(): void
- get url: string
- resetConfig(config: Routes): void
- ngOnDestroy(): void
- dispose(): void
- createUrlTree(commands: any[], navigationExtras: NavigationExtras = {}): UrlTree
- navigateByUrl(url: string|UrlTree, extras: NavigationExtras = {skipLocationChange: false}): Promise<boolean>
- navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}): Promise<boolean>
- serializeUrl(url: UrlTree): string
- parseUrl(url: string): UrlTree
- isActive(url: string|UrlTree, exact: boolean): boolean
- }
本项目例子:
- gotoDetail ({ entry }) {
- this.router.navigate([`/detail/${entry}`])
- }
两个例子相比文档的概览都是最简单的用法,有需要的话可以看下其它方法,基本可以满足所有的路由需求。
这里不同于 vue 和 react,angular 提供了前端全栈的解决方案,包含了 http 模块,只需要在
里面引入
- app.module.ts
- import { HttpClientModule } from '@angular/common/http'
- // ...
- imports: [
- HttpClientModule
- ]
- // ...
请求的语法也很简单,具体可以到 github 看代码。
这里说一个小坑,在实现
路由的时候在
- detail
钩子里拿到当前路由参数进行请求,改变路由时没有触发请求更新,最后改版如下。
- ngOnInit
代码如下
- ngOnInit() {
- this.route.params.subscribe((params) = >{
- this.loading = true
- const apiURL = `https: //dict.youdao.com/jsonapi?q=${params['word']}`
- this.http.get(` / ?url = $ {
- encodeURIComponent(apiURL)
- }`).subscribe(res = >{
- // set component data
- this.loading = false
- })
- })
- }
之前无效是因为没写
,所以每次不会触发监听
- this.route.params.subscribe((params) => {})
这里的
会一直监听
- subscribe
的变化。
- this.route.params
如同 axios 的 baseURL,在请求时我们不希望每个请求都写完整路径,需要配置全局的 baseURL 来使得请求路径简短。
angular 里面需要一个
,熟悉的概念——依赖注入,关于细则有跟多文章介绍,这里说下针对此需求的解决方案
- @Injectable
- @Injectable()
- export
- class
- ExampleInterceptor
- implements
- HttpInterceptor
- {
- intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
- const url = 'https://proxy-oagpwnbkpe.now.sh'
- req = req.clone({
- url: url + req.url
- })
- return next.handle(req)
- }
- }
- // ...
- providers: [
- AppComponent,
- { provide: HTTP_INTERCEPTORS, useClass: ExampleInterceptor, multi: true }
- ]
- // ...
这段代码解决了 baseURL 的问题。
注意到上一节的这里
,根路径不是有道的路径。
- const url = 'https://proxy-oagpwnbkpe.now.sh'
还是做了一层 node 的 proxy 处理。跨域问题还是要处理。
node 服务的代码也十分简单,这里使用了 fly 进行 node 端请求
代码如下
- const express = require('express')
- const fly = require('flyio')
- const app = express()
- app.use('/', async (req, res) => {
- const data = await fly.get(req.query.url).then(res => res.data)
- res.set('Access-Control-Allow-Origin', '*')
- res.send(data)
- })
- app.listen(process.env.PORT || 3001)
重点是在返回头 set 一个
,这样浏览器就不会拦截请求了。
- Access-Control-Allow-Origin: *
在
页面,拆分了 5 个子组件,当然父子组件是十分简单的单向数据流
- detail
例:父组件的 html 如下
- <
- app-detail-phrs-list-tab
- [
- simple
- ]=
- "simple"
- [
- ec
- ]=
- "ec"
- >
- </app-detail-phrs-list-tab>
子组件的
如下
- component.ts
- export class DetailPhrsListTabComponent {
- @Input() simple
- @Input() ec
- }
就可以使用
取到父组件传进来的值了,说到这里全局的状态管理怎么做,要看下项目的复杂度
- @Input
简单的全局状态管理可以创建一个
,再创建依赖注入,如下
- global.ts
- // globals.ts
- import { Injectable } from '@angular/core';
- @Injectable()
- export class Globals {
- role: string = 'test';
- }
在组件中可以这样调用
- // hello.component.ts
- import { Component } from '@angular/core';
- import { Globals } from './globals';
- @Component({
- selector: 'hello',
- template: 'The global role is {{globals.role}}',
- providers: [Globals]
- })
- export class HelloComponent {
- constructor(private globals: Globals) {}
- }
另一种方式是 SPA 开发者熟悉的全局状态管理库,如 flex, redux
angular 也提供了 angular-redux,复杂应用中建议使用。
打包命令围绕
,提供几种配置参数,这里不赘述,
- ng build
部署这里使用的是 surge
友情提示:不要将私有项目部署到此类公开服务,弊端很多。
不论哪种前端框架,都有它的长处,由于此项目较小,到这里没机会释放
的威力,angular-cli 默认装了这个库,处理复杂的异步数据流非常高效,写了好多轮子,毕竟还是样例,但是,折腾不能停。
- rxjs
本次 angular 5 更新相关文档如下
官方文档:next.angular.io/docs
官方博客:blog.angular.io/version-5-0…
官方cli:github.com/angular/ang…
尽量翻墙查看,国内 angular.cn/ 文档还没更新。
来源: https://juejin.im/post/59fae0286fb9a044fb07184f