最近文章

Angular依赖注入注解inject()和注解@Inject

注解@InjectAngular提供了注解@Inject来向组件或服务注入实例,例如:@Injectable() class AService{ constructor(@Inject(BService) bService:BService) { } }这等同于添加访问修饰符后去掉注解,这也是我们常用的方式,上面示例可改为:@Injectable() class AService{
标签:

安装使用Window版的nvm-windows,管理nodejs安装

nvm是一个nodejs的版本管理工具,可以通过nvm安装和切换不同版本的nodejs。下载安装包地址:https://github.com/coreybutler/nvm-windows/releases下载地址提供了两种安装包:nvm-noinstall.zip:绿色免安装版,但使用时需进行配置。nvm-setup.zip:安装版安装1、解压nvm-setup.zip包,双击nvm-setup
标签:

npm ERR! cb.apply is not a function

1、进入npm全局的根目录,默认是:C:\Users\[用户名]\AppData\Roaming如果不是默认的目录,使用以下命令查找npm全局的根目录:npm root -g2、删除npm目录,如果存在npm-cache目录,也把它一并删除3、执行npm清除缓存命令npm clean cache —force此处记得加上--force4、(可选)删除Nodejs相关的文件夹,然后重装nodejs。
标签:

JavaScript简单的Async/Await示例

Node 7.6 新增了async/await! 下面以一个简单可运行的示例来说明async /await的使用。const axios = require('axios'); //基于promise的http请求框架function getCoffee() { return new Promise(resolve => { setTimeout(() => resolve('
标签:

如何对REST API进行版本控制

如果您对API不太熟悉,您可能会想...为什么对API版本控制大惊小怪?如果您对API的更改感到厌倦,那么您可能会大惊小怪。如果您是API的维护者,那么您可能还会大惊小怪地尝试解决诸如此类的难题:# 下面的v2(版本2)表示的只是产品版本还是整个APId呢?/v2/products# 是什么促使v1和v2的更改呢? 它们之间有什么不同呢?/v1/products/v2/products这些有关版本
标签:

Angular 5升级到Angular 6

Angular升级建议先到https://update.angular.io,它会根据你选择的原Angular和目标Angular版本给出一些升级建议。Angular 5升级到Angular 6升级前1、如果项目中有用到HttpModule和Http Service,把它们切换到HttpClientModule和HttpClient Service。HttpClient有几个特性:不需要调用.js
标签:

Angular 5 RouterLinkActive的用法

如果链接为当前路由(此时链接为激活状态),RouterLinkActive指令可以让我们给链接元素添加css的class样式。父路由连接激活默认情况下,当路由为链接的父路由或者完全匹配链接,链接都会认为是激活的状态。<a routerLink="/a/b" routerLinkActive="active">B链接</a>所以,示例里当路由为“/a”和“/a/b”class
标签:

Angular 5交换(重排)动态组件的位置

如果忽略了angular的组件是由数据控制的特性,很容易陷入使用dom操作控制组件。最简单的方式是使用数组存放数据,通过重排数组中的元素的位置,从而达到重排组件的效果。示例:app.tsimport {Component, NgModule, VERSION} from '@angular/core'import {BrowserModule} from '@angular/platform-br
标签:

Angular 5获取当前的路由url及参数

获取当前加载组件的路由信息可以使用ActivatedRoute。ActivatedRoute接口如下:interface ActivatedRoute {    snapshot: ActivatedRouteSnapshot   url: Observable<UrlSegment[]> &nb
标签:

JavaScript检测Chrome浏览器是否在隐身窗口

浏览器在隐身模式下,它的FileSystem API是禁用的,可以通过检测FileSystem API来判断浏览器是否在隐身窗口。function isIncognito() {  var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
标签:

Angular在启动页index.html多次重复使用组件的方法

默认情况下,在angular的index.html只能使用一个根标签作为应用的入口,如:<!DOCTYPE html> <html>  <head>   <base href="." />  </head>   <body>    <
标签:

Angular 5配置使用service-worker

Angular 5实现了一个适用于Angular应用的Service Worker,Angular CLI 1.6也添加了对service worker的支持。使用Angular CLI对应用添加service worker的支持分两种情况:新项目应用中添加service worker在已有项目中添加service&n
标签:

Angular 4选择组件模板DOM元素(DOM操作)的方式

Angular提供了多种方式让我们获取页面的元素。主要分为两种:基于Decorator:@ViewChild,@ViewChildren,@ContentChild,@ContentChildren使用ElementRef以及querySelectorDecorator:@ViewChild,@ViewChildren,@ContentChild,@ContentChildren@ViewChil
标签:

Angular 5:HttpClient的基本用法

Angular 4.3引入了新的用于http请求的接口HttpClient。它旨在替换之前版本的接口Http。有以下新特性:对响应的body做类型检查默认返回的是JSON对象,不再需要使用json()显式解析返回的数据支持拦截器支持进度事件请求后验证和基于刷新的测试框架安装HttpClientModuleHttpClient属于@angular/common/http包的模块HttpCl
标签:

配置Nginx解决Angular 4刷新路由重新加载报404错误

Angular重新加载页面会报404,原因可分为三种:1、路由不存在2、Angular使用html5模式的路由,需要把所有路由的请求映射到index.html配置Nginx如下:location / {     root /var/html;     try_files $uri $uri/ /index.html =404;   } 其
标签:

Angular Material:使用angular-material-prefix-updater修改选择器前缀md为mat

Angular Material 2.0.0-beta.12正式移除了所有选择器的前缀md,替换为前缀mat。对于使用md的项目需要做迁移升级。使用angular-material-prefix-updater可以帮助修改前缀md为mat。安装npm i -g angular-material-prefix-updater 查看工具的帮助说明mat-switcher --hel
标签:

Angular4使用Rxjs5共享Http请求返回的结果数据

在使用Angular开发的单页面应用里,有时需要处理这种场景:使用http请求某个数据,数据请求成功后需要把数据更新到多个组件里。也就是说,有多个observer(观察者)对http请求数据返回的Observable订阅。getDta() {     return this.http.get('/dataUrl').map(res&nbs
标签:

Angular依赖注入注解inject()和注解@Inject

更新于 2022.06.15 9分钟阅读 0 评论 5 推荐

    Angular依赖注入

    作者: 刘敏
  1. Angular依赖注入注解inject()和注解@Inject Page 1

注解@Inject

Angular提供了注解@Inject来向组件或服务注入实例,例如:

@Injectable()
class AService{
  constructor(@Inject(BService) bService:BService) {
  }  
}

这等同于添加访问修饰符后去掉注解,这也是我们常用的方式,上面示例可改为:

@Injectable()
class AService{
  //去掉了注解@Inject,注意需要添加访问修饰符private/protec/public
  constructor(private bService:BService) {
  }  
}

使用@Inject()注解有个限制,它只能在构造器参数上使用。

手动依赖注入方法inject()

inject()函数是Ivy引进来的,支持使用命令式做依赖注入。相对于注解@Inject仅限在构造函数参数上使用,inject()函数使用范围有所扩大,但仍有限制。inject()函数只能在以下几处使用:

  1. 由依赖注入实例化的类构造函数,例如@Injectable或@Component。
  2. 类字段属性的初始化
  3. 在为 Provider 或 @Injectable 的 useFactory 指定的工厂函数中。
  4. 在为InjectionToken 指定的工厂函数中。

这几处都是在类实例化创建的上下文中调用inject()函数。

类的构造函数

@Injectable()
class AService
  private bService: BService;
  constructor() {
    this.bService = inject(BService);
  }  
}

类属性字段

@Injectable()
class AService
  private bService: BService = inject(BService);
  constructor() {
  }  
}

provider工厂函数

providers: [
  {provide: AService, useFactory: () => {
    const bService= inject(BService);
    return new AService(bService);
  }}
]

调用报NG0203错

在类创建上下文之外调用 inject() 函数会报错。有一个地方要注意,在创建类实例后,在类的其他方法(包括生命周期挂钩)中不允许调用 inject(), 如ngOnInit:

@Component({ ... })
export class MyComponent {
  ngOnInit() {
    // ERROR: 此处调用时已经晚了,因为MyComponent已经实例化完成
    const myService= inject(MySerivce);
  }
}

它报NG0203错误,信息类似:

core.mjs:6494 ERROR Error: NG0203: inject() must be called from an injection context
   at injectInjectorOnly (core.mjs:4768:1)
   at ɵɵinject (core.mjs:4778:1)
   at Module.ɵɵdirectiveInject (core.mjs:14430:1)

angular版本的影响

在Angular13以及之前的版本,是不支持在@Component和@Directive中使用,Angular 14已修正以支持在组件和指令中使用,修复的信息:

fix(core): set correct context for inject() for component ctors (#45991)

The `inject()` function was introduced with Ivy to support imperative injection in factory/constructor contexts, such as directive or service constructors as well as factory functions defined in `@Injectable` or `InjectionToken`. However, `inject()` in a component/directive constructor did not work due to a flaw in the logic for creating the internal factory for components/directives. The original intention of this logic was to keep `ɵɵdirectiveInject` tree- shakable for applications which don't use any component-level DI. However, this breaks the `inject()` functionality for component/directive constructors. This commit fixes that issue and adds tests for all the various cases in which `inject()` should function. As a result `ɵɵdirectiveInject` is no longer tree-shakable, but that's totally acceptable as any application that uses `*ngIf` or `*ngFor` already contains this function. It's possible to change how `inject()` works to restore this tree-shakability if needed.

 

 

安装使用Window版的nvm-windows,管理nodejs安装

nvm是一个nodejs的版本管理工具,可以通过nvm安装和切换不同版本的nodejs。

下载

安装包地址:https://github.com/coreybutler/nvm-windows/releases

下载地址提供了两种安装包:

  • nvm-noinstall.zip:绿色免安装版,但使用时需进行配置。
  • nvm-setup.zip:安装版

安装

1、解压nvm-setup.zip包,双击nvm-setup.exe进行安装:

勾选"I accept the agreement",点击“next”:

2、配置NVM的安装路径

3、配置node.js安装路径

点击“Next”,最后安装。

4、【可选】安装完成后,配置node下载的镜像地址。

配置nodejs安装包镜像,目的是加快在国内下载node安装包的速度。

进入nvm安装路径,打开settings.txt,添加淘宝镜像url:

完整内容类似:

使用

1、查看安装版本

PS D:\mytest> nvm
Running version 1.1.7.
Usage:
  nvm arch                     : Show if node is running in 32 or 64 bit mode.
  nvm install <version> [arch] : The version can be a node.js version or "latest" for the latest stable version.
                                 Optionally specify whether to install the 32 or 64 bit version (defaults to system arch).
                                 Set [arch] to "all" to install 32 AND 64 bit versions.
                                 Add --insecure to the end of this command to bypass SSL validation of the remote download server.
  nvm list [available]         : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls.
  nvm on                       : Enable node.js version management.
  nvm off                      : Disable node.js version management.
  nvm proxy [url]              : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.
                                 Set [url] to "none" to remove the proxy.
  nvm node_mirror [url]        : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.
  nvm npm_mirror [url]         : Set the npm mirror. Defaults to https://github.com/npm/cli/archive/. Leave [url] blank to default url.
  nvm uninstall <version>      : The version must be a specific version.
  nvm use [version] [arch]     : Switch to use the specified version. Optionally specify 32/64bit architecture.
                                 nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.
  nvm root [path]              : Set the directory where nvm should store different versions of node.js.
                                 If <path> is not set, the current root will be displayed.
  nvm version                  : Displays the current running version of nvm for Windows. Aliased as v.

这个列出了nvm命令的使用说明.

2、列出当前安装的nodejs版本

PS D:\mytest> nvm list
  * 12.22.1 (Currently using 64-bit executable)

3、列出可用的安装版本

PS D:\mytest> nvm list available
|   CURRENT    |     LTS      |  OLD STABLE  | OLD UNSTABLE |
|--------------|--------------|--------------|--------------|
|   15.14.0    |   14.16.1    |   0.12.18    |   0.11.16    |
|   15.13.0    |   14.16.0    |   0.12.17    |   0.11.15    |
|   15.12.0    |   14.15.5    |   0.12.16    |   0.11.14    |
|   15.11.0    |   14.15.4    |   0.12.15    |   0.11.13    |
|   15.10.0    |   14.15.3    |   0.12.14    |   0.11.12    |
|    15.9.0    |   14.15.2    |   0.12.13    |   0.11.11    |
|    15.8.0    |   14.15.1    |   0.12.12    |   0.11.10    |
|    15.7.0    |   14.15.0    |   0.12.11    |    0.11.9    |
|    15.6.0    |   12.22.1    |   0.12.10    |    0.11.8    |
|    15.5.1    |   12.22.0    |    0.12.9    |    0.11.7    |
|    15.5.0    |   12.21.0    |    0.12.8    |    0.11.6    |
|    15.4.0    |   12.20.2    |    0.12.7    |    0.11.5    |
|    15.3.0    |   12.20.1    |    0.12.6    |    0.11.4    |
|    15.2.1    |   12.20.0    |    0.12.5    |    0.11.3    |
|    15.2.0    |   12.19.1    |    0.12.4    |    0.11.2    |
|    15.1.0    |   12.19.0    |    0.12.3    |    0.11.1    |
|    15.0.1    |   12.18.4    |    0.12.2    |    0.11.0    |
|    15.0.0    |   12.18.3    |    0.12.1    |    0.9.12    |
|   14.14.0    |   12.18.2    |    0.12.0    |    0.9.11    |
|   14.13.1    |   12.18.1    |   0.10.48    |    0.9.10    |
This is a partial list. For a complete list, visit https://nodejs.org/download/release

4、安装指定版本的nodejs

PS D:\mytest> nvm install 14.16.1
Downloading node.js version 14.16.1 (64-bit)... 
Complete
Creating D:\program\nvm\temp
Downloading npm version 6.14.12... Complete
Installing npm v6.14.12...
Installation complete. If you want to use this version, type
nvm use 14.16.1

5、安装最新版的nodejs

PS D:\mytest> nvm install latest 
Downloading node.js version 15.14.0 (64-bit)... 
Complete
Creating D:\program\nvm\temp
Downloading npm version 7.7.6... Complete
Installing npm v7.7.6...
Installation complete. If you want to use this version, type
nvm use 15.14.0

6、激活以及切换nodejs版本

PS D:\mytest> nvm use 14.16.1
Now using node v14.16.1 (64-bit)

7、卸载指定版本的nodejs

PS D:\mytest> nvm uninstall 14.16.1
Uninstalling node v14.16.1... done

 

 

npm ERR! cb.apply is not a function

1、进入npm全局的根目录,默认是:C:\Users\[用户名]\AppData\Roaming

如果不是默认的目录,使用以下命令查找npm全局的根目录:

npm root -g

2、删除npm目录,如果存在npm-cache目录,也把它一并删除

3、执行npm清除缓存命令

npm clean cache —force

此处记得加上--force

4、(可选)删除Nodejs相关的文件夹,然后重装nodejs。

前面三步一般情况下是可以解决问题的,只有在前面三步还没解决问题才做第四步


JavaScript简单的Async/Await示例

Node 7.6 新增了async/await! 下面以一个简单可运行的示例来说明async /await的使用。

const axios = require('axios'); //基于promise的http请求框架
function getCoffee() {
return new Promise(resolve => {
setTimeout(() => resolve('☕'), 2000); // 延时两秒返回咖啡
});
}
async function go() {
try {
// 首先拿咖啡
const coffee = await getCoffee();
console.log(coffee); // ☕
//接着通过ajax的方式获取些数据
const wes = await axios('https://api.github.com/users/wesbos');
console.log(wes.data); // mediocre code
// 多个请求并发
//触发三个请求,获取它的promise
const wordPromise = axios('http://www.setgetgo.com/randomword/get.php');
const userPromise = axios('https://randomuser.me/api/');
const namePromise = axios('https://uinames.com/api/');
// 等待三个promise返回,然后把结果赋值给三个变量
const [word, user, name] = await Promise.all([wordPromise, userPromise, namePromise]);
console.log(word.data, user.data, name.data); // cool, {...}, {....}
} catch (e) {
console.error(e); // 💩
}
}
go();

如何对REST API进行版本控制

如果您对API不太熟悉,您可能会想...为什么对API版本控制大惊小怪?

如果您对API的更改感到厌倦,那么您可能会大惊小怪。如果您是API的维护者,那么您可能还会大惊小怪地尝试解决诸如此类的难题:

# 下面的v2(版本2)表示的只是产品版本还是整个APId呢?
/v2/products
# 是什么促使v1和v2的更改呢? 它们之间有什么不同呢?
/v1/products
/v2/products

这些有关版本控制的问题都不容易回答。有时并不总是很清楚什么是v1或者v2。而且,当第一个版本看似不能够满足时,我们不应该就新建第二个版本。

是有明确的理由,为什么你的API需要有版本,并有清晰的策略去有效地引导API的变化。

但是,我发现大多数开发人员(包括我自己),直到我以艰难的方式学到了一些教训后,才意识到这些原因和策略。

本文旨在突出版本控制的原因以及实现它的策略。我们将假设是使用REST API,因为它是很多API的标准,然后重点讨论它的版本控制。

什么是版本控制?

我们应该从术语“ API版本控制”的级别设置开始。这是我们的定义:

API版本控制是透明管理API更改的做法。

版本控制是围绕API更改进行的有效沟通,因此,消费者知道可以从中获得什么。假如你正在以某种方式对外传递数据,当你更改数据的传递方式时,是需要对外进行沟通。

归根结底,这就是管理数据合约和破坏变更(break change)。前者是API的主要构建块,而后者则说明了为什么需要版本控制。

数据合约

API是应用程序编程接口(Interface),而接口是交换信息的共享边界。数据合约是此接口的核心。

数据合约是关于请求和/或响应数据的格式以及内容的协议。

为了说明数据协定,这是一个基本的JSON响应体:

{
"data": [
{
"id": 1,
"name": "Product 1"
},
{
"id": 2,
"name": "Product 2"
}
]
}

对象中包含了data属性,它是一个表示产品的数组(列表),每个产品有属性id和name。但是属性data可以改为body属性,每个产品的属性id可以是GUID而不是整数。如果要返回单个产品,则data可以是一个对象而不是一个数组。

对于数据的“形状”,这些看似微妙的变化将导致不同的协议,不同的约定。数据形状可以体现在属性名称,数据类型,甚至可以是想要的格式(JSON与XML)。

为什么需要版本控制?

使用API​​时,就是简单的把属性名productId改为productID,对消费者来说也是破坏性的更改。我们的团队上周就发生了这件事。

幸运的是,我们进行了测试,发现了这个api协定的更改。但是,我们不应该需要那些测试,因为API的维护者应该知道这将是一个重大变化。

重大更改(Breaking changes)

这是对约定的数据合约的重大更改,这些更改会让我们也更改应用程序。

什么是API的“重大更改”?你对API约定的任何更改都迫使消费者也进行更改。

重大更改主要包括以下几类:

  1. 更改请求/响应格式(例如,从XML到JSON)
  2. 更改属性名称(例如从name到productName)
  3. 在请求上添加必填字段(例如,请求正文中新的必填标头或属性)
  4. 删除响应中的属性(例如,description从产品中删除)

API变更管理

强迫API使用者进行更改永远是不明智的选择。如果您必须进行重大更改,那就是版本控制的目的,我们将介绍对应用程序和端点(endpoint)进行版本控制的最有效方法。

但是首先让我们简要地讨论一下如何避免破坏更改。我们可以称之为API变更管理。

以下原则概括了API的有效变更管理:

  • 继续支持现有的属性/端点(endpoint)
  • 添加新属性/端点(endpoint),而不是更改现有属性/端点
  • 考虑过时的属性/端点

这是一个示例,它在请求用户数据的响应中展示了所有这三个原理:

{
"data": {
"id": 1,
"name": "Carlos Ray Norris", // 原属性
"firstName": "Carlos", // 新属性
"lastName": "Norris", // 新属性
"alias": "Chuck", // 过时的属性
"aliases": ["Chuck", "Walker"] // 新属性
},
"meta": {
"fieldNotes": [
{
"field": "alias",
"note": "Sunsetting on [future date]. Please use aliases."
}
]
}
}

在此示例中,name是原始属性。如果消费者希望通过一些字符串插值来显示“ Norris先生”,而不必解析该字段,则需要将firstName和lastName字段分开来提供更细name选项。但是,新格式需要继续支持name属性。

alias是另一种情况,它将被废弃,推荐使用aliases数组,因为Chuck的别名很多,在响应中添加注释,说明alias的废弃时间。

您如何版本化API?

这些原则在不需要更改版本的情况下,引导API的更改需要花费很长的时间。但是,有时这是可以避免的,并且如果你需要全新的数据约定,则需要新版本的端点(endpoint)。因此,你需要以某种方式将其传达给公众。

顺便说一句,请注意,我们并不是在谈论基础代码库的版本。因此,如果你正在为还支持公共API的应用程序使用语义版本控制,则可能需要将这些版本控制系统分开。

如何创建API的新版本?这样做有哪些不同的方法?您需要确定总体上要采用哪种类型的版本控制策略,然后在开发和维护API时,需要确定每个版本更改的范围。

范围(Scop)

让我们先解决范围。正如我们上面所探讨的,有时数据合约会因重大更改而受到损害,这意味着我们需要提供新版本的数据合约。这可能意味着端点(endpoint)的新版本,或者可能意味着在更全局的应用程序范围内进行了更改。

我们可以在树的类比中考虑范围变化的级别:

  • 叶子(Leaf ) -更改为隔离的端点,与其他端点没有关系
  • 分支(Branch ) -更改为一组端点或通过多个端点访问的资源
  • 主干(Trunk ) -应用程序级更改,保证大多数或所有端点上的版本更改
  • 根(Root) -更改影响对所有版本的所有API资源的访问

如您所见,从叶到根,这些变化变得越来越有影响力,并且范围越来越广。

该叶范围通常可以通过有效的API变更管理处理。如果没有,只需使用新的资源数据协定创建一个新的端点。

一个分支是有点麻烦,这取决于有多少端点由有问题的资源数据契约变化的影响。如果更改相对仅限于一组清晰的相关端点,则可以通过为资源引入新名称并相应地更新文档来进行导航。

# variants, which has a breaking change, is accessed on multiple routes
/variants
/products/:id/variants
# we introduce product-variants instead
/product-variants
/products/:id/product-variants

主干(trunk)指的是经常在以下类别中的一个的变化的结果的应用程序级的变化:

  • 格式(例如,从XML到JSON)
  • 规范(例如,从内部到JSON API或Open API)
  • 必需的标题(例如,用于身份验证/授权)

这些将需要更改您的整体API版本,因此您应该仔细计划并正确执行过渡。

一个根本的变化将迫使你走一步,确保您的API的所有版本的消费者都知道这种变化。

API版本控制的类型

当我们转向不同类型的API版本控制时,我们将希望利用这些见解对API更改的不同范围进行评估。每种方法在根据其范围来解决更改时都有其优点和缺点。

有几种方法可以管理您的API版本。URI路径版本控制是最常见的。

URI路径

http://www.example.com/api/v1/products
http://api.example.com/v1/products

此策略涉及将版本号放在URI的路径中,并且通常使用前缀“ v”来完成。API设计人员通常会使用它来引用其应用程序版本(即“ trunk”)而不是终结点版本(即“ leaf”或“ branch”),但这并不总是一个安全的假设。

URI路径版本控制意味着应用程序版本的协调发行,这将需要以下两种方法之一:在开发新版本时维护一个版本,或强迫使用者等待新资源,直到新版本发布为止。这也意味着您需要在版本之间保留所有未更改的端点。但是,对于挥发性相对较低的API,这仍然是一个不错的选择。

您可能不希望将您的版本号与端点或资源的版本号相关联,因为它很容易导致类似于v4of,products但v1of of variants,这会造成混乱。

查询参数(Query Params)

http://www.example.com/api/products?version=1

这种类型的版本控制将查询参数添加到指示版本的请求中。在“叶子”级别上请求资源的版本方面非常灵活,但是它不包含整个API版本的概念,并且使其自身具有上述注释中提到的不同步问题URI路径的端点级别版本控制。

头部(Header)

Accept: version=1.0

标头方法是一种在提供任何给定资源的请求版本时提供更细粒度的方法。

但是,它埋在请求对象中,不像URI路径选项那样透明。仍然很难确定1.0是引用端点的版本还是API本身。

整合类型

这些方法中的每一个似乎都有缺点,要么偏爱“叶子”范围,要么偏爱“树干”范围,但不支持两者。

如果您需要维护整个API版本,并且还提供对多种资源版本的支持,请考虑URI路径和查询参数类型的混合,或者使用更高级的标头方法。

# URI path and query params combo
http://api.example.com/v1/products?version=1
http://api.example.com/v1/products?version=2
# Extended headers, for http://api.example.com/products
Accept: api-version=1; resource-version=1
Accept: api-version=1; resource-version=2

结论

我们在这里介绍了很多内容,现在回顾一下:

  • API版本控制是透明管理API更改的做法。
  • 管理API归结为定义和发展数据合同以及处理重大更改。
  • 在不破坏更改的情况下发展API的最有效方法是遵循有效的API更改管理原则。
  • 对于大多数API,URI路径中的版本控制是最简单的解决方案。
  • 对于更复杂或易失的API,您可以通过采用URI路径和查询参数方法的集成来管理变化范围。

尽管这些原则应为如何有效管理API更改提供明确的指导,但演变API可能是一门艺术,而不是一门科学。创建和维护可靠的API需要思想和远见。

原文:https://www.freecodecamp.org/news/how-to-version-a-rest-api/


Angular 5升级到Angular 6

Angular升级建议先到https://update.angular.io,它会根据你选择的原Angular和目标Angular版本给出一些升级建议。

Angular 5升级到Angular 6

升级前

1、如果项目中有用到HttpModule和Http Service,把它们切换到HttpClientModule和HttpClient Service。

HttpClient有几个特性:

  • 不需要调用.json()来映射返回的数据到json格式,默认就是json格式
  • 支持拦截器

具体可以参考:HttpClient

2、如果项目中有使用到<template>标签,把它们替换为<ng-template>

升级

1、与Angular 6匹配的Angular CLI需要用到Node 8 或以上版本

2、升级Angular CLI(全局和本地),迁移配置到罪行的angular.json,执行以下命令:

npm install -g @angular/cli
npm install @angular/cli
ng update @angular/cli

3、升级Angular 6以及升级RxJS, TypeScript到对应的版本

ng update @angular/core

升级后,新版的RxJS和Typescript可能会提示旧代码的错误

4、如果使用了angular material,升级如下:

ng update @angular/material

5、angular cli 6新增了命令ng update

升级后

1、使用rxjs-tslint auto update rules删除已被RxJS 6废弃的特性

大多数情况下可以执行下面两个命令:

npm install -g rxjs-tslint
rxjs-5-to-6-migrate -p src/tsconfig.app.json

2、但所有的依赖升级到RxJS 6后, 删除rxjs-compat



Angular 5 RouterLinkActive的用法

如果链接为当前路由(此时链接为激活状态),RouterLinkActive指令可以让我们给链接元素添加css的class样式。

父路由连接激活

默认情况下,当路由为链接的父路由或者完全匹配链接,链接都会认为是激活的状态。

<a routerLink="/a/b" routerLinkActive="active">B链接</a>

所以,示例里当路由为“/a”和“/a/b”class active都会被添加。

完全匹配激活

如果限定当前路由需要和链接路径完全匹配才激活,可以在routerLinkActiveOptions属性添加exact: true。

<a routerLink="/a/b" routerLinkActive="active" [routerLinkActiveOptions]="{exact:
true}">B链接</a>

添加多个class

可以在routerLinkActive添加多个class:

<a routerLink="/a/b" routerLinkActive="class1 class2">B链接</a>
<a routerLink="/a/b" [routerLinkActive]="['class1', 'class2']">B链接</a>

需要注意上面两种赋值方式的区别:

  • routerLinkActive="class1 class2":这种是直接赋值字符串,这和普通设置class值class="class1 class2"是一样的。
  • [routerLinkActive]="['class1','class2']":这是angular的绑定表达式,双引号里可以是一个在组件里定义的变量,这里直接使用了数组表达式。

父元素设置routerLinkActive

如果有多个链接需要设置routerLinkActive,可以统一在这组链接的父元素设置routerLinkActive:

<div routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a routerLink="/a/b">B链接</a>
<a routerLink="/a/c">C链接</a>
</div>

调用RouterLinkActive的api

可以把RouterLinkActive实例赋值给一个变量,然后就可以通过变量来调用RouterLinkActive的api:

<a routerLink="/a/b" routerLinkActive #rla="routerLinkActive">
B链接 {{ rla.isActive ? '(当前)' : ''}}
</a>

Angular 5交换(重排)动态组件的位置

如果忽略了angular的组件是由数据控制的特性,很容易陷入使用dom操作控制组件。最简单的方式是使用数组存放数据,通过重排数组中的元素的位置,从而达到重排组件的效果。

示例:

app.ts

import {Component, NgModule, VERSION} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
@Component({
selector: 'my-app',
template: `
<ng-container *ngFor="let item of array">
<div>
item: {{item}}
</div>
</ng-container>
<button (click)="reverse()">交互组件</button>
`,
})
export class App {
array = [];
constructor() {
this.renderArray.push("组件A")
this.renderArray.push("组件B")
}
reverse(){
this.renderArray = this.array.reverse();
}
}
@NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}

Angular 5获取当前的路由url及参数

获取当前加载组件的路由信息可以使用ActivatedRoute。ActivatedRoute接口如下:

interface ActivatedRoute { 
  snapshot: ActivatedRouteSnapshot
  url: Observable<UrlSegment[]>
  params: Observable<Params>
  queryParams: Observable<Params>
  fragment: Observable<string>
  data: Observable<Data>
  outlet: string
  component: Type<any> | string | null
  get routeConfig: Route | null
  get root: ActivatedRoute
  get parent: ActivatedRoute | null
  get firstChild: ActivatedRoute | null
  get children: ActivatedRoute[]
  get pathFromRoot: ActivatedRoute[]
  get paramMap: Observable<ParamMap>
  get queryParamMap: Observable<ParamMap>
  toString(): string
}

可以看到ActivatedRoute提供了url,params,queryParams等。ActivatedRoute这几个属性返回的是Observable,它是可以用来监控url,参数的变化。

注入使用如下:

@Component({...})
class MyComponent {
  constructor(private route: ActivatedRoute) {
    const id: Observable<string> = route.params.map(p => p.id);   //获取参数
    const url: Observable<string> = route.url.map(segments => segments.join('')); //获取拼接的url
    const user = route.data.map(d => d.user); //获取数据
  }
}

JavaScript检测Chrome浏览器是否在隐身窗口

浏览器在隐身模式下,它的FileSystem API是禁用的,可以通过检测FileSystem API来判断浏览器是否在隐身窗口。

function isIncognito() {
 var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
 if (!fs) {
  result.textContent = "检测失败?";
  return;
 }
 fs(window.TEMPORARY, 100, function(fs) {
  result.textContent = "你估计没有隐身";
 }, function(err) {
  result.textContent = "哈哈,你估计在隐身!";
 });
}

Angular在启动页index.html多次重复使用组件的方法

默认情况下,在angular的index.html只能使用一个根标签作为应用的入口,如:

<!DOCTYPE html>
<html>
 <head>
  <base href="." />
 </head> 
 <body>
   <app-root></app-root>
 </body>
</html>

如果要在index.html页面多次使用组件,可以在模块的ngDoBootstrap()方法里添加:

app.ts

根组件app.ts定义组件,组件选择器为"my-app"

import { Component, NgModule, Inject, OpaqueToken } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'

import { ApplicationRef, ComponentFactory, ComponentFactoryResolver, Type } from '@angular/core';

@Component({
 selector: 'my-app',
 template: `
  <div>
   <h2>Hello {{name}}</h2>
  </div>
 `,
})
export class App {
 name:string;
 constructor() {
  this.name = 'Angular'
 }
}

export const BOOTSTRAP_COMPONENTS_TOKEN = new OpaqueToken('bootstrap_components');

@NgModule({
 imports: [ BrowserModule ],
 declarations: [ App ],
 entryComponents: [ App ]
})
export class AppModule {
  constructor(
    private resolver : ComponentFactoryResolver
    @Inject(BOOTSTRAP_COMPONENTS_TOKEN) private components
  ) {}

  ngDoBootstrap(appRef : ApplicationRef) {
    this.components.forEach((componentDef : {type: Type<any>, selector: string}) => {
     const factory = this.resolver.resolveComponentFactory(componentDef.type);
     factory.selector = componentDef.selector;
     appRef.bootstrap(factory);
    });
  }
}

在AppModule的构造函数里注入在index.html使用的组件components,它在main.ts里声明注册。

核心代码在ngDoBootstrap():

ngDoBootstrap(appRef : ApplicationRef) {
    this.components.forEach((componentDef : {type: Type<any>, selector: string}) => {
     const factory = this.resolver.resolveComponentFactory(componentDef.type);
     factory.selector = componentDef.selector;
     appRef.bootstrap(factory);
    });
  }

main.ts

angular 应用入口

import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule, App, BOOTSTRAP_COMPONENTS_TOKEN} from './app';

const components = [
 { type: App, selector: "my-app#app-1" },
 { type: App, selector: "my-app#app-2" }
];

const platform = platformBrowserDynamic([
 {provide: BOOTSTRAP_COMPONENTS_TOKEN, useValue: components}
]);
platform.bootstrapModule(AppModule);

在入口的main.ts定义了在index.html跟页面使用的组件列表components,并且使用paltformBorwserDynamic()注册。

index.html

在index.html使用如下:

<!DOCTYPE html>
<html>

 <head>
  <base href="." />
  <title>angular2 playground</title>
  <link rel="stylesheet" href="style.css" />
  <script src="https://unpkg.com/zone.js/dist/zone.js"></script>
  <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
  <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
  <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
  <script src="config.js"></script>
  <script>
  System.import('app').catch(console.error.bind(console));
 </script>
 </head>

 <body>
  <my-app id="app-1">loading 1...</my-app>
  <my-app id="app-2">loading 2...</my-app>
 </body>

</html>

Angular 5配置使用service-worker

Angular 5实现了一个适用于Angular应用的Service Worker,Angular CLI 1.6也添加了对service worker的支持。

使用Angular CLI对应用添加service worker的支持分两种情况:

  1. 新项目应用中添加service worker
  2. 在已有项目中添加service worker

新项目应用添加servcie worker

使用Angular CLI在新建项目时可以使用--service-worker,很简单就可以让新项目添加对service-worker的支持。

ng new swdemo --service-worker

使用--service-worker,ng会帮我们自动的配置好service-worker。与service worker相关的变化如下:

1、package.json新增了@angular/service-worker

"@angular/service-worker": "^5.0.0",

2、ng会在.angular-cli.json文件的apps[0]节点添加如下:


{
...
"apps": [
   ...
   "serviceWorker": true
  }
 ],
...
}

3、app.module.ts引入了ServiceWorkerModule,代码如下:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ServiceWorkerModule } from '@angular/service-worker';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment';

@NgModule({
 declarations: [
  AppComponent
 ],
 imports: [
  BrowserModule,
  environment.production ? ServiceWorkerModule.register('/ngsw-worker.js') : []
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

当环境变量为production时才注册ServiceWorkerModule。注册方式:

ServiceWorkerModule.register('/ngsw-worker.js') : []

4、在src下新建了用于service worker的ngsw-config.json文件,内容如下:

{
 "index": "/index.html",
 "assetGroups": [{
  "name": "app",
  "installMode": "prefetch",
  "resources": {
   "files": [
    "/favicon.ico",
    "/index.html"
   ],
   "versionedFiles": [
    "/*.bundle.css",
    "/*.bundle.js",
    "/*.chunk.js"
   ]
  }
 }, {
  "name": "assets",
  "installMode": "lazy",
  "updateMode": "prefetch",
  "resources": {
   "files": [
    "/assets/**"
   ]
  }
 }]
}

这个文件用来配置控制service worker缓存文件及数据。

已有项目添加service worker

可以按照上面新建项目的相关改变对已有项目进行修改。

1、安装@angular/service-worker

npm install @angular/service-worker --save

2、在.angular-cli.json添加”serviceWorker”=true配置

ng set apps.0.serviceWorker=true

3、app.module.ts引入了ServiceWorkerModule,参照新建项目的例子。

4、添加用于service worker的src/ngsw-config.json文件,参照新建项目的例子。

构建

使用ng构建生产环境的产品包时就可以报service-worker打包进去。如:

ng build --prod

Angular 4选择组件模板DOM元素(DOM操作)的方式

Angular提供了多种方式让我们获取页面的元素。主要分为两种:

  • 基于Decorator:@ViewChild,@ViewChildren,@ContentChild,@ContentChildren
  • 使用ElementRef以及querySelector

Decorator:@ViewChild,@ViewChildren,@ContentChild,@ContentChildren

@ViewChild

获取页面匹配到第一个元素或者指令

页面

<input #myname>

使用

@Component({selector: 'myCmp', templateUrl: 'myCmp.html'})
class MyCmp implements AfterViewInit {
  @ViewChild('myname') input;

  ngAfterViewInit() {
    // do something
  }
}

匹配指令

import {AfterViewInit, Component, Directive, ViewChild} from '@angular/core';

@Directive({selector: 'child-directive'})
class ChildDirective {
}

@Component({selector: 'someCmp', templateUrl: 'someCmp.html'})
class SomeCmp implements AfterViewInit {
 @ViewChild(ChildDirective) child: ChildDirective;

 ngAfterViewInit() {
  // do something
 }
}

@ViewChildren

和@ViewChild类似,匹配多个页面元素或指令,返回一个QueryList

@Directive({selector: 'child-directive'})
class ChildDirective {
}

@Component({selector: 'someCmp', templateUrl: 'someCmp.html'})
class SomeCmp implements AfterViewInit {
 @ViewChildren(ChildDirective) viewChildren: QueryList<ChildDirective>;

 ngAfterViewInit() {
  // viewChildren is set
 }
}

对于多个元素可以使用逗号隔开,注意不支持有空格,@ViewChildren("var1,var2,var3")

我们可以在ngOnInit里订阅@ViewChild或@ViewChildren里元素的更新:

@ViewChildren(SomeType) viewChildren;

ngOnInit() {
  this.viewChildren.changes.subscribe(changes => console.log(changes));
  this.contentChildren.changes.subscribe(changes => console.log(changes));
}

@ContentChild和@ContentChildren:它们类似于@ViewChild和@ViewChildren,只是它们针对的是<ng-content>。

ElementRef

处理使用@ViewChild,@ViewChildren,@ContentChild和@ContentChildren外,我们也可以在组件的构造器注入ElementRef,使用ElementRef内置nativeElement的querySelector获取DOM节点。

export class MyComponent implements AfterViewInit{
  constructor(private elRef:ElementRef) {}
  ngAfterViewInit() {
    var div = this.elRef.nativeElement.querySelector('div');
    console.log(div);
  }
}

Angular 5:HttpClient的基本用法

Angular 4.3引入了新的用于http请求的接口HttpClient。它旨在替换之前版本的接口Http。有以下新特性:

  1. 对响应的body做类型检查
  2. 默认返回的是JSON对象,不再需要使用json()显式解析返回的数据
  3. 支持拦截器
  4. 支持进度事件
  5. 请求后验证和基于刷新的测试框架

安装HttpClientModule

HttpClient属于@angular/common/http包的模块HttpClientModule。使用HttpClient需要导入HttpClientModule。


import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';

import {HttpClientModule} from '@angular/common/http';

@NgModule({
 imports: [
  BrowserModule,
  //在BrowserModule之后导入HttpClientModule
  HttpClientModule,
 ],
})
export class AppModule {}

请求JSON数据

使用get请求/api/items的JSON数据。

JSON格式:

{
  "results": [
    "Item 1",
    "Item 2",
  ]
}

示例组件

@Component(...)
export class MyComponent implements OnInit {

 results: string[];

 // 注入HttpClient
 constructor(private http: HttpClient) {}

 ngOnInit(): void {
 
  this.http.get('/api/items').subscribe(data => {
      //此处不需要使用data.json()来解析响应数据
   this.results = data['results'];
  });
 }
}

由于HttpClient默认使用JSON格式,所以在处理响应数据不再需要显式解析JSON。

类型检查

在上面的例子里没有直接使用data.results获取data的results属性的值,而是使用下标语法data["results"]。这是由于http响应不知道data的数据结构。

如果需要对返回的数据做类型检查,可是使用泛型对get请求指定响应数据类型。

interface ItemsResponse {
  results: string[];
}

例子里的get请求改为

http.get<ItemsResponse>('/api/items').subscribe(data => {
  this.results = data.results;
});

获取完整的response

上面的例子获取的是返回的JSON数据,如果需要获取整个respons,包括响应头信息等,可以对get函数指定

http
  .get<MyJsonData>('/data.json', {observe: 'response'})
  .subscribe(resp => {
    console.log(resp);
    console.log(resp.headers.get('X-Custom-Header'));
    console.log(resp.body.someField);
  });

response.body就是上面例子里的data,也是一个JSON对象。

错误处理

http请求失败会返回一个HttpErrorResponse

http
 .get<ItemsResponse>('/api/items')
 .subscribe(
  data => {...},
  (err: HttpErrorResponse) => {
   if (err.error instanceof Error) {
    console.log('An error occurred:', err.error.message);
   } else {
    console.log(err);
    console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
   }
  }
 );

我们也可以使用rxjs的retry()重试来处理错误:

import 'rxjs/add/operator/retry';
...
http
  .get<ItemsResponse>('/api/items')
  // 最大重试次数设定为3
  .retry(3)
  // 
  .subscribe(...);

请求非JSON数据

HttpClient返回的数据默认为JSON对象。可以使用是非JSON对象:

http
  .get('/textfile.txt', {responseType: 'text'})
  // 返回的数据类型为string
  .subscribe(data => console.log(data));

Post请求

post请求和get类似,同样有subscribe函数,body默认为json对象:

const body = {name: 'Brad'};

http
  .post('/api/developers/add', body)
  .subscribe(...);

添加请求头信息

可以使用headers设置请求头信息:

http
  .post('/api/items/add', body, {
    headers: new HttpHeaders().set('Authorization', 'my-auth-token'),
  })
  .subscribe();

添加Url参数

使用params给Url添加参数:

http
  .post('/api/items/add', body, {
    params: new HttpParams().set('id', '3'),
  })
  .subscribe();

注意

需要注意的是,所有HttpClient返回的Observable都为冷对象。返回Observable并不意味者发起了请求,只有对用subscrib函数时才会真正发起http请求。对Observable调用多次subscribe函数,就会发起多次请求。

const req = http.post('/api/items/add', body);
// 此时没有发起请求
req.subscribe();
// 发次第一次请求
req.subscribe();
// 发次第二次请求

配置Nginx解决Angular 4刷新路由重新加载报404错误

Angular重新加载页面会报404,原因可分为三种:

1、路由不存在

2、Angular使用html5模式的路由,需要把所有路由的请求映射到index.html

配置Nginx如下:

location / {
    root /var/html;
    try_files $uri $uri/ /index.html =404;
  }

其中root配置的是html文件所在的文件夹

3、Angular使用hash的路由(即用#的URL来路由),base配置有误

<base href=".">

需要改为

<base href="/">

Angular Material:使用angular-material-prefix-updater修改选择器前缀md为mat

Angular Material 2.0.0-beta.12正式移除了所有选择器的前缀md,替换为前缀mat。

对于使用md的项目需要做迁移升级。使用angular-material-prefix-updater可以帮助修改前缀md为mat。

安装

npm i -g angular-material-prefix-updater

查看工具的帮助说明

mat-switcher --help

执行前缀更新

mat-switcher -p path/to/project/tsconfig.json

如果css里也使用md作为前缀,可以使用--extra-css选项

mat-switcher -p path/to/project/tsconfig.json --extra-css 'custom/**/*.css'

迁移升级完后,为了避免代码里误用了md前缀,在应用的根模块的providers添加@angular/material的MATERIAL_COMPATIBILITY_MODE
强制代码使用mat作为前缀。示例如下:

import {MATERIAL_COMPATIBILITY_MODE} from '@angular/material';
 
@NgModule({
  providers: [
    {provide: MATERIAL_COMPATIBILITY_MODE, useValue: true},
    // ...
  ],
})
export class MyModule { }

Angular4使用Rxjs5共享Http请求返回的结果数据

在使用Angular开发的单页面应用里,有时需要处理这种场景:使用http请求某个数据,数据请求成功后需要把数据更新到多个组件里。也就是说,有多个observer(观察者)对http请求数据返回的Observable订阅。

getDta() {
    return this.http.get('/dataUrl').map(res => res.json());
}
let dataObservable = getDta();

let subscriber1 = dataObservable.subscribe(...);
let subscriber2 = dataObservable .subscribe(...);

如果代码类似上面这样写,你会发现http发了两次请求。这明显和我们的设想有出入,我们是希望http请求完后共享请求的数据。

这里分为两种场景:

  1. 请求的数据是固定不变的,可以考虑把请求的数据缓存起来

请求数据后是要同时更新多个组件,下次需要重新请求再更新相应的组件

缓存共享数据

对于请求数据是固定不变的或者是在一段时间内不会发生修改,可以考虑把数据缓存起来。

示例:

import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of'; //proper way to import the 'of' operator
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';
import {Data} from './data';

@Injectable()
export class DataService {
  private url:string = 'https://cors-test.appspot.com/test';

  private data: Data;
  private observable: Observable<any>;

  constructor(private http:Http) {}

  getData() {
    if(this.data) {
      //数据已存在,返回数据的Observable
      return Observable.of(this.data); 
    } else if(this.observable) {
      //数据还没有返回,正在请求中,返回该请求的observable
      return this.observable;
    } else {
      //还没有发起请求,发起http请求。
      this.observable = this.http.get(this.url)
      .map(response =>  {
        //数据返回后,此次的observable不再需要,下次访问返回data的Observable
        this.observable = null;
        if(response.status == 400) {
          return "FAILURE";
        } else if(response.status == 200) {
          this.data = new Data(response.json());
          return this.data;
        }
      })
      .share();//使用share(),允许多个订阅获取结果
      return this.observable;
    }
  }
}

多组件共享每次请求的数据

对于每次请求的数据可能会被修改,那么就不适合缓存数据了。

Rxjs 5.4.0新增了shareReplay()函数,可以使用它来共享http ajax请求的数据

示例

getDta() {
    return this.http.get('/dataUrl').map(res => res.json()).shareReplay();
}
let dataObservable = getDta();

参考:https://stackoverflow.com/questions/36271899/what-is-the-correct-way-to-share-the-result-of-an-angular-2-http-network-call-in