BlueXIII's Blog

热爱技术,持续学习

0%

官网

pom.xml整合

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>

注意需要更新至最新版,以支持SQL排序

配置拦截器插件

公司框架中MyBatisConfigure.class已配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties p = new Properties();
p.setProperty("offsetAsPageNum", "true");
p.setProperty("rowBoundsWithCount", "true");
p.setProperty("reasonable", "false");
p.setProperty("pageSizeZero", "true");
pageInterceptor.setProperties(p);
return pageInterceptor;
}

@Bean(
name = {"sqlSessionFactory"}
)
public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSourceRouter") DataSource dataSourceRouter) {
... ...
sqlSessionFactory.setPlugins(new Interceptor[]{this.pageInterceptor()});
... ...
return sqlSessionFactory;
}

基本使用示例

推荐使用PageHelper.startPage方式:

1
2
3
4
5
6
public PageInfo<ThreedutyCheckRecord> findByUnitCode(String unitCode, int pageNum, int pageSize, String orderBy) {
PageHelper.<ThreedutyCheckRecord>startPage(pageNum, pageSize, orderBy);
List<ThreedutyCheckRecord> result = threedutyCheckRecordMapper.findByUnitCode(unitCode);
PageInfo<ThreedutyCheckRecord> page = new PageInfo<>(result);
return page;
}

使用方式详解

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

简介

Gist是Github的一个子服务. 最简单的功能就是分享代码片段,例如把一些小型脚本放到Gist方便分享和管理. 不同于大型项目使用repository进行管理, Gist就是小型代码片段的分享. 类似的服务还有如 PastebinPastie, 但明显出生于Github的Gist更有优势了.

地址

https://gist.github.com/

使用教程

Gisto 客户端工具

https://www.gistoapp.com/

Intellij IDEA默认支持

https://www.jetbrains.com/help/idea/share-code-with-gists.html

Intellij IDEA插件

https://plugins.jetbrains.com/plugin/7236-get-gist-beta-

教程

https://www.typescriptlang.org/docs/home.html
https://ts.xcatliu.com
https://zhongsp.gitbook.io/typescript-handbook/tutorials

参考文档

安装TypeScript

1
npm install -g typescript

Hello TS

1
2
3
4
5
6
function sayHello(person: string) {
return 'Hello, ' + person;
}

let user = 'Tom';
console.log(sayHello(user));
1
tsc hello.ts

原始数据类型

  • boolean
  • number
  • null
  • undefined
1
2
3
4
5
let isDone: boolean = false;

// 使用构造函数 Boolean 创造的对象不是布尔值,而是一个 Boolean 对象
let createdByNewBoolean: boolean = new Boolean(1);
let createdByBoolean: boolean = Boolean(1);
1
2
3
4
5
6
7
8
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
1
2
3
4
5
let myName: string = 'Tom';
let myAge: number = 25;
// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
let unusable: void = undefined;

// 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量
let u: undefined = undefined;
let n: null = null;

let num: number = undefined;
let u: undefined;
let num: number = u;

// 而 void 类型的变量不能赋值给 number 类型的变量:
let u: void;
let num: number = u; // ERROR Type 'void' is not assignable to type 'number'.

任意值

在任意值上访问任何属性都是允许的

1
2
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;

也允许调用任何方法

1
2
3
4
5
6
7
8
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);

let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

1
2
3
4
let something;
something = 'seven';
something = 7;
something.setName('Tom');

类型推论

TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查。

1
2
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种。

1
2
3
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
1
2
3
4
// length 不是 string 和 number 的共有属性,所以会报错
function getLength(something: string | number): number {
return something.length;
}

接口

1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age: number;
}

let tom: Person = {
name: 'Tom',
age: 25
};

可选属性的含义是该属性可以不存在

1
2
3
4
5
6
7
8
interface Person {
name: string;
age?: number;
}

let tom: Person = {
name: 'Tom'
};

有时候我们希望一个接口允许有任意的属性

1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
name: 'Tom',
gender: 'male'
};

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

1
2
3
4
5
6
7
8
9
10
11
12
interface Person {
name: string;
age?: number;
[propName: string]: string;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// ERROR Type 'number' is not assignable to type 'string'.

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用readonly定义只读属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};

tom.id = 9527;
// ERROR TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

数组的类型

1
let fibonacci: number[] = [1, 1, 2, 3, 5];

数组的项中不允许出现其他的类型

数组泛型

1
let fibonacci: Array<number> = [1, 1, 2, 3, 5];

用接口表示数组
虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。

1
2
3
4
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

类数组

1
2
3
function sum() {
let args: number[] = arguments;
}

any在数组中的应用

1
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];

函数的类型

1
2
3
4
// 函数声明
function sum(x: number, y: number): number {
return x + y;
}
1
2
3
4
// 函数表达式
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};

在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

用接口定义函数的形状

1
2
3
4
5
6
7
8
interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}

可选参数
可选参数后面不允许再出现必需参数了

1
2
3
4
5
6
7
8
9
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

参数默认值

1
2
3
4
5
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型。

1
2
3
<类型>值

值 as 类型

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的:

1
2
3
4
5
6
7
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}

声明文件

  • declare var 声明全局变量
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明(含有子属性的)全局对象
  • interface 和 type 声明全局类型
  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6 默认导出
  • export = commonjs 导出模块
  • export as namespace UMD 库声明全局变量
  • declare global 扩展全局变量
  • declare module 扩展模块
  • /// 三斜线指令

npm包的声明文件可能存在于两个地方:
-与该 npm 包绑定在一起。判断依据是 package.json 中有 types 字段,或者有一个 index.d.ts 声明文件。这种模式不需要额外安装其他包,是最为推荐的,所以以后我们自己创建 npm 包的时候,最好也将声明文件与 npm 包绑定在一起。

  • 发布到 @types 里。我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件,安装命令是 npm install @types/foo –save-dev。这种模式一般是由于 npm 包的维护者没有提供声明文件,所以只能由其他人将声明文件发布到 @types 里了。

npm 包的声明文件主要有以下几种语法:

  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6 默认导出
  • export = commonjs 导出模块

内置对象

  • Boolean
  • Error
  • Date
  • RegExp

类型别名

1
2
3
4
5
6
7
8
9
10
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}

字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串中的一个。

1
2
3
4
5
6
7
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}

handleEvent(document.getElementById('hello'), 'scroll'); // 没问题
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'

元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

1
2
3
4
5
6
7
8
let tom: [string, number] = ['Tom', 25];

let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;

tom[0].slice(1);
tom[1].toFixed(2);

枚举

1
2
3
4
5
6
7
8
9
10
11
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

手动赋值

1
2
3
4
5
6
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

public private 和 protected

1
2
3
4
5
6
7
8
9
10
11
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom

参数属性

1
2
3
4
5
6
class Animal {
// public name: string;
public constructor (public name) {
// this.name = name;
}
}

readonly

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
readonly name;
public constructor(name) {
this.name = name;
}
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';

// index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}

class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`);
}
}

let cat = new Cat('Tom');

类的类型

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}

let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

类与接口

类实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Alarm {
alert();
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}

class Car implements Alarm {
alert() {
console.log('Car alert');
}
}

一个类可以实现多个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Alarm {
alert();
}

interface Light {
lightOn();
lightOff();
}

class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}

接口继承接口

1
2
3
4
5
6
7
8
interface Alarm {
alert();
}

interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}

接口继承类

1
2
3
4
5
6
7
8
9
10
class Point {
x: number;
y: number;
}

interface Point3d extends Point {
z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

1
2
3
4
5
6
7
8
9
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

例子

1
2
3
4
5
6
7
8
9
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']

自动推算

1
2
3
4
5
6
7
8
9
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

多个类型参数

1
2
3
4
5
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

泛型类

1
2
3
4
5
6
7
8
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

声明合并

  • 函数的合并
  • 接口的合并
  • 类的合并

代码检查

由于 ESLint 默认使用 Espree 进行语法解析,无法识别 TypeScript 的一些语法,故我们需要安装 @typescript-eslint/parser,替代掉默认的解析器

1
2
npm install --save-dev eslint
npm install --save-dev typescript @typescript-eslint/parser

.eslintrc.js配置

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
// 禁止使用 var
'no-var': "error",
// 优先使用 interface 而不是 type
'@typescript-eslint/consistent-type-definitions': [
"error",
"interface"
]
}
}

手动检查一个ts文件

1
./node_modules/.bin/eslint index.ts

自动检查一个文件

1
2
3
4
5
6
vi package.json
{
"scripts": {
"eslint": "eslint index.ts"
}
}
1
npm run eslint

检查整个项目的ts文件
vi package.json

1
2
3
4
5
{
"scripts": {
"eslint": "eslint src --ext .ts"
}
}

使用Prettier修复格式错误

1
npm install --save-dev prettier

prettier.config.js配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// prettier.config.js or .prettierrc.js
module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 使用 4 个空格缩进
tabWidth: 4,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: 'none',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// 换行符使用 lf
endOfLine: 'lf'
};

修改 .vscode/settings.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"files.eol": "\n",
"editor.tabSize": 4,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.autoFixOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
}
],
"typescript.tsdk": "node_modules/typescript/lib"
}

TS编码规范

Lodash库

https://www.lodashjs.com/

简介

工程必须编写Junit单元测试,只有通过所有测试并达到指定覆盖率之后,才允许构建。

初期不强制要求,视团队接受/排斥情况而定

约定

存放及命名

所有单元测试均放在src/test/java文件夹下,命名为类名+Test(s).java,即对于某个类的所有单元测试均放在同一个文件内

Jacoco代码覆盖率

工程必须配置Jacoco插件,以进行代码测试覆盖率的检查,只有覆盖率达到一定比例才能通过构建。更多细节请参考Sonar代码检测.md

准备工作

pom.xml

修改zorrodemo.service模块的pom.xml,添加mockito依赖

1
2
3
4
5
6
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>

Service层单元测试DEMO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class AttachmentServiceUnitTest {
private static final Logger log = LoggerFactory.getLogger(AttachmentServiceUnitTest.class);
@InjectMocks
private AttachmentService attachmentService = new AttachmentService();
@Mock
private AttachmentMapper attachmentMapper;

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}

@Test
public void query() {
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.setId("1");
attachment.setName("attachmentName");
attachmentList.add(attachment);

given(attachmentMapper.query(Mockito.any(String.class))).willReturn(attachmentList);

PageInfo<Attachment> result = attachmentService.query("1.docx", 1, 10);
log.info("result: {}", result);

assertTrue(result.getTotal() > 0);
}
}

Service层集成测试DEMO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RootApplicationContextConfigForTest.class)
@Transactional //事务默认回滚
public class AttachmentServiceIntegrationTest {
private static final Logger log = LoggerFactory.getLogger(AttachmentServiceIntegrationTest.class);
@Autowired
private AttachmentService attachmentService;

@Test
public void save() {
Attachment attachment = new Attachment();
attachment.setId("1");
attachment.setName("test1");
attachment.setPath("/tmp");

int result = attachmentService.save(attachment);
log.info("result: {}", result);

assertEquals(1, result);
}

@Test(expected = DataIntegrityViolationException.class)
public void saveWithException() {
Attachment attachment = new Attachment();
attachment.setId("1");

int result = attachmentService.save(attachment);
log.info("result: {}", result);

assertEquals(1, result);
}

@Test
public void query() {
PageInfo<Attachment> result = attachmentService.query("1.docx", 1, 10);
log.info("result: {}", result);

assertTrue(result.getTotal() > 0);
}

@Test
public void queryWithNotExistName() {
PageInfo<Attachment> result = attachmentService.query("notexist", 1, 10);
log.info("result: {}", result);

assertEquals(0, result.getTotal());
}

@Test
public void delete() {
Attachment attachment = new Attachment();
attachment.setId("1");
attachment.setName("test0");
attachment.setPath("/tmp");
attachmentService.save(attachment);

int result = attachmentService.delete("1");
log.info("result: {}", result);

assertTrue(result > 0);
}
}

Controller层集成测试

  • 无法使用@SpringBootTest,不容易实现
  • 公司框架中controller层基本上无业务逻辑,仅是rest接口的定义。本层测试省略后影响较小。

关于回归测试

  • 方式1: 利用IDEA自带的HTTP client实现。
  • 方式2: 使用更通用的postman+newman编写测试脚本并测试
  • 不强制回归测试
  • 更多细节参考接口回归测试.md

其它说明

  • 尽量简化开发流程,降低上手难度,除公司标准框架外,不引入新的复杂度
  • 不做TDD,不做每个层级的单元测试
  • 仅做service层的集成测试
  • 为避免数据不一致造成的测试失效,后期可考虑引入H2 Database(会小幅度增加开发复杂度,根据情况权衡)

RESTful风格简介

定义

REST = REpresentational State Transfer = 表现层状态转移

REST描述的是在网络中Client/Server的一种交互风格,并非强制性规范。

名词&资源

URL中只使用名词来指定资源,原则上不使用动词,“资源”是REST的核心。

例如: http://localhost/profiles/1 获取个人资料

动词&操作

用HTTP协议里的动词来实现资源的添加,修改,删除等操作。

  • GET 用来获取资源
  • POST 新建资源
  • PUT 更新资源
  • DELETE 删除资源

例如:

状态&结果

用HTTP Status Code传递Server的状态信息。例如200表示成功,500表示服务器内部错误等。

RESTful接口约定

命名规则

举例:
http://ip:port/{domain}/{service_name}/{version}/{api_name}?{pathparam}={somevalue}&{pathparam}={somevalue}
http://10.161.66.60/act/resourceremain/v1/resources?serialNumber=18601969575&netTypeCode=50&xAcceptMode=2

  • {domain}:服务所属域信息,如act。小型工程可省略。
  • {service_name}:服务名,如resourceremain。小型工程可省略。
  • {version}: API版本号,如v1。小型工程可省略。
  • {api_name}: 接口名称,如resources

说明:

  • URL中字母都是小写。
  • 每个URL代表一种资源,URL不能有动词,只能是名词。如show,get,update,query,delete等不能出现在URL中。
  • 利用HTTP请求的动词表示对资源操作的行为。
  • 对于资源的描述的名词应该是层级嵌套的方式,比如/companys/departments/projects。通过这种对于信息层级描述的方式,更利于实体的抽象。
  • 路径终点的命名使用复数形式,比如/books。一般一个URL路径表示的资源会映射为数据库一系列表的记录的集合,因此使用复数更直观。

HTTP方法

对于资源的具体操作类型,由HTTP动词表示,常用的HTTP动词有下面五个:

  • GET方法 从服务器取出资源(一项或多项)
  • POST方法 在服务器新建一个资源
  • PUT方法 在服务器更新资源(客户端提供改变后的完整资源)
  • PATCH方法 在服务器更新资源(客户端提供改变的属性)
  • DELETE方法 从服务器删除资源

示例:

  • GET /books:列出所有书
  • POST /books:新建一个书
  • GET /books/{id} 获取某本书的信息
  • PUT /books/{id} 更新某本书的信息(提供该书的全部属性)
  • PUT /books/{id} 更新某本书的信息(仅提供该书需要改变的属性)
  • DELETE /books/{id} 删除某个书
  • GET /books/{id}/notes 列出本书的所有笔记
  • DELETE /books/{id}/notes/{id} 删除某本书的某条笔记

过滤信息

如果记录数量很多,API应该提供参数,过滤返回结果。参数名仅做参考。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页,以及每页的记录数。
  • ?start_size&file_size:指定请求的起始条数,以及请求记录条数。
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
  • ?animal_type_id=1:指定筛选条件。

    参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

状态码

服务器向用户返回的状态码和提示信息,常见的有以下一些:

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [ * ]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [ * ]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [ * ]:表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [ * ]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 408 REQUEST_TIMEOUT - 请求超时
  • 409 CONFLICT - 冲突,大部分业务异常可以选择使用此状态
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH]:当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [ * ]:服务器发生错误,用户将无法判断发出的请求是否成功。

成功&异常处理

2XX

状态码200/201,表示成功。响应报文中,直接返回业务信息,不需要添加冗余的code/message等表示状态字段。例如:

1
2
3
4
{
"name": "1.docx",
"path": "/tmp/1.docx"
}

状态码204,表示成功,且不需要返回任何信息。响应报文为空即可。

公司框架中,响应报文强制包含状态信息,可无视以本条约定

4XX

状态码是4xx,表示普通的业务异常。应向用户返回出错信息。例如:

1
2
3
4
5
{
"code" "9999",
"message": "可向用户展示的错误信息",
"devMessage": "更详细报错信息,用于前端调试"
}

所有可捕获的业务异常,都应该使用4xx来表示,具体分类细节可自由定义。常用的错误码有:

  • 404 NOT_FOUND
  • 408 REQUEST_TIMEOUT
  • 409 CONFLICT
  • 422 UNPROCESSABLE_ENTITY

5XX

状态码5xx,表示服务内部异常。如服务积压、数据库连接异常等。

此类异常应在框架的公共异常处理部分统一处理,业务代码中无须进行实现。

一般统一归类到500 INTERNAL SERVER ERROR 下即可。

来源

本文档直接取自GitHub30-seconds-of-angular项目。

Table of contents

Beginner snippets

Intermediate snippets

Advanced snippets

Beginner snippets

Accessing Enums in template

Enums are great but they are not visible in Angular templates by default.
With this little trick you can make them accessible.

1
2
3
4
5
6
7
8
9
10
11
12
enum Animals {
DOG,
CAT,
DOLPHIN
}

@Component({
...
})
export class AppComponent {
animalsEnum: typeof Animals = Animals;
}


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: enums templates


Cheat Sheets and Checklists

Check out Angular Cheat Sheet or (alternative version) containing lots of useful information condensed in one place.

Also Angular Checklist contains is curated list of common mistakes made when developing Angular applications.

https://malcoded.com/angular-cheat-sheet/,https://angular.io/guide/cheatsheet,https://angular.io/guide/styleguide


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip cheat sheet


Component State Debugging

Debug the component state in the browser console by running:

1
ng.probe($0).componentInstance

$0 - is the DOM node currently selected in dev tools ($1 for the previous one and so on).

Bonus

With Ivy renderer engine:

1
ng.getComponent($0)

https://blog.angularindepth.com/everything-you-need-to-know-about-debugging-angular-applications-d308ed8a51b4


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips


Default ViewEncapsulation value

If you’re using ViewEncapsulation value which is different than default, it might be daunting to set the value manually for every component.

Luckily you can configure it globally when bootstrapping your app:

1
2
3
4
5
6
platformBrowserDynamic().bootstrapModule(AppModule, [
{
// NOTE: Use ViewEncapsulation.None only if you know what you're doing.
defaultEncapsulation: ViewEncapsulation.None
}
]);


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: configuration styling


hammerjs-gestures

To act upon swipes, pans, and pinhces as well as the other mobile gestures, you can use hammerjs with HostListener decorator, or an event binding,

1
npm install hammerjs
1
2
3
4
@HostListener('swiperight')
public swiperight(): void {
// Run code when a user swipes to the right
}
Bonus

Here are samples on how to use all of the hammerjs event bindings, you can use these events with a HostListener as well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!-- pan events -->
<div (pan)="logEvent($event)"></div>
<div (panstart)="logEvent($event)"></div>
<div (panmove)="logEvent($event)"></div>
<div (panend)="logEvent($event)"></div>
<div (pancancel)="logEvent($event)"></div>
<div (panleft)="logEvent($event)"></div>
<div (panright)="logEvent($event)"></div>
<div (panup)="logEvent($event)"></div>
<div (pandown)="logEvent($event)"></div>

<!-- pinch events -->
<div (pinch)="logEvent($event)"></div>
<div (pinchstart)="logEvent($event)"></div>
<div (pinchmove)="logEvent($event)"></div>
<div (pinchend)="logEvent($event)"></div>
<div (pinchcancel)="logEvent($event)"></div>
<div (pinchin)="logEvent($event)"></div>
<div (pinchout)="logEvent($event)"></div>

<!-- press events -->
<div (press)="logEvent($event)"></div>
<div (pressup)="logEvent($event)"></div>

<!-- rotate events -->
<div (rotate)="logEvent($event)"></div>
<div (rotatestart)="logEvent($event)"></div>
<div (rotatemove)="logEvent($event)"></div>
<div (rotateend)="logEvent($event)"></div>
<div (rotatecancel)="logEvent($event)"></div>

<!-- swipe events -->
<div (swipe)="logEvent($event)"></div>
<div (swipeleft)="logEvent($event)"></div>
<div (swiperight)="logEvent($event)"></div>
<div (swipeup)="logEvent($event)"></div>
<div (swipedown)="logEvent($event)"></div>

<!-- tap event -->
<div (tap)="logEvent($event)"></div>

https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/hammer_gestures.ts,http://hammerjs.github.io/api/#hammer.manager,https://angular.io/api/platform-browser/HammerGestureConfig


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components gestures


Loader Component

You can create own helper component and use it instead of *ngIf.

1
2
3
4
5
6
7
8
9
10
@Component({
selector: 'loader',
template: `
<ng-content *ngIf="!loading else showLoader"></ng-content>
<ng-template #showLoader>🕚 Wait 10 seconds!</ng-template>
`
})
class LoaderComponent {
@Input() loading: boolean;
}

For usage example:

1
<loader [loading]="isLoading">🦊 🦄 🐉</loader>

Note that the content will be eagerly evaluated, e.g. in the snippet below destroy-the-world will be created before the loading even starts:

1
<loader [loading]="isLoading"><destroy-the-world></destroy-the-world></loader>

https://medium.com/claritydesignsystem/ng-content-the-hidden-docs-96a29d70d11b,https://blog.angularindepth.com/https-medium-com-thomasburleson-animated-ghosts-bfc045a51fba


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tips good-to-know components templates


ng-content

With ng-content you can pass any elements to a component.
This simplifies creating reusable components.

1
2
3
4
5
6
7
8
9
@Component({
selector: 'wrapper',
template: `
<div class="wrapper">
<ng-content></ng-content>
</div>
`,
})
export class Wrapper {}
1
2
3
<wrapper>
<h1>Hello World!</h1>
</wrapper>

https://medium.com/p/96a29d70d11b


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components


ngIf else

*ngIf directive also supports else statement.

1
2
3
<div *ngIf="isLoading; else notLoading">loading...</div>

<ng-template #notLoading>not loading</ng-template>


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: ngif templates


Optional parameters in the middle

Navigate with matrix params:

the router will navigate to /first;name=foo/details

1
2
3
<a [routerLink]="['/', 'first', {name: 'foo'}, 'details']">
link with params
</a>

https://stackblitz.com/edit/angular-xvy5pd


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: routing


Renaming inputs and outputs

In certain cases @Input and @Output properties can be named differently than the actual inputs and outputs.

1
2
3
4
5
<div 
pagination
paginationShowFirst="true"
(paginationPageChanged)="onPageChanged($event)">
</div>
1
2
3
4
5
6
7
8
@Directive({ selector: '[pagination]'})
class PaginationComponent {
@Input('paginationShowFirst')
showFirst: boolean = true;

@Output('paginationPageChanged')
pageChanged = new EventEmitter();
}

Note: Use this wisely, see StyleGuide recommedation

https://angular.io/guide/styleguide#style-05-13


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: components templates


Safe Navigation Operator

The Safe Navigation Operator helps with preventing null-reference exceptions in component template expressions. It returns object property value if it exists or null otherwise.

1
<p> I will work even if student is null or undefined: {{student?.name}} </p>
Bonus
1
{{a?.b?.c}} 

Underneath will be compiled to.

1
(_co.a == null)? null: ((_co.a.b == null)? null: _co.a.b.c));

https://github.com/angular/angular/issues/791


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: object property handling tips good to know


trackBy in for loops

To avoid the expensive operations, we can help Angular to track which items added or removed i.e. customize the default tracking algorithm by providing a trackBy option to NgForOf.

So you can provide your custom trackBy function that will return unique identifier for each iterated item.
For example, some key value of the item. If this key value matches the previous one, then Angular won’t detect changes.

trackBy takes a function that has index and item args.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component({
selector: 'my-app',
template: `
<ul>
<li *ngFor="let item of items; trackBy: trackByFn">{{item.id}}</li>
</ul>
`
})
export class AppComponent {
trackByFn(index, item) {
return item.id;
}
}

If trackBy is given, Angular tracks changes by the return value of the function.

Now when you change the collection, Angular can track which items have been added or removed according to the unique identifier and create/destroy only changed items.

https://angular.io/api/common/NgForOf,https://angular.io/api/core/TrackByFunction


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components performance


Understanding Microsyntax

Under the hood Angular compiles structural directives into ng-template elements, e.g.:

1
2
3
4
5
<!-- This -->
<div *ngFor="let item of [1,2,3]">

<!-- Get expanded into this -->
<ng-template ngFor [ngForOf]="[1,2,3]" let-item="$implicit"></ng-template>

The value passed to *ngFor directive is written using microsyntax. You can learn about it in the docs.

Also check out an interactive tool that shows the expansion by Alexey Zuev

https://angular.io/guide/structural-directives#microsyntax,https://alexzuza.github.io/ng-structural-directive-expander/,https://angular.io/guide/structural-directives#inside-ngfor


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip structural directive microsyntax


Intermediate snippets

Accessing all nested form controls

Sometimes we need to work with every single Control is a form. Here’s how it can be done:

1
2
3
4
5
6
7
8
function flattenControls(form: AbstractControl): AbstractControl[] {
let extracted: AbstractControl[] = [ form ];
if (form instanceof FormArray || form instanceof FormGroup) {
const children = Object.values(form.controls).map(flattenControls);
extracted = extracted.concat(...children);
}
return extracted;
}

For examples use:

1
2
3
4
5
6
// returns all dirty abstract controls
flattenControls(form).filter((control) => control.dirty);

// mark all controls as touched
flattenControls(form).forEach((control) =>
control.markAsTouched({ onlySelf: true }));

https://angular.io/guide/reactive-forms


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: reactive forms tips good to know


Adding keyboard shortcuts to elements

It’s really easy to add keyboard shortcuts in the template:

1
<textarea (keydown.ctrl.enter)="doSomething()"></textarea>
Bonus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<input (keydown.enter)="...">
<input (keydown.a)="...">
<input (keydown.esc)="...">
<input (keydown.shift.esc)="...">
<input (keydown.control)="...">
<input (keydown.alt)="...">
<input (keydown.meta)="...">
<input (keydown.9)="...">
<input (keydown.tab)="...">
<input (keydown.backspace)="...">
<input (keydown.arrowup)="...">
<input (keydown.shift.arrowdown)="...">
<input (keydown.shift.control.z)="...">
<input (keydown.f4)="...">

https://alligator.io/angular/binding-keyup-keydown-events


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tips good-to-know


Bind to host properties with host binding

Every rendered angular component is wrapped in a host element (which is the same as component’s selector).

It is possible to bind properties and attributes of host element using @HostBinding decorators, e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Component, HostBinding } from '@angular/core';

@Component({
selector: 'my-app',
template: `
<div>Use the input below to select host background-color:</div>
<input type="color" [(ngModel)]="color">
`,
styles: [
`:host { display: block; height: 100px; }`
]
})
export class AppComponent {
@HostBinding('style.background') color = '#ff9900';
}


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: components


Component level providers

Generally we get one service instance per the whole application.
It is also possible to create an instance of service per component or directive.

1
2
3
4
5
6
@Component({
selector: 'provide',
template: '<ng-content></ng-content>',
providers: [ Service ]
})
export class ProvideComponent {}
1
2
3
4
5
@Directive({
selector: '[provide]',
providers: [ Service ]
})
export class ProvideDirective {}

https://angular.io/guide/hierarchical-dependency-injection#component-level-injectors,https://stackblitz.com/edit/angular-cdk-happy-animals


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tips components dependency-injection


Global event listeners

It is possible to add global event listeners in your Components/Directives with HostListener. Angular will take care of unsubscribing once your directive is destroyed.

1
2
3
4
5
6
7
8
9
@Directive({
selector: '[rightClicker]'
})
export class ShortcutsDirective {
@HostListener('window:keydown.ArrowRight')
doImportantThings() {
console.log('You pressed right');
}
}
Bonus

You can have multiple bindings:

1
2
3
4
5
@HostListener('window:keydown.ArrowRight')
@HostListener('window:keydown.PageDown')
next() {
console.log('Next')
}

You can also pass params:

1
2
3
4
@HostListener('window:keydown.ArrowRight', '$event.target')
next(target) {
console.log('Pressed right on this element: ' + target)
}


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: events components


Injecting document

Sometimes you need to get access to global document.

To simplify unit-testing, Angular provides it through dependency injection:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { DOCUMENT } from '@angular/common';
import { Inject } from '@angular/core';

@Component({
selector: 'my-app',
template: `<h1>Edit me </h1>`
})
export class AppComponent {

constructor(@Inject(DOCUMENT) private document: Document) {
// Word with document.location, or other things here....
}
}

https://angular.io/api/common/DOCUMENT


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: dependency injection


Mark reactive fields as touched

Here is the way to notify user that there are fields with non-valid values.

markFieldsAsTouched function FormGroup or FormArray as an argument.

1
2
3
4
5
6
function markFieldsAsTouched(form: AbstractControl): void {
form.markAsTouched({ onlySelf: true });
if (form instanceof FormArray || form instanceof FormGroup) {
Object.values(form.controls).forEach(markFieldsAsTouched);
}
}
Bonus

It’s very useful to check out more general method Accessing all nested form controls by Thekiba to work with controls.

https://angular.io/guide/reactive-forms


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: reactive forms validation tips good to know


Observables as outputs

EventEmitters used for @Output‘s are just Observables with an emit method.

This means that you can just use Observable instance instead, e.g. we can wire up FormControl value changes directly:

1
2
readonly checkbox = new FormControl();
@Output() readonly change = this.checkbox.valueChanges;


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip outputs


Passing template as an input

It’s possible to take a template as @Input for a component to customize the render

1
2
3
4
5
6
7
8
9
10
@Component({
template: `
<nav>
<ng-container *ngTemplateOutlet="template"></ng-container>
</nav>
`,
})
export class SiteMenuComponent {
@Input() template: TemplateRef<any>;
}
1
2
3
4
5
6
<site-menu [template]="menu1"></site-menu>

<ng-template #menu1>
<div><a href="#">item1</a></div>
<div><a href="#">item2</a></div>
</ng-template>

Note: ng-content should be used for most of the cases and it’s simpler and more declarative.
Only use this approach if you need extra flexibility that can’t be achieved with ng-content.

https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: template


Preseving whitespaces

By default Angular strips all whitespaces in templates to save bytes. Generally it’s safe.

For rare cases when you need to preserve spaces you can use special ngPreserveWhitespaces attribute:

1
2
3
4
5
<div ngPreserveWhitespaces>
(___()'`;
/, /`
jgs \\"--\\
</div>

You can also use preserveWhitespaces option on a component.

https://twitter.com/mgechev/status/1108913389277839360


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip


Reusing code in template

While the best way of reusing your code is creating a component, it’s also possible to do it in a template.

To do this you can use ng-template along with *ngTemplateOutlet directive.

1
2
3
4
5
6
7
8
9
10
11
<p>
<ng-container *ngTemplateOutlet="fancyGreeting"></ng-container>
</p>

<button>
<ng-container *ngTemplateOutlet="fancyGreeting"></ng-container>
</button>

<ng-template #fancyGreeting>
Hello <b>{{name}}!</b>
</ng-template>

https://angular.io/api/common/NgTemplateOutlet,https://angular.io/guide/structural-directives#the-ng-template


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: templates


Reusing existing custom pipes

If you need a custom pipe, before creating one, consider checking out the NGX Pipes package which has 70+ already implemeted custom pipes.

Here are some examples:

1
2
3
4
5
6
7
8
9
10
11
<p>{{ date | timeAgo }}</p> 
<!-- Output: "last week" -->

<p>{{ 'foo bar' | ucfirst }}</p>
<!-- Output: "Foo bar" -->

<p>3 {{ 'Painting' | makePluralString: 3 }}</p>
<!-- Output: "3 Paintings" -->

<p>{{ [1, 2, 3, 1, 2, 3] | max }}</p>
<!-- Output: "3" -->

https://github.com/danrevah/ngx-pipes


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip pipes library


Style bindings

You can use advanced property bindings to set specific style values based on component property values:

1
2
3
4
5
6
7
<p [style.background-color]="'green'">
I am in green background
</p>

<p [style.font-size.px]="isImportant ? '30' : '16'">
May be important text.
</p>
Bonus
1
2
3
4
5
6
7
8
<!-- Width in pixels -->
<div [style.width.px]="pxWidth"></div>

<!-- Font size in percentage relative to the parent -->
<div [style.font-size.%]="percentageSize">...</div>

<!-- Height relative to the viewport height -->
<div [style.height.vh]="vwHeight"></div>


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: styles


Two-way binding any property

Similar to how you can two-way bind [(ngModel)] you can two-way bind custom property on a component, for example [(value)]. To do it use appropriate Input/Output naming:

1
2
3
4
5
6
7
8
@Component({
selector: 'super-input',
template: `...`,
})
export class AppComponent {
@Input() value: string;
@Output() valueChange = new EventEmitter<string>();
}

Then you can use it as:

1
<super-input [(value)]="value"></super-input>


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip binding


Using APP_INITIALIZER to delay app start

It is possible to execute asynchronous task before the app start by providing a function returning promise using APP_INITIALIZER token.

1
2
3
4
5
6
7
8
9
10
11
@NgModule({
providers: [
{
provide: APP_INITIALIZER,
useValue: functionReturningPromise
multi: true
},
})
export class AppModule {}


https://hackernoon.com/hook-into-angular-initialization-process-add41a6b7e,https://angular.io/api/core/APP_INITIALIZER


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip


Window Location injection

For testing purposes you might want to inject window.location object in your component.
You can achieve this with custom InjectionToken mechanism provided by Angular.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const LOCATION_TOKEN = new InjectionToken<Location>('Window location object');

@NgModule({
providers: [
{ provide: LOCATION_TOKEN, useValue: window.location }
]
})
export class SharedModule {}

//...

@Component({
})
export class AppComponent {
constructor(
@Inject(LOCATION_TOKEN) public location: Location
) {}
}

https://itnext.io/testing-browser-window-location-in-angular-application-e4e8388508ff,https://angular.io/guide/dependency-injection


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: dependency-injection testing


Advanced snippets

Getting components of different types with ViewChild

It’s possible to use @ViewChild (also @ViewChildren and @ContentChild/Children) to query for components of different types using dependency injection.

In the example below we can use @ViewChildren(Base) to get instances of Foo and Bar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class Base {}

@Component({
selector: 'foo',
providers: [{ provide: Base, useExisting: Foo }]
})
class Foo extends Base {}

@Component({
selector: 'bar',
providers: [{ provide: Base, useExisting: Bar }]
})
class Bar extends Base {}

// Now we can require both types of components using Base.
@Component({ template: `<foo></foo><bar></bar>` })
class AppComponent {
@ViewChildren(Base) components: QueryList<Base>;
}

https://www.youtube.com/watch?v=PRRgo6F0cjs


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components dependency-injection


Router Custom Preloading

Angular allows us to control the way module preloading is handled.

There are 2 strategies provided by @angular/router: PreloadAllModules and NoPreloading. The latter enabled by default, only preloading lazy modules on demand.

We can override this behavior by providing custom preloading strategy: In the example below we preload all included modules if the connection is good.

1
2
3
4
5
6
7
8
9
10
11
import { Observable, of } from 'rxjs';

export class CustomPreloading implements PreloadingStrategy {
public preload(route: Route, load: () => Observable<any>): Observable<any> {
return preloadingConnection() ? load() : of(null);
}
}

const routing: ModuleWithProviders = RouterModule.forRoot(routes, {
preloadingStrategy: CustomPreloading
});

Note that that the example above would not be very efficient for larger apps, as it’ll preload all the modules.

https://angular.io/api/router/PreloadingStrategy,https://vsavkin.com/angular-router-preloading-modules-ba3c75e424cb,https://medium.com/@adrianfaciu/custom-preloading-strategy-for-angular-modules-b3b5c873681a,https://coryrylan.com/blog/custom-preloading-and-lazy-loading-strategies-with-angular


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: router


SVG

It is possible to use SVG tags in your Angular component, to create beautiful graphs and visualizations. There are 3 things you need to know:

  1. When binding an SVG attribute, use attr
    1
    <circle [attr.cx]="x" [attr.cy]="y"></circle>
  2. When creating sub-components, use attribute and not tag selector:
    1
    2
    // Not: <child-component></child-component>
    <g child-component></g>
    1
    @Component({selector: '[child-component]' })
  3. When using SVG tags in sub-components use svg prefix:
    1
    2
    3
    4
    @Component({
    selector: '[child-component]',
    template: `<svg:circle></svg:circle>`
    })


⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip SVG


简介

使用公司框架开发时,在HMR开发模式,前端如果使用自已封装的HttpClient,无法调用@delon/mock。

如果省略mock,直接与后端API一起联调,则需要将页面打进后端war包中,在Tomcat中测试。由于无法使用HMR,每次页面改动都要刷新页面,浪费一些时间。

通过配置HMR代理,可以将后端Tomcat 9090端口下的服务,代理到HMR 4200端口下,提升前端开发效率。

service模块配置

临时将/api/*添加到CAS例外

1
2
3
4
5
6
7
   <!-- begin CAS ignorePattern -->
<context-param>
<param-name>ignorePattern</param-name>
<param-value>
/api/*|.*.js|/assets/.*|.*.menu.*|/publicConfig/.*|.*./user/.*|.*.assets/i18n/[a-z]{2}\.json
</param-value>
</context-param>

临时将/api/*添加到JWT例外

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- begin JWT ValidationWithoutCas Filter -->
<filter>
<filter-name>JWT ValidationWithoutCas Filter</filter-name>
<filter-class>
com.yourcompany.common.authentication.filter.JwtValidationFilter
</filter-class>
<init-param>
<param-name>ignorePattern</param-name>
<param-value>
.*/cale/api/.*,.*.assets.*,.*.publicConfig.*,.*.css,.*.js,.*.html,.*.htm,.*.json,.*.svg,.*.cale/;jsessionid=.*,.*.cale/,.*.png,.*.login/user.*,.*.login/logout.*
</param-value>
</init-param>
</filter>

注意仅在本地修改,不要提交到git上

web模块配置

新建proxy.conf.json

1
2
3
4
5
6
7
{
"/api": {
"target": "http://10.30.22.xx:9090/cale",
"secure": false,
"logLevel": "debug"
}
}

注意target中的ip地址不要填写localhsot或127.0.0.1,否则可能无法正确转发
映射关系为: http://localhost:4200/api/attachment -> http://10.30.22.242:9090/cale/api/attachment

修改package.json

1
2
3
"scripts": {
"hmr": "ng serve -c=hmr --proxy-config=proxy.conf.json"
},

启动前端

1
npm run hmr

简介

Yarn作为一个新的包管理器,用于替代现有的npm客户端或者其他兼容npm仓库的包管理工具。

可以解决npm拉取依赖包较慢,依赖重复,以及cnpm容易丢失依赖的痛点。

特性:

  • 离线模式(Offline Mode):如果你之前安装过某个包,那么你可以在没有互联网连接的情况下,对这个包进行重新安装。
  • 确定性(Deterministic):不管安装顺序如何,相同的依赖在每台机器上会以完全相同的方式进行安装。
  • 网络性能:Yarn会对请求进行高效地排队,避免出现请求瀑布(waterfall),便于将网络的使用效率达到最大化。
  • 网络弹性(Network Resilience):单个请求的失败不会导致整个安装的失败,请求会基于故障进行重试。
  • 扁平模式(Flat Mode):将不匹配的依赖版本都会解析为同一个版本,避免重复创建。

官网

安装

npm方式:

1
npm install -g yarn

brew方式:

1
brew install yarn

对比

npm yarn desc
npm init yarn init 初始化包的开发环境
npm install yarn install 安装package文件里定义的所有依赖
npm install xxx —save yarn add xxx 安装某个依赖,默认保存到package中
npm uninstall xxx —save yarn remove xxx 移除某个依赖项目
npm install xxx —save-dev yarn add xxx —dev 安装某个开发时依赖项目
npm update xxx —save yarn upgrade xxx 更新某个依赖项目
npm install xxx –global yarn global add xxx 安装某个全局依赖项目
npm run/test yarn run/test 运行某个命令

官网

https://www.postgresql.org/
版本选择: 12.1, 11.6, 10.11, 9.6.16, 9.5.20, 9.4.25

参考文档

电子书

  • PostgreSQL实战
  • PostgreSQL10.1中文手册
  • 数据架构师的PostgreSQL修炼

macos下brew安装

暂时选择较流行的10.11版本

1
2
3
4
5
mv /usr/local/var/postgres /usr/local/var/postgres.old

brew install postgresql@10

echo 'export PATH="/usr/local/opt/postgresql@10/bin:$PATH"' >> ~/.zshrc
1
2
3
4
默认端口 5431,默认数据库 postgres,默认用户密码同与macos当前用户相同
查看版本号: pg_ctl -V
二进制路径: /usr/local/Cellar/postgresql@10/10.11
数据库路径: initdb /usr/local/var/postgres

brew服务启停

1
2
3
brew services run postgresql@10
brew services start postgresql@10
brew services stop postgresql@10

手工启停

1
2
3
pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start
pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log status
pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log stop -s -m fast

客户端

psql

1
2
psql -h 127.0.0.1 -p 5432 -d postgres -U yourname
psql -d postgres

pgcli

https://www.pgcli.com/

一个增强的psql命令行工具,提供高亮与代码补全,可替代psql

1
2
3
pip3 install pgcli

pgcli -d postgres

GUI

  • pgAdmin4 WEB版UI较差,可适用于数据库管理,界面逻辑清晰,可显示DDL便于学习
  • Navicat 老版本对PG支持差需使用V15.X,UI较好但隐藏过多细节,可适用于数据库管理与SQL编写
  • DataGrip 仅适用于SQL编写,全平台,在IDEA中已集成,对PG支持略差
  • DBeaver 仅适用于SQL编写,全平台,对PG支持略好,UI较差

    没有特别突出的方案。大而全选Navicat,偏重管理选择pgAdmin4,偏重开发运维选择DataGrip

psql控制台命令

\password:设置当前登录用户的密码
\h:查看SQL命令的解释,比如\h select。
?:查看psql命令列表。
\l:列出所有数据库。
\c [database_name]:连接其他数据库。
\d:列出当前数据库的所有表格。
\d [table_name]:列出某一张表格的结构。
\du:列出所有用户。
\e:打开文本编辑器。
\conninfo:列出当前数据库和连接的信息。
\password [user]: 修改用户密码
\q:退出

常用管理SQL

创建用户

1
2
CREATE USER testuser WITH PASSWORD 'testuser';
CREATE ROLE testuser WITH LOGIN PASSWORD 'testuser';

在PG中,用户和角色的概念相同

创建删除数据库

1
2
CREATE DATABASE testdb OWNER testuser;
DROP DATABASE testdb;

创建SCHEMA

1
CREATE SCHEMA testschema2 AUTHORIZATION zorrouser;

将DB权限赋予用户

1
GRANT ALL PRIVILEGES ON DATABASE testdb to testuser;

用户赋权限

1
2
ALTER ROLE testuser CREATEDB;
ALTER ROLE testuser WITH LOGIN;

其它赋权语句

1
2
ALTER SCHEMA public OWNER TO testuser;
ALTER ROLE testuser WITH PASSWORD 'testuser';

常用数据类型

http://www.postgres.cn/docs/9.3/datatype.html

类型 解释 长度
integer 存储整数 4字节
bigint 存储整数,大范围 8字节
numeric 用户指定的精度,精确 可自定
serial 自动递增整数 4字节
bigserial 大的自动递增整数 8字节
varchar(size) 字符串
text 可变长度字符串
timestamp 日期和时间(无时区) 8字节
boolean 它指定true或false的状态 1字节

远程访问

1
2
3
4
5
6
7
8
vi postgresql.conf
listen_addresses = '*'

vi pg_hba.conf
# TYPE DATABASE USER CIDR-ADDRESS METHOD
host all all 0.0.0.0/0 md5

sudo service postgresql restart

数据导入导出

备份与恢复