0%

Angular 安装使用

网上的三方教程大部分是 AngularJS, 但我要用的是 Angular, 是 AngularJS 的新版本, 有些东西不太一样. 关键这两者名字太像了, 这框架命名竟然不用版本号命名, 而只是比老版本少了两个字符, 搜索起来太坑了......

学习使用时使用的环境: - Node.js: 12.10.0 - npm: 6.10.3 - Angular CLI: 8.3.4

以及 Angular 一些包的环境:

1
2
3
4
5
6
7
8
Package                      Version
------------------------------------------------------
@angular-devkit/architect 0.803.4
@angular-devkit/core 8.3.4
@angular-devkit/schematics 8.3.4
@schematics/angular 8.3.4
@schematics/update 0.803.4
rxjs 6.4.0

官方文档: 中文文档.

安装与指令

安装

先安装 Node.js.

然后使用 npm 安装 Angular 的命令行界面工具 Angular CLI.

Angular CLI 是一个命令行界面工具, 可用于初始化、开发、构建和维护 Angular 应用. Angular CLI 的主版本会跟随它所支持的 Angular 主版本, 不过其小版本可能会独立发布.

1
npm install -g @angular/cli

指令

  1. 新建一个项目并运行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 创建项目文件夹, 生成一些配置文件
    # 会有一些个性化配置选择, 比如选择 CSS 还是 LESS,
    # JavaScript 还是 TypesCript 等. 选默认的即可.
    ng new <project_name>

    # 进入项目文件夹
    cd <project_name>

    # 运行项目
    # 新项目自带一个 hello world 程序
    # 在 http://localhost:4200/ 访问
    ng serve
  2. 在项目中新建模块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 新建一个 Component
    # 会在 `src/app` 下创建一个同名文件夹, 默认产生四个文件:
    # <name>.component.css, <name>.component.html,
    # <name>.component.ts, <name>.component.spec.ts
    ng generate component <name>
    ng g c <name> # 上面指令的缩写

    # 新建一个 Service
    # 会在 `src/app` 下创建两个文件 (不产生文件夹):
    # <name>.service.ts 和 <name>.service.spec.ts
    ng generate service <name>

    能够 ng generate 的东西还有很多, 但我还没用过.

    其中 .spec.ts 是单元测试编写用例用的.

  3. 单元测试

    1
    2
    # 在 http://localhost:9876/ 访问
    ng test

语法用法

内容太多了, 稍微整理点...

.html 中的语法

这玩意太坑了, 好好的 html 格式各种被魔改, 报错也没处说理去, 很多语法写在了引号内, vscode 插件也没法报语法错误.

关键这些语法也花里胡哨的, 没个规律.

插值语法 (双花括号)

1
{{ }}

会把双大括号内的值作为文本渲染出来. 例如

1
<span id="{{'aaa'}}">{{'233'+666}}</span>

会被渲染为

1
<span id='aaa'>233666</span>

. 同时渲染会执行转义, 例如

1
<span>{{'<br />'}}</span>

会被渲染为

1
<span>&lt;br /&gt;</span>

, 而不是

1
<span><br /></span>

, 所以想插入标签是不可能的. 它这么做目的是防止恶意注入等问题, 不过也可以使用 ng-bind-html-unsafe 指令来关掉默认的 html标签转义 (没用过, 用到了再写).

插值可以使用变量, 变量可以是 .ts 中定义的全局变量或函数, 也可以是 .html 中父标签定义的局部变量. 例如:

1
2
3
<ng-container *ngFor="let item of this.items">
<span>{{this.getValueOf(item)}}</span>
</ng-container>

*ngIf, *ngFor

写在 HTML 标签中.

例如在 Component 的 .ts 中定义了全局变量:

1
2
3
4
export class ItemComponent implements OnInit {
items = ['aa', 'bb', 'cc', 'dd'];
// ... ...
}

, 则在 Component 的 .html 中:

1
2
3
<div *ngFor="let item of this.items; let i = index;">
<span *ngIf="i < 2">{{item}}</span>
</div>

会被渲染为:

1
2
3
4
<div><span>aa</span></div>
<div><span>bb</span></div>
<div></div>
<div></div>

可以看到, 对于 ngIf, 条件成立则渲染该标签 (及其子标签), 否则不渲染.

对于 ngFor, 可以看到渲染是包括 for 语句所在的标签的, 即重复渲染的是当前标签, 而不是仅仅重复子标签.

两者注意事项

*ngIf, *ngFor 不能同时存在于一个标签. 如

1
<div *ngIf="..." *ngFor="..."></div>

是非法的, 可以用 ng-container 拆开, 改写为

1
2
3
<ng-container *ngIf="...">
<div *ngFor="..."></div>
</ng-container>

*ngIf 注意事项

我还以为它支持 js/ts 的所有语法, 结果它好像是自己实现了部分语法 (或者支持的语法版本老旧).

我想使用 *ngIf="key in obj" 来判断元素是否在 Object 内, 但它并不能识别, 报错了.

只能使用 *ngIf="obj.hasOwnProperty(key)" 来判断.

同时它也不支持 async 异步语法. *ngIf="func1(); func2();" 没问题, *ngIf="func1(); async func3();" 会报错. 只能想别的办法.

*ngFor 注意事项

  1. 如果我们希望重复多个同级标签, 却又不想用一个多余的标签包裹它们 , 要用 ng-container.

    1
    2
    3
    4
    5
    <ng-container *ngFor="let item of this.items">
    <div>ooooo</div>
    <div>{{item}}</div>
    <div>xxxxx</div>
    </ng-container>

    被渲染为:

    1
    2
    3
    4
    <div>ooooo</div><div>aa</div><div>xxxxx</div>
    <div>ooooo</div><div>bb</div><div>xxxxx</div>
    <div>ooooo</div><div>cc</div><div>xxxxx</div>
    <div>ooooo</div><div>dd</div><div>xxxxx</div>
  2. 引号内末尾的封号可加可不加.

    *ngFor="let e of arr"*ngFor="let e of arr;", 以及 *ngFor="let e of arr; let i = index"*ngFor="let e of arr; let i = index;" 都可以, 只要中间的封号加了就行.

  3. 遍历的不同语法:

    遍历 Array: let e of arr.

    遍历 Array 同时需要知道元素的数组下标: let e of arr; let i = index.

    遍历 Object (字典): let e of obj | keyvalue. 调用方法: , .

<ng-container></ng-container>

不会在最终编译出来的网页中存在该标签, 作用是框住一些元素, 方便 ngFor 和 ngIf 针对内部的元素进行操作. 或者配合 ng-template 使用 ( 见 ng-template 部分).

1
2
3
4
5
<div>
<ng-container>
<p>text</p><br /><p>text</p>
</ng-container>
</div>

会被渲染为

1
2
3
<div>
<p>text</p><br /><p>text</p>
</div>

, 相当于直接去掉了这个壳.

<ng-template></ng-template>

不会在最终编译出来的网页中存在该元素及其子元素, 这只是定义了一个模板. 只有在被 ng-container 调用时显现在调用的位置.

例子:

1
2
3
4
5
6
7
<!-- 定义模板, 模板 id 为 displaySetting, 传入参数 nodeData, 定义模板内变量 item 并赋值为 nodeData. -->
<ng-template #displaySetting let-item='nodeData'>{{item}}</ng-template>

<!-- 使用 ng-container 调用模板, 在 ng-container 处模板内元素会替换 ng-container 的位置. -->
<ng-container *ngTemplateOutlet='displaySetting; context: {nodeData: 233}'></ng-container>

<!-- 最终结果就是在 ng-container 处输出了个 233. -->

骚操作: 使用 ng-template 和 ng-container DFS 一颗树并输出:

(以下代码从真实代码提取的, 但是改完没跑一下测试, 应该能跑通...吧)

1
2
3
4
5
6
7
8
9
10
11
let tree = {
name: 'root',
children: [
{ name: 'aaa', chileren: [] },
{ name: 'bbb', chileren: [
{ name: 'bbbaaa', chileren: [] },
{ name: 'bbbbbb', chileren: [] }
] },
{ name: 'ccc', chileren: [] }
]
};
1
2
3
4
5
6
7
<ng-template #dfs let-item='data'>
<div>{{item.name}}</div>
<ng-container *ngFor="let child of item.children">
<ng-container *ngTemplateOutlet='dfs; context: {data: child}'></ng-container>
</ng-container>
</ng-template>
<ng-container *ngTemplateOutlet='dfs; context: {data: this.tree}'></ng-container>

输入输出语法 (), [], [()]

懒得写了, 官方教程这一块写的还算清晰.

.ts 语法

ng generate 指令生成时已经写好了基础模板.

但是我发现 Service 无法 implements OnInit, 实现了这个接口也不会初始化.

constructor()ngOnInit() 区别

ngOnInit 是 Angular Component 的生命周期中的一环, 一般尽量使用 ngOnInit. constructor 是类的初始化, 比 ngOnInit 更早, 可能会出现获取遍历的值为 undefined.

踩坑

ng serve 报错

报错如下:

1
2
系统找不到指定的路径。
An unhandled exception occurred: Cannot find module '@angular/compiler-cli'

是依赖包没正确安装.

在项目目录下, 先删除 node_modules 文件夹, 然后运行 npm install (或 yarn, 如果 npm install 不成功的话) 以安装 package.json 内的库.

国内源加速

其实这算 Node.js 的问题. 淘宝的源:

1
2
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install