Jack N @ GitHub

Full stack AI Engineer, focus on: React, Next.js, node.js and .Net

1. nodejs 有哪些特点?

是单线程的,但是有很高的可扩展性,使用 JavaScript 作为主流编程语言。使用的是非阻塞IO,异步处理机制和事件驱动。处理高效。

擅长 IO 密集型业务业务处理,不擅长 CPU 密集型业务;

2. 什么是事件循环 Event Loop

在进程启动动时,Node 会创建一个类似于 while(true)的循环,每执行一次循环的过程我们称之为 Tick。每个 Tick 的过程就是查看是否有事件等待处理,如果有,就取出时间相关的回调函数。如果存在相关联的回调函数,就执行它们。然后进入下一个循环;如果不再有事件待处理,就退出进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   ┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
  1. timers: 这个阶段执行 setTimeout()和 setInterval()设定的回调。
  2. I/O callbacks: 执行几乎所有的回调,除了 close 回调,timer 的回调,和 setImmediate()的回调。
  3. idle, prepare: 仅内部使用。
  4. poll: 获取新的 I/O 事件;node 会在适当条件下阻塞在这里。
  5. check: 执行 setImmediate()设定的回调。
  6. close callbacks: 执行比如 socket.on(‘close’, …)的回调。

在底层, Node 是通过 libuv 来实现多线程的。Libuv库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给 V8 引擎。

另:浏览器下的 Event Loop 如下:

  1. 所有任务都在js执行线程上执行,形成一个执行栈(Execution Context Stack)
  2. 在js执行线程之外还存在一个任务队列(Task Queen),系统把异步任务放到任务队列中,然后主线程继续执行后续的任务
  3. 一旦执行栈中所有的任务执行完毕,系统就会读取任务队列。如果这时异步任务已结束等待状态,就会从任务队列进入执行栈,恢复执行
  4. js执行线程不断重复上面的第三步

3. 在每个 tick 的过程中,如何判断是否有事件需要处理呢?

  1. 每个事件循环中有一个或者多个观察者,而判断是否有事件需要处理的过程就是向这些观察者询问是否有要处理的事件。
  2. 在 Node 中,事件主要来源于网络请求、文件的 I/O 等,这些事件对应的观察者有文件 I/O 观察者,网络 I/O 的观察者。
  3. 事件循环是一个典型的生产者/消费者模型。异步 I/O,网络请求等则是事件的生产者,源源不断为 Node 提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。

4. 什么是回调函数?

回调函数是指用一个函数作为参数传入另一个函数,这个函数会被在某个时机调用。

5. node.js 计时器 promise.then, process.nextTick, setTimeout,setImmediate 执行顺序

首先了解 2 个概念

  • macro-task(宏任务): script ,setTimeout, setInterval, setImmediate, I/O, UI rendering.Event Loop 在每个阶段执行的任务;
  • micro-task (微任务): process.nextTick, Promise,Object. fvEvent Loop 在每个阶段之间执行的任务 observe,MutationObserver

对于 node.js, micro-task 的任务优先级高于 macro-task 的任务优先级,所以微任务普遍优先于宏任务的执行。

另一种优先级顺序,是“观察者优先级”, 在每次轮训检查中,各观察者的优先级分别是:idle观察者 > I/O观察者 > check观察者。

  • idle 观察者:process.nextTick
  • I/O 观察者:一般性的 I/O 回调,如网络,文件,数据库 I/O 等
  • check 观察者:setTimeout > setImmediate (setTimeout(()=>{},0)优先于 setImmediate 执行);
  • 总结
  1. 同步代码执行顺序优先级高于异步代码执行顺序优先级;
  2. new Promise(fn)中的 fn 是同步执行;
  3. process.nextTick()>Promise.then()>setTimeout>setImmediate。

6. next tick 和 setImmediate 的区别是什么?

  • NextTick 会等待当前的 event 执行完成或者下一轮儿事件循环到达再执行。

  • SetImmediate, 会在下一轮的事件循环中,执行回调并且返回当前的循环来做读写操作.

7. node.js 模块加载机制

Nodejs 遵循 commonjs 规范的模块加载机制,使用 require 加载文件,使用 exports 或 module.exports 导出文件

8. 什么是 globals?

有三个 global 的关键字。

  1. Global 代表的是最上层的命名空间,用来管理所有其他的全局对象。
  2. Process 是一个全局对象,可以把异步函数转化成异步回调, 它可以在任何地方被访问,它主要是用来返回系统的应用信息和环境信息.
  3. Buffer, 是用来处理二进制数据的类.

9. 什么是 EventEmitter?

EventEmitter 是 node 中一个实现观察者模式的类,主要功能是监听和发射消息,用于处理多模块交互问题.

主要用于: 1) 模块间传递消息 2) 回调函数内外传递消息 3) 处理流数据,因为流是在 EventEmitter 基础上实现的. 4) 观察者模式发射触发机制相关应用

10. process 有哪些常用方法?

process.stdin, process.stdout, process.stderr, process.on, process.env, process.argv, process.arch, process.platform, process.exit

11. 解释一下什么是 reactor pattern。

reactor 设计模式是 event-driven architecture 的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor 会解耦并发请求的服务并分发给对应的事件处理器来处理。

Reactor pattern 主要是非阻滞的 i/o 操作。提供一个回调函数来关联 io 操作。io 请求完成以后会不会提交给 demultiplexer, 这是一个通知接口用来处理并发性的非阻滞的 io 操作,这个功能是通过查询一个 event loop 来实现的.

12. 什么是错误优先的回调函数

node.js 广泛使用异步编程,而异步中的异常很难在主程序中捕获。为此,我们将异步中的异常、错误,通过回调函数传递给主程序。

错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。

13. 如何避免回调地狱

  1. 模块化:将回调函数分割为独立的函数
  2. 使用 Promise/ansyc (ES6)
  3. 使用 yield 来计算生成器或 Promise

14. 什么是 Promise?

Promise 可以帮助我们更好地处理异步操作。采用类似同步结构的代码,实现异步处理,同时避免回调地狱。

15. 使用 NPM 有哪些好处?

通过 NPM,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号。 对于 Node 应用开发而言,你可以通过 package.json 文件来管理项目信息,配置脚本, 以及指明项目依赖的具体版本。

16. 什么是 Stub?

Stub 是用于模拟一个组件或模块的函数或程序,在测试用例中很常用。简单的说,你可以用 Stub 去模拟一个方法,从而避免调用真实的方法, 使用 Stub 你还可以返回虚构的结果。你可以配合断言使用 Stub。

17. 什么是测试金字塔?

测试金字塔指的是: 测试用例从下到上,包括单元测试、集成测试、端到端测试;当我们在编写测试用例时,底层的单元测试应该远比上层的端到端测试要多。

18. Node 模块机制

Node 中,每个文件模块都是一个对象,所有的模块都是 Module 的实例。

19. Node.js require 的模块加载机制

1、先计算模块路径
2、如果模块在缓存里面,取出缓存;如果不存在生成模块实例,存入缓存
3、加载模块
4、输出模块的 exports 属性

20. V8 的垃圾回收机制

在 V8 中,主要将内存分为新生代和老生代两代。新生代中的对象存活时间较短的对象,老生代中的对象存活时间较长,或常驻内存的对象。
新生代中的对象主要通过 Scavenge 算法进行垃圾回收。这是一种采用复制的方式实现的垃圾回收算法。它将堆内存一份为二,分为 2 个 semispace。在这两个 semispace 空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的 semispace 空间称为 From 空间,处于闲置状态的空间称为 To 空间。

  1. 当开始垃圾回收的时候,会检查 From 空间中的存活对象,这些存活对象将被复制到 To 空间中,而非存活对象占用的空间将会被释放。完成复制后,From 空间和 To 空间发生角色对换。
  2. 应为新生代中对象的生命周期比较短,就比较适合这个算法。
  3. 当一个对象经过多次复制依然存活,它将会被认为是生命周期较长的对象。这种新生代中生命周期较长的对象随后会被移到老生代中。
    老生代主要采取的是标记清除的垃圾回收算法。与 Scavenge 复制活着的对象不同,标记清除算法在标记阶段遍历堆中的所有对象,并标记活着的对象,只清理死亡对象。活对象在新生代中只占叫小部分,死对象在老生代中只占较小部分,这是为什么采用标记清除算法的原因。

标记清楚算法的问题:
主要问题是每一次进行标记清除回收后,内存空间会出现不连续的状态

  1. 这种内存碎片会对后续内存分配造成问题,很可能出现需要分配一个大对象的情况,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次回收是不必要的。
  2. 为了解决碎片问题,标记整理被提出来。就是在对象被标记死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存。

21. child-process

node 是异步非阻塞的,这对高并发非常有效.可是我们还有其它一些常用需求,比如和操作系统 shell 命令交互,调用可执行文件,创建子进程进行阻塞式访问或高 CPU 计算等,child-process 就是为满足这些需求而生的.child-process 顾名思义,就是把 node 阻塞的工作交给子进程去做.

22. 创建子进程的方法有哪些,简单说一下它们的区别

创建子进程的方法大致有:

  1. spawn(): 启动一个子进程来执行命令
  2. exec(): 启动一个子进程来执行命令,与 spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况
  3. execFlie(): 启动一个子进程来执行可执行文件
  4. fork(): 与 spawn()类似,不同电在于它创建 Node 子进程需要执行 js 文件
  5. spawn()与 exec()、execFile()不同的是,后两者创建时可以指定 timeout 属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死
  6. exec()与 execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。

(exec 可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile 是执行一个文件; spawn 是流式和操作系统进行交互; fork 是两个 node 程序(javascript)之间时行交互.)

23. 怎样充分利用多个 CPU?

一个 CPU 运行一个 node 实例。 (Child_Process)

24. 有哪些方法可以让 node 程序遇到错误后自动重启?

Linxu: 1) runit 2) forever 3) nohup npm start &
Winwow: Windows service(auto restart)

25. 关于 Express 框架

25.1. express 生成器的作用是什么?

通过应用生成器工具 express-generator 可以快速创建一个应用的框架, 包含一整套配置好的服务器配置, 文件和文件夹等, 包括静态资源的暴露等, 包括路由的配置, 和模板引擎配置, 以及 404 的处理,异常处理等;

25.2. express 优点是什么?

Express 的优点是线性逻辑:路由和中间件完美融合,通过中间件形式把业务逻辑细分,简化,一个请求进来经过一系列中间件处理后再响应给用户,再复杂的业务也是线性了,清晰明了。

25.3. 什么是中间件

  • 中间件是可以访问请求对象,响应对象以及 next 应用程序请求-响应周期中的函数,使用 app.use()来使用/定义中间件;
  • Express 是一个自身功能极简,完全是路由和中间件构成一个 web 开发框架;
  • 从本质上来说,一个 Express 应用就是在调用各种中间件。封装了一些或许复杂但肯定是通用的功能, 非内置的中间件需要通过安装后,require 到文件就可以运行。

25.4. express 缺点是什么?

Express 是基于 callback 来组合业务逻辑。Callback 有两大硬伤,一是不可组合,二是异常不可捕获。

25.5. ejs 作用是什么?

EJS 是一个 Javascript 模板库, 用来从 JSON 数据中生成 HTML 文件。其他模板库(模板引擎如 jade/pug);

25.6. 什么是后端渲染项目?

浏览器请求静态网页资源, 服务器端会在后端把数据渲染到 HTML 页面上, 再把 html 文件内的字符串一起返回给浏览器进行展示的一种手段, Nodejs 中体现为 ejs 和 pug 模板引擎;

session 是区别于数据库存在的一种服务器临时存储技术, 它主要存储一些无需持久化的数据, 比如临时的登录状态信息等

cookie 是存在于浏览器上的一种浏览器本地存储的方式, 同域名下的 cookie 不同标签页可以共享, 默认过期时间是浏览器关闭时, 而且在进行 http 请求时, 会自动带上浏览器全部的 cookie 发给后台, 后台也可以获取 cookie, 设置可以在响应时, 想浏览器中设置 cookie。

25.8. 跨域(CORS)是什么, 如何解决跨域?

当 ajax 请求所在域名或接口和请求目标 url 的域名或接口, 有一个不同, 即发生了跨域请求, 浏览器会阻止这次 ajax 请求.

解决跨域请求的方法:

  1. 让后台(API)开启跨域支持;
  2. 通过iframe
  3. 使用 jsonp 方式处理跨域(需要服务器端支持);
  4. 反向代理(API Gateway),UI/API 配置到同一个API Gateway,浏览器端看到的结果就是在同一个域了;
  5. UI有编程能力(node.js/ asp.net)的话,可以代理其他站点的API

25.9. express4 中 app 和 router 的区别

app 可以认为是全局的,方便注入中间件;router 主要针对具体的 routing,除了注入中间件,具体处理 routing 对应的核心业务逻辑;
app 级路由不方便模块化封装,而且 app.js 文件没有路由代码,路由代码在 router 文件中,而且 router 方便模块化封装

25.10. Cookies 如何防范 XSS 攻击?

XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者在返回的 HTML 中插入 JavaScript 脚本。为了减轻这些攻击,需要在 HTTP 头部配置 set-cookie:

  • HttpOnly - 这个属性可以防止 cross-site scripting,因为它会禁止 Javascript 脚本访问 cookie。

  • secure - 这个属性告诉浏览器仅在请求为 HTTPS 时发送 cookie。

结果应该是这样的: Set-Cookie: sid=; HttpOnly. 使用 Express 的话,cookie-session 默认配置好了。

25.11. Koa 和 Express 的对比

  1. Express 是一个完整的 nodejs 应用框架。Koa 是由 Express 团队开发的,但是它有不同的关注点。Koa 致力于核心中间件功能。nodejs 中间件是访问请求对象(req)和响应对象(res)的例程。 Express 包含一个完整的应用程序框架,具有路由和模板等功能。
  2. Koa 对 Express 进行了扩展,并充分利用了 ES7 新的语法。Koa 的 Context 对象是对 Express 核心请求和应答对象的扩展,另外利用 async/await 来消除回调(callback)陷阱。
  3. Koa 的中间件的设计是洋葱模型。
  4. Koa 不是立即响应,是整个中间件处理完成在最外层进行了响应,而 Express 则是立即响应

26. KOA

26.1. KOA 洋葱模型

中间件执行就像洋葱一样,最早 use 的中间件,就放在最外层。处理顺序从左到右,左边接收一个 request,右边输出返回 response。
一般的中间件都会执行两次,调用 next 之前为第一次,调用 next 时把控制传递给下游的下一个中间件。当下游不再有中间件或者没有执行 next 函数时,就将依次恢复上游中间件的行为,让上游中间件执行 next 之后的代码。



—————- END —————-

1. 概要

大多数 Angular 短语都是日常用语或计算机术语,但是在 Angular 体系中,它们有特别的含义。

1. 概要

预编译 (ahead-of-time, AOT)
即时编译 (just-in-time, JIT)
Angular元素(element, Angular Element)
注解(Annotation): 注解也叫装饰器;
装饰器(decorator | decoration): 注解也叫装饰器;
应用外壳(app-shell)
架构(Architect)
指令 directives
属性型指令(attribute directives)
绑定 (binding)
启动/引导 (bootstrap)
构建器(Builder)
大小写类型(case types)
小驼峰形式(camelCase):小驼峰(也叫标准驼峰)形式的第一个字母要使用小写形式。
大驼峰形式(UpperCamelCase)或叫帕斯卡形式(PascalCase)
中线形式(dash-case)或叫烤串形式(kebab-case)
下划线形式(underscore_case)或叫蛇形形式(snake_case)
大写下划线形式(UPPER_UNDERSCORE_CASE)或叫大写蛇形形式(UPPER_SNAKE_CASE)
变更检测(change detection)
类装饰器(class decorator)
类字段装饰器(class field decorator)
集合(collection)
命令行工具,命令行客户端(CLI, Angular CLI)
组件 (component)
配置(configuration)
内容投影 (content projection)
自定义元素(Custom element):也叫 Web Component
数据绑定 (data binding)
可声明对象(declarable)
装饰器(decorator | decoration)
依赖注入(dependency injection)
DI 令牌(Token)
差异化加载 (differential loading)
指令 (directive)
领域特定语言(domain-specific language, DSL)
动态组件加载(dynamic component loading)
急性加载(Eager Loading)
惰性加载(Lazy loading)
元素(Element, Angular Element)
入口点(Entry Point)
表单控件(form control)
表单模型(form model)
表单验证(form validation)
不可变性(immutability)
可注入对象(injectable)
注入器 (injector)
输入属性 (input)
输出属性 (output)
插值 (interpolation)
常春藤引擎(Ivy)
惰性加载(Lazy loading)
库(Library)
生命周期钩子(Lifecycle hook)
模块 (module)
ngcc (Angular 兼容性编译器)
npm 包 (npm package)
ngc (ngc 是一个 TypeScript 到 JavaScript 的转译器)
可观察对象(Observable)
观察者(Observer)
管道(pipe)
平台(platform)
腻子脚本(polyfill)
项目(project)
提供者 (provider)
响应式表单 (reactive forms)
解析器(resolver)
路由守卫 (route guard)
路由器 (router)
路由出口(router outlet)
路由组件 (routing component)
规则(rule)
原理图(schematic)
Schematics CLI (Schematics 自带了一个命令行工具)
范围化包 (scoped package)
服务端渲染 (server-side rendering)
服务 (service)
结构型指令(Structural directives)
订阅者(Subscriber)
目标 (target)
模板 (template)
模板驱动表单(template-driven forms)
模板表达式(template expression)
模板引用变量 (template reference variable)
令牌(Token)
转译(transpile)
目录树(tree)
TypeScript 配置文件
单向数据流 (unidirectional data flow)
视图 (view)
视图引擎(View Engine)
视图树(View hierarchy)
Web 组件 (web component)
工作空间(Workspace)
工作空间配置(Workspace configuration)
区域 (zone)



—————- END —————-






======================

1. 概要

众所周期,javascript是一个很老的语言,起起伏伏,逐渐成为流行语言(主流语言),说白了,也是因为早期各个浏览器(ie/firefox/sarfari/chrome)互不妥协,谁也说服不了对方接收自己的标准,不过却都同时支持javascript。对于广大程序员来说,随着web流行度越来越高,那么为了同时支持大多数浏览器,那么使用标准的javascript成为趋势。而第三方(coffeescript, typescript, dart等)也都是选择将代码编译为JavaScript,然后在浏览器端执行。

作为从C#、java等语言转到javascript的同学来说,会发现不少C#、java的“我觉得应该是这样”的东西,JavaScript中却是不一样的。下面总结一下

2. this

对C#、Java等语言,this就是当前对象,但是javascript不是,简单来说:

  • 全局this 是window;
  • 函数this 是调用者;
  • 构造函数的this 是new 之后的新对象,
  • call 和 apply bind的this第一个参数

3. 原型链, prototype

  • 函数对象都包含prototype属性(函数的原型对象),其作用就是让该函数所实例化的对象们都可以找到公用的属性和方法;
  • constructor属性的含义就是指向该对象的构造函数
  • ①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。
  • JavaScript 的每个对象都继承另一个父级对象,父级对象称为原型 (prototype) 对象。
  • 每一个实例对象都有一个私有属性__proto__指向其构造函数的原型对象prototype;该原型对象也会作为实例对象有一个私有属性__proto__,层层向上直到一个对象的原型对象值为null。
  • 当访问一个对象的属性或方法时,js引擎会先查找该对象本身是否包含,如果没有,会去该对象的__proto__属性所指向的原型对象上找,如果没有,会继续向上一层找,直到某个对象的__proto__值为null,这就是原型链。
  • 每个构造函数都有一个prototype属性,指向另外一个对象,说明整个对象所有的属性和方法都会被构造函数所拥有。

4. new操作符都做了什么

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一。

new Object()举例:

  1. 创建一个新对象
  2. 把新对象的原型指向构造函数的prototype
  3. 把构造函数里的this指向新对象
  4. 返回这个新对象

5. === VS ==

1
2
3
4
5
100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true

很有意思吧, 看看这两个,是等效的:

1
2
obj == null
obj === null || obj === undefined

6. 闭包

在JavaScript中的一大特点就是闭包,很多高级应用(早期的jquery,现在的angular、vue)都要依靠闭包来实现。由于闭包会使得函数中的变量都被保存在内存中,会消耗很大的内存,导致页面的性能问题,甚至导致内存泄漏。建议不要或者少用闭包。

1
2
3
4
5
6
function outerMethod() {

var a = "some value";
var innerMethod = function() { console.log( a + "!") ;}
innerMethod();
}

7. 总结

  1. Javascipt 中有些内容和其他编程语言不一致,需要牢记
  2. JavaScript中拿不准的东西,可以简单测试一下,免得代码写好后出错。
  3. 简单测试,可以打开浏览器,直接F12(进入Dev Tools),再选择Console(控制台), 直接输入要测试的内容,查看返回结果



—————- END —————-






======================

环境:

  • Angular CLI: 11.0.6
  • Angular: 11.0.7
  • Node: 12.18.3
  • npm : 6.14.6
  • IDE: Visual Studio Code

1. 概要

实际的Angular项目,肯定不是一个简单的Hello World程序,会包含很多的功能,很多的文件。那么如何更好的组织这些文件呢?官方给了一个原则,可以供参考:
https://angular.io/guide/styleguide#application-structure-and-ngmodules。
下面,我们来通过一个例子具体解释一下。

2. 目录结构(工程结构)推荐

2.1. 总的原则

  1. 基于Angular CLI创建模块(module),组件(component)等等的内容;
  2. 源代码都放到src 文件夹下;
  3. 应用的根目录创建一个 NgModule, 并命名为app.module.ts(例如 /src/app,这个Angular CLI 会自动帮我们做)
  4. 组件具有多个伴生文件 (.ts、.html、.css 和 .spec),建议为它创建一个文件夹;(Angular CLI 会自动帮我们做)
  5. 为每一组功能(特性区)创建一个模块(NgModule);(这个也方便我们应用惰性加载/延迟加载,预加载)
  6. 在 src/app/shared 目录中创建名叫 SharedModule 的共享模块,方便其他功能调用;

2.2. 实例

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
src/ 目录下:
+---app
| | app-routing.module.ts
| | app.component.css
| | app.component.html
| | app.component.spec.ts
| | app.component.ts
| | app.module.ts
| |
| +---feature1
| | | ......
| |
| +---feature1
| | | ......
| |
| +---core
| | | core.module.ts
| | | ....
| |
| \---shared
| | shared.module.ts
| |
| +---components
| +---pipes
| +---services
|
+---assets
| .gitkeep
|
\---environments
environment.prod.ts
environment.ts

说明:

  1. 根目录下的NgModule 默认是 app.module.ts, 不要改名,方便阅读
  2. app 下,每个目录,同时也都是一个模块-NgModule
  3. 一个项目,一般包含多个功能(feature)模块
  4. 推荐使用共享模块-SharedModule,将通用的、功能的功能(service/component/pipe等)放到改模块中。 (下文详细介绍)
  5. 推荐使用核心模块-CoreModule,可以将项目的一些全局的设置、UI等放到该模块。如header、footer组件,安全组件(服务),上下文存储服务等。

2.3. 共享模块-SharedModule

上文提到,推荐使用共享模块-SharedModule,将通用的、功能的功能(service/component/pipe等)放到改模块中。具体存放内容包括:

  1. 共享模块中声明那些可能被特性模块引用的可复用组件(Component)、指令(Directive)和管道(Pipe)。
  2. 如果放置服务(Service),由于服务的单例特性,共享模块中只建议放置无状态的服务(Service),对于有状态、或者和业务紧密相关的服务,建议放到CoreModule中。
  3. SharedModule 中声明(declarations)和导出(exports)所有组件、指令和管道,方便其他模块调用
  4. 共享模块(SharedModule)在项目中处于底层,从逻辑上,只能由其他业务逻辑模块调用,不能调用其他模块;
  5. 共享模块(SharedModule)不建议使用延迟加载(惰性加载),因为这样会破坏服务的单例特性;

3. 高级应用 - angular库、工作空间(workspace )

想一想你是如何安装angular相关的类库的?我们使用的是npm install xxx。那么这些第三方angular库是如何开发的呢?我们是否可以把通用的内容写成Angular类库,然后方便在多个angular项目间共享了?又或者直接发布到官方npm站点?

Angular 从6.0开始,引入了工作区的概念。使用Angular CLI,默认创建的就是一个工作空间(workspace)。一个工作空间(workspace)可以有一个主项目,同时可以有多个子项目。当然这些子项目可以是angular的application, 也可以是Library。Angular的子项目,都在app下的projects 之下,和src目录平级。

这样,一般来说,我们的项目中,除了主程序,还可以包含多个子类库。这样在开发时方便主程序、类库同时开发调试;也可以项目结束后,单独发布类库,实现项目之间的代码共享。

创建子项目也很简单,子项目有2种类型,application(可以启动的)和Library(Angular类库),默认都在projects文件夹下:

  1. 创建子Applicaton
1
ng generate application <name> [options]
  1. 创建子类库(Angular Library)
1
ng generate library <name> [options]

3.1. 目录结构示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+---projects
| \---ng-lib-a
| \---src
| \---lib
| \---ng-lib-b
| \---src
| \---lib
\---src
+---app
| +---feature1
| +---feature2
| +---core
| | +---footer
| | \---header
| \---shared
| +---components
| +---pipes
| \---services
+---assets
\---environments

4. 总结

  1. Angular CLI默认创建的Angular项目,实际是一个工作空间(workspace ),在其内部还可以创建多个子项目
  2. 子项目可以是Application,也可以是类库(Library);
  3. 对于主项目(一般来说是一个Application),以功能模块的方式进行组织;
  4. 公共内容,建议放到共享模块-SharedModule中。
  5. 对于功能模块,可以通过惰性加载(延迟)加载,提高首页加载速度;同时通过预加载技术,可以在空闲时间加载这部分模块,使用户体验更好。



—————- END —————-






======================

1. 概要

Flexible Box翻译过来就是弹性盒子、弹性布局,是css3中新增的一种布局方式,是当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。引入弹性布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。

弹性盒子由弹性容器(Flex container)和弹性子元素(Flex item)组成。

  • 弹性容器通过设置 display 属性的值为 flex 或 inline-flex将其定义为弹性容器。
  • 弹性容器内包含了一个或多个弹性子元素。
属性 描述
display 指定 HTML 元素盒子类型。
flex-direction 指定了弹性容器中子元素的排列方式
justify-content 设置弹性盒子元素在主轴(横轴)方向上的对齐方式。
align-items 设置弹性盒子元素在侧轴(纵轴)方向上的对齐方式。
flex-wrap 设置弹性盒子的子元素超出父容器时是否换行。
align-content 修改 flex-wrap 属性的行为,类似align-items, 但不是设置子元素对齐,而是设置行对齐
flex-flow flex-direction 和 flex-wrap 的简写
order 设置弹性盒子的子元素排列顺序。
align-self 在弹性子元素上使用。覆盖容器的 align-items 属性。
flex 设置弹性盒子的子元素如何分配空间。

2. justify-content 属性

内容对齐(justify-content)属性应用在弹性容器上,把弹性项沿着弹性容器的横轴(主轴线,main axis)对齐。

·justify-content 语法:

1
justify-content: flex-start | flex-end | center | space-between | space-around

可选项:

  • flex-start:弹性项目向行头紧挨着填充。这个是默认值。第一个弹性项的main-start外边距边线被放置在该行的main-start边线,而后续弹性项依次平齐摆放。
  • flex-end:弹性项目向行尾紧挨着填充。第一个弹性项的main-end外边距边线被放置在该行的main-end边线,而后续弹性项依次平齐摆放。
  • center:弹性项目居中紧挨着填充。(如果剩余的自由空间是负的,则弹性项目将在两个方向上同时溢出)。
  • space-between:弹性项目平均分布在该行上。如果剩余空间为负或者只有一个弹性项,则该值等同于flex-start。否则,第1个弹性项的外边距和行的main-start边线对齐,而最后1个弹性项的外边距和行的main-end边线对齐,然后剩余的弹性项分布在该行上,相邻项目的间隔相等。
  • space-around:弹性项目平均分布在该行上,两边留有一半的间隔空间。如果剩余空间为负或者只有一个弹性项,则该值等同于center。否则,弹性项目沿该行分布,且彼此间隔相等(比如是20px),同时首尾两边和弹性容器之间留有一半的间隔(1/2*20px=10px)。

3. align-items 属性

align-items 设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式。

align-items语法

1
align-items: flex-start | flex-end | center | baseline | stretch

可选项:

  • flex-start:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界。
  • flex-end:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界。
  • center:弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。
  • baseline:如弹性盒子元素的行内轴与侧轴为同一条,则该值与’flex-start’等效。其它情况下,该值将参与基线对齐。
  • stretch:如果指定侧轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。

4. flex-wrap 属性

flex-wrap 属性用于指定弹性盒子的子元素换行方式。

语法

1
flex-wrap: nowrap|wrap|wrap-reverse|initial|inherit; 

可选项:

  • nowrap - 默认,弹性容器为单行。该情况下弹性子项可能会溢出容器。
  • wrap - 弹性容器为多行。该情况下弹性子项溢出的部分会被放置到新行,子项内部会发生断行
  • wrap-reverse -反转 wrap 排列。

5. align-content 属性

align-content 属性用于修改 flex-wrap 属性的行为。类似于 align-items, 但它不是设置弹性子元素的对齐,而是设置各个行的对齐。

语法

1
align-content: flex-start | flex-end | center | space-between | space-around | stretch

各个值解析:

  • stretch - 默认。各行将会伸展以占用剩余的空间。
  • flex-start - 各行向弹性盒容器的起始位置堆叠。
  • flex-end - 各行向弹性盒容器的结束位置堆叠。
  • center -各行向弹性盒容器的中间位置堆叠。
  • space-between -各行在弹性盒容器中平均分布。
  • space-around - 各行在弹性盒容器中平均分布,两端保留子元素与子元素之间间距大小的一半。

6. 居中

使用弹性盒子,居中变的很简单,只想要设置 margin: auto; 可以使得弹性子元素在两上轴方向上完全居中:

如

1
2
3
4
5
.flex-item {
width: 100px;
height: 100px;
margin: auto;
}

7. align-self

align-self 属性用于设置弹性元素自身在侧轴(纵轴)方向上的对齐方式。

选项:

  • auto:如果’align-self’的值为’auto’,则其计算值为元素的父元素的’align-items’值,如果其没有父元素,则计算值为’stretch’。
  • flex-start:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界。
  • flex-end:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界。
  • center:弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。
  • baseline:如弹性盒子元素的行内轴与侧轴为同一条,则该值与’flex-start’等效。其它情况下,该值将参与基线对齐。
  • stretch:如果指定侧轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。

8. 总结

1.



—————- END —————-






======================

环境:

  • Angular CLI: 11.0.6
  • Angular: 11.0.7
  • Node: 12.18.3
  • npm : 6.14.6
  • IDE: Visual Studio Code

1. 概要

当我们完成angular的开发后,如何部署到服务器呢?

2. 编译打包

2.1. 基本打包命令

基于Angular CLI生成的Angular项目,默认会有2个环境配置文件

1
2
3
└──myProject/src/environments/
└──environment.ts
└──environment.prod.ts
  1. environment.ts: 针对开发环境使用的环境文件
  2. environment.prod.ts: 生产环境编译时,将替换原有的environment.ts,然后再打包。 (根目录下的angular.json定义了这个默认行为,有需要,可以进行修改)

AngularCLI刚刚生成2个文件后,如果打开比较2个文件的区别,可以看到开发环境使用的environment.ts文件中,有这么一句production: false。因为,针对生产环境,angular在编译时需要核心考虑效率等问题,而开发环境,要考虑方便开发者进行调试,侧重点不同。

那么针对生产环境如何编译呢?Angular CLI同样提供了命令,

1
ng build --prod

其中,参数--prod 即告诉编译环境,编译为生产环境包。同样,angular.json中定义了默认的编译参数,如果需要,可以进行修改。主要配置参数如下

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
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "5mb",
"maximumError": "10mb"
}
]
}
}

Angular默认打包到根目录下的dist目录下,生成的文件为纯静态文件(html, css, js),以及图片文件。

2.2. 打包部署到二级目录

有不少情况,我们的angular web站点不能直接部署到网站的根目录下,需要部署到二级目录下。 比如,不能部署到 http://abc.com下,要求部署到 http://abc.com/demo 这个二级目录下。针对这种情况,就需要修改一下我们的编译参数,修改为:

1
ng build --prod --deploy-url /demo/ --base-href /demo/

增加 --deploy-url 和 --base-href。

使用场景:比如我们有多个站点,希望使用同一个反向代理, http://site1, http://site2, 分别映射到 http://abc.com/site1, http://abc.com/site2/。 那么为了方便配置,需要把site1, site2都部署到二级目录,如http://site1/site1, http://site2/site2。 然后 http://site1/site1代理到http://abc.com/site1, http://site2/site2代理到http://abc.com/site2/, 免得css、js因为目录级别问题找不到。

3. Angular站点的发布

Angular站点编译打包后,可以方便的发布到已有web服务器,或者打成docker image, 然后发布。

3.1. web服务器发布

因为我们打包后,生成的文件为纯静态文件(html, css, js, 图片等), 所以打包后的问题,可以直接copy到iis, nginx , apache tomcat等web服务器,或者node.js, java等可以显示静态文件的程序的目录下即可。

3.2. 使用docker发布

如果部署到docker,我们可以基于一个基础的nginx docker, 然后把编译好的angular项目,copy到docker 内的nginx目录下即可。

基本步骤:

  1. 准备Dockerfile 文件, docker可以基于nginx:alpine, 将编译好的angular 站点文件复制到 docker 的nginx默认目录 /usr/share/nginx/html
1
2
FROM nginx:alpine
COPY . /usr/share/nginx/html

说明: 1) 假设angular打包后的文件,与Dockerfile文件在同一个目录
2) COPY . /usr/share/nginx/html, 两个参数 . 代表当前路径, /usr/share/nginx/html是docker中的目标目录

  1. 编译docker。 在Dockerfile目录下,执行
1
2
3
docker build -t your-docker-name .
docker save your-docker-name > your-docker-name.tar
gzip your-docker-name.tar

三条命令分别为:

  • 生成docker image, 名字(name)为your-docker-name
  • 导出docker image为本地文件, 文件名为 your-docker-name.tar
  • 压缩docker image

可以看到,因为angular编译后为纯静态文件,所以使用docker发布非常简单。部署时,只需要复制docker文件到目标机器,解压缩,然后执行 docker load < your-docker-name.tar 即可加载docker image到目标机器。

4. 总结

  1. 为生产环境编译,一定要加参数--prod
  2. 如果要部署到二级目录,编译时加参数。如部署到/demo二级目录下,加参数: --deploy-url /demo/ --base-href /demo/
  3. 使用docker发布,可以选择基本的nginx docker, 然后将编译好的angular文件copy到nginx目录下即可。



—————- END —————-






======================

环境:

  • Angular CLI: 11.0.6
  • Angular: 11.0.7
  • Node: 12.18.3
  • npm : 6.14.6
  • IDE: Visual Studio Code

1. 摘要

在我们的实际的业务开发过程中,我们经常会遇到如下需求:

  1. 需要限制某些 URL 的可访问性,例如,对于系统管理界面,只有那些拥有管理员权限的用户才能打开。
  2. 当用户处于编辑界面时,在没有保存就离开时,需要提示用户是否放弃修改。

针对以上场景,Angualr使用路由守卫(Route Guards)来实现。

2. 路由守卫(Route Guards)

2.1. 创建路由守卫

Angular CLI提供了命令行工具,可以快速创建路由守卫框架文件:ng generate guard auth。 执行后,Angular CLI会问我们需要实现哪些接口,我们直接勾选即可:

1
2
3
4
5
? Which interfaces would you like to implement? (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) CanActivate
( ) CanActivateChild
( ) CanDeactivate
( ) CanLoad

说明:

  1. CanActivate: 控制路由是否可以激活
  2. CanActivateChild: 控制子路由是否可以激活
  3. CanDeactivate: 控制路由是否可以退出
  4. CanLoad: 控制模块(module)是否可以被加载

比较经常使用的是1、3,分别控制进入和退出。 按照上面配置,AngularCLI自动生成如下代码,return true; 替换为我们实际的代码即可。return false; 表示不允许跳转,或者取消离开当前页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanDeactivate<unknown> {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
}
}

在canActivate方法中,我们还可以使用跳转。如页面判断是否已经登录,如果没有登录,跳转到Login页面:

1
2
this.router.navigate(['/login']);
return false;

2.2. 控制路由是否可以激活

控制路由是否可以激活,需要定义在定义路由的地方,增加canActivate属性。如果需要,还可以增加data属性, 比如告诉我们的AuthGuard进入当前路由需要验证哪些权限。data属性是可选的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const routes: Routes = [
{
path: "page1",
component: Page1Component,
data: { permissions: ['YourPage1Permission'] }, // 传入参数给AuthGuard,可选
canActivate: [AuthGuard]
},
{
path: "page2",
component: Page2omponent,
data: { permissions: ['YourPage2Permission'] }, // 传入参数给AuthGuard,可选
canActivate: [AuthGuard]
}
]

2.3. 控制路由是否退出(离开)

和控制路由是否可以激活类似,在路由定义出增加 canDeactivate,并制定相应的Guard守卫即可。这里不再举例

3. 总结

  1. 通过路由守卫(Route Guards)实现控制URL的进入和离开;
  2. Angular CLI可以辅助我们创建guard文件;



—————- END —————-






======================

环境:

  • Angular CLI: 11.0.6
  • Angular: 11.0.7
  • Node: 12.18.3
  • npm : 6.14.6
  • IDE: Visual Studio Code

1. 摘要

简单来说地址栏中,不同的地址(URL)对应不同的页面,这就是路由。同时,点击浏览器的前进和后退按钮,浏览器就会在你的浏览历史中向前或向后导航,这也是基于路由。

在 Angular 里面,Router 是一个独立的模块,定义在 @angular/router 模块中,

  1. Router 可以配合 NgModule 进行模块的延迟加载(懒加载)、预加载操作(参考《Angular入门到精通系列教程(11)- 模块(NgModule),延迟加载模块》);
  2. Router 会管理组件的生命周期,它会负责创建、销毁组件。

对于一个新的基于AngularCLI的项目,初始化时可以通过选项,将AppRoutingModule默认加入到app.component.ts中。

2. 路由(Router)基本用法

2.1. 准备

我们首先创建2个页面,用于说明路由的使用:

1
2
ng g c page1
ng g c page2

使用上面AnuglarCLI命令,创建Page1Component, Page2Component 2个组件。

2.2. 注册路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//src\app\app-routing.module.ts
const routes: Routes = [
{
path: 'page1',
component: Page1Component
},
{
path: 'page2',
component: Page2Component
},
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}

可以看到,简单的路由注册,只需要path和component2个属性,分别定义路由的相对路径,以及这个路由的响应组件。

2.3. html中的用法

1
2
<a routerLink="page1">Page1</a> |
<a routerLink="page2">Page2</a>

在html模板中,直接使用routerLink属性,标识为angular的路由。执行代码,可以看到 Page1和Page2 两个超链接,点击可以看到地址栏地址改为http://localhost:4200/page2或http://localhost:4200/page1, 页面内容在page1和page2中切换

2.4. ts 代码中的用法

有时候,需要根据ts中的业务逻辑,进行跳转。ts中,需要注入Router实例,如

1
constructor(private router: Router) {}

跳转代码:

1
2
3
4
5
// 跳转到 /page1
this.router.navigate(['/page1']);

// 跳转到 /page1/123
this.router.navigate(['/page1', 123]);

3. 接收参数

3.1. 路径中的参数

一般来说,我们把参数作为url中的一段,如/users/1, 代表查询id是1的用户,路由定义为”/users/id” 这种风格。

针对我们的简单页面,比如我们的page1页面可以传id参数,那么我们需要修改我们的routing为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const routes: Routes = [
{
path: 'page1/:id', //接收id参数
component: Page1Component,
},
{
// 实现可选参数的小技巧。 这个routing处理没有参数的url
path: 'page1',
redirectTo: 'page1/', // 跳转到'page1/:id'
},
{
path: 'page2',
component: Page2Component,
},
];

ts代码读取参数时, 首先需要注入ActivatedRoute,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
constructor(private activatedRoute: ActivatedRoute) {}

ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((params) => {
console.log('Parameter id: ', params.get('id'));

// 地址 http://localhost:4200/page1/33
// 控制台输出:Query Parameter name: 33

// 地址 http://localhost:4200/page1/
// 控制台输出:Query Parameter name: (实际结果为undefined)
});
}

3.2. 参数(QueryParameter)中的参数

参数还有另外一种写法,如http://localhost:4200/?name=cat, 即URL地址后,加一个问号’?’, 之后再加参数名和参数值(’name=cat’)。这种称为查询参数(QueryParameter)。

取这查询参数时,和之前的路由参数类似,只是paramMap改为queryParamMap,代码如下:

1
2
3
4
5
6
7
8
9
this.activatedRoute.queryParamMap.subscribe((params) => {
console.log('Query Parameter name: ', params.get('name'));

// 地址 http://localhost:4200/page1?name=cat
// 控制台输出:Query Parameter name: cat

// 地址 http://localhost:4200/page1/
// 控制台输出:Query Parameter name: (实际结果为undefined)
});

4. URL路径显示格式

不同于传统的纯静态(html)站点,angular中的url不是对应一个真实的文件(页面),因为anuglar接管的路由(Routing)处理,来决定显示那个Component给终端用户。为了针对不同的场景,angular的URL路径显示格式有2中:

  1. http://localhost:4200/page1/123
  2. http://localhost:4200/#/page1/123

默认是第一种,不加#的。如果需要,可以在app-routing.ts中,加入useHash: true, 如:

1
2
3
4
5
// app-routing.ts
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule],
})

5. 部署中遇到的问题

同样,因为anuglar接管的路由(Routing)处理,所以部署时,部署到iis, nginx等等的服务器,都会有不同的技巧(要求),详细参考:
https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode

6. 总结

  1. angular默认不支持可选路由(e.g. /user/:id?),但是我们可以定义2个路由,指向同一个Component来实现这个,达到代码复用;(或者使用redirectTo)
  2. 可以使用useHash参数,实现augular路径前加一个#;
  3. 读取参数时,都需要subscribe订阅一下,不能直接读取。
  4. 打包后部署问题,查看官方wifi (https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode)



—————- END —————-






======================

环境:

  • Angular CLI: 11.0.6
  • Angular: 11.0.7
  • Node: 12.18.3
  • npm : 6.14.6
  • IDE: Visual Studio Code

1. 摘要

指令(Directive)在Angular 1.0时代(当时叫AngularJS)是很流行的,现在用到的偏少。可以简单理解为,指令(Directive)用于扩展已有Element(DOM)。

2. 组件与指令之间的关系

如果去看Angular源码,可以看到下面定义

1
2
3
4
5
6
/**
* Supplies configuration metadata for an Angular component.
*
* @publicApi
*/
export declare interface Component extends Directive {

是的,Component派生于Directive,也就是说,Component属于Directive。

2.1. 指令的种类

  1. Component 是 Directive 的子接口,是一种特殊的指令,Component 可以带有 HTML 模板,Directive 不能有模板。
  2. 属性型指令:用来修改 DOM 元素的外观和行为,但是不会改变DOM 结构,Angular 内置指令里面典型的属性型指令有 ngClass、ngStyle,如果打算封装自己的组件库,属性型指令是必备的内容。
  3. 结构型指令:可以修改 DOM 结构,内置的常用结构型指令有 *ngFor、*ngIf 和 *ngSwitch。由于结构型指令会修改 DOM 结构,因而同一个 HTML 标签上面不能同时使用多个结构型指令。如果要在同一个 HTML 元素上面使用多个结构性指令,可以考虑加一层空的元素来嵌套,比如在外面套一层空的(div) 。

3. Angular 中指令的用途

Angualr中用指令来增强DOM的功能,包括 HTML 原生DOM和我们自己自定义的组件(Component)。举例来说,可以扩展一个Button,实现避免点击后,服务器端未响应前的二次点击;高亮某些收入内容等等。

4. 指令举例

4.1. 指令功能

实现一个指令,鼠标移动到上面时, 背景显示为黄色,鼠标移开,恢复正常。

4.2. Anuglar CLI生成基本文件

1
ng generate directive MyHighlight

Anuglar CLI自动生成html、css、ut等文件。

4.3. Directive指令核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Directive, ElementRef } from '@angular/core';

@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}

@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}

@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}

private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}

4.4. 使用该指令

1
2
<p my-highlight>Highlight me!</p>

my-highlight 即我们的元素扩展属性(指令、directive)。

5. 总结

  • 指令(Directive)用于扩展DOM 元素或组件的功能。
  • Angular中的 *ngFor、*ngIf 和 *ngSwitch 都是Angular内置的指令。



—————- END —————-






======================

0%