143

Angular 5 开发一个有道翻译

 6 years ago
source link: https://juejin.im/post/59fae0286fb9a044fb07184f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Angular 5 开发一个有道翻译

2017年11月02日 09:08 ·  阅读 5427
Angular 5 开发一个有道翻译
b08465e8e450afc021de90066b1a816b~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp

力争国内 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/[email protected]复制代码

这里直接把版本指向 1.5.0

ng new PROJECT-NAME
cd PROJECT-NAME复制代码

这时依赖已经安装完成,执行 ng -v,可以看到如下

04d924da04ecc7a6486b972ad31e002c~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp
ng serve复制代码

默认 4200 端口,就可以看到初始化页面了。

安装过程可能较长,建议本地先安装 yarn,安装依赖的时候 cli 会自动使用 yarn 装依赖,会快不少。

到这就可以开发了。

udao 词典的公开接口已经废弃,这里拿来的接口是非官方的,支持的功能有限

这里明确要用的 UI 库是 ng-bootstrap,loading 用的是 ngx-loading

安装时会有依赖版本不符的警告,如下

f930d73e8587a63fb6c017d5c7eb04ea~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.awebp

但是勉强能用,前面说想找到合适的组件库比较困难,这里讲个小技巧,去 google 搜 angular [some component] 基本都是 angular 1.x 的组件,那么根据历史分析组件命名有 ng- ng2-,到了 4 大家感觉心累所以干脆叫 ngx-,搜索直接搜 ngx-[some component]

这里为什么是 ng-bootstrap 而没选 ngx-bootstrap 呢,这里真的有 ngx-bootstrap,因为 ng-bootstrap 只支持 bootstrap4,后者支持 3 和 4,为了避免版本纠纷,直接用了 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 跳转

  • a 标签的写法
@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: *,这样浏览器就不会拦截请求了。

detail 页面,拆分了 5 个子组件,当然父子组件是十分简单的单向数据流

例:父组件的 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

友情提示:不要将私有项目部署到此类公开服务,弊端很多。

不论哪种前端框架,都有它的长处,由于此项目较小,到这里没机会释放 rxjs 的威力,angular-cli 默认装了这个库,处理复杂的异步数据流非常高效,写了好多轮子,毕竟还是样例,但是,折腾不能停。

本次 angular 5 更新相关文档如下

官方文档:next.angular.io/docs
官方博客:blog.angular.io/version-5-0…
官方cli:github.com/angular/ang…

尽量翻墙查看,国内 angular.cn/ 文档还没更新。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK