Angularjs
组件
组件由三部分组成:
- class:数据、功能(函数);
- template:HTML 模板
- style:样式
在 HTML 标签中,点击事件绑定用以下方法来表示:
1 |
|
HTML 模板中所需要的数据,通过使用依赖注入的方法,实现动态更新的效果,示例如下:
单向绑定
1 |
|
此处的“| uppercase” 是一个管道连接符 + 一个内置格式转换函数,它可以将字符串转成大写。在 angular 中,这种方式被称为 pipe 函数,包括 DatePipe, UppercasePipe, LowercasePipe, CurrencyPipe, DecimalPipe, PercentPipe 等;
双向绑定
1 |
|
此处通过 [(ngModel)] 实现双向绑定,这样就不需要在 JS 中监听 input 事件,并手工更新 JS 中的数据,确实方便很多;
服务
组件的数据可以依赖服务来注入,实现数据和组件之间的解耦。这样当数据的实现发生变化时,不需要变更组件,只需要更新服务内部的代码即可;数据来源可以是本地存储、硬编码、网络接口等;
通过将服务注入组件内部的 constructor 构建函数,之后在组件的 onInit hook 中调用初始化函数,来实现内部对数据的自定义处理
此处非常有意思,首先外部引入的 HeroService 本身就是一个类(或函数),已经实现了第一层的解耦,但之后在组件中引入该服务时,只是将其添加为组件 Class 的私有属性,同时组件又定义了自己的 Service 函数,在该 Service 函数中调用 HeroSerivice,这样一样就实现了双重解耦,如果 HeroService 的实现有任何变化,都只需要更新组件自己定义的 Service 函数,而不会影响到组件内部的其他位置的代码;
理论上任何读取外部数据的场景,都最好自定义一个函数,来实现解耦,避免内部代码跟外部数据的实现之间产生耦合;
创建服务的命令:ng generate service
@Injectable 修饰符用来定义服务的元属性,例如定义可注入的范围等(root 表示全局可注入),有点类似 @component 用来定义组件的元属性一样;
当服务的数据来源是网络接口时,数据的获得是异步的,因此服务需要支持该异步场景,对异步状态下的数据进行处理;
为了支持异常,ng 引入了一个 Observable 类,这个类有点像是 Promise,它会返回一个对象,该对象有一个 Subscribe 方法;该方法接受一个回调函数,并将最终数据做为参数,传递给回调函数;
constructor 构建函数的初始化很有意思,它有一个快捷方式,即在参数定义中,可以直接将参数赋值给相应名称的属性,这个做法跟 c++ 不太一样,示例如下:
constructor*(private messageService: MessageService) { }
路由
理论上路由貌似应该是一个全局的东西,当用户在浏览器中,或者在页面上点击某个链接时,根据链接中的 path,从路由表中查到对应的组件,然后展示该组件(有点类似后端的路由,只是后端的路由是调用某个视图函数,而前端变成了调用某个视图组件);
RouterModule 的方法
- forRoot,用来添加路由表;
- routerLink 属性用来在 HTML 页面上的 标签中指定路由路径;
定义路径时,可以用冒号来表示变量,例如 “/detail/:id”,此处 id 是一个变量;
当用户点击某个包含 routerLink 属性的链接时,路由器将接受到事件对象,并跳转到对应的组件,之后组件有可能需要接受参数数据来完成初始化,此时需要用到 ActivatedRoute 对象来获得所需的参数数据;
- 示例:ActivatedRoute.snapshot.paramMap.get(“id”)
但是有时候可能该参数很大,例如是一个对象,此时该如何传递数据呢?
当用户从 A 组件页面跳转到 B 组件页面后,如果需要返回原来的 A 组件页面,貌似有两种方法,一种是在 B 页面上面放置 A 页面的链接。这种方法需要多一些工作量;如果此时能够记住原来的页面栈,而用一个通用返回方法,回到页面栈的上一层就会非常的方便,此时需要用到一个内置的 Location 服务,来获得该页面栈;
添加路由的过程
- 创建:创建 routing 路由模块;通过 CLI 创建时,它会自动在 app 根模块中引入路由模块;
- 映射:路由模块中定义路径和组件的映射关系,以便当用户点击或在浏览器中输入路径时,可以展示相应的组件;记得定义一个默认路径,让其重定向到指定的路径;
- 展示:在需要展示组件的位置,添加内置的 router-outlet 组件,它会将路由返回的组件展示出来;一般此时还会在 HTML 页面上添加导航,以便在不同的版块之间切换;
- 链接:给需要进行跳转的组件添加 routerLink 属性,当用户点击时,实现跳转;
HTTP
通过引入内置的 HttpClient 模块,可以实现发送 HTTP 请求;由于请求返回的结果是异步的,因此 angular 使用 RxJS 库来处理异步,RxJS 将请求结果封装为 Observable 对象,有点类似 promise,可以调用该对象的 subscribe 方法,来获得异步返回的数据;
HttpClient 拥有常见的各种请求方法,例如 get/post/put/delete 等,它的调用方式有点特别,需要使用类似 C++ 中的泛型来定义返回数据的类型,例如:
HttpClient.get<Hero[]>(url),此处 url 为参数,不同的请求方法,接受的参数个数不同;所有的请求方法都会返回一个 RxJS 中的 Observable 对象;
RxJS 的 Observable 对象有一个 pipe 方法,该方法可用来将多个函数组合成一个函数。当条件满足时,该组合函数会被触发(调用),然后它会依次执行组合中的各个函数;例如:
1
2
3
4
5
return this.http.get<Hero>(url)
.pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);此处的 tap 和 catchError 都是内置函数,但是它们的执行场景不同,tap 一定会执行,而 catchError 仅在 HTTP Response 报错的情况下才会执行;这两个函数都接受一个回调函数做为其参数,并在得到 Response 后,调用该回调函数;
因此,在 HttpClient 请求中结合 pipe,我们就可以对返回的结果加入一些我们想要实现的额外操作,例如 log 和错误处理等;
RxJS 有很多内置方法(称为 operator),例如 map, tap, catchError 等,这些内置方法可以很方便的实现一些常用的功能,详细可查看官方文档:https://rxjs.dev/api
RxJS 库中还有另外一个特别的东西叫 Subject,本质上它是一个特定类型的 Observable 对象,因此它同样支持 Observable 的各种方法,它的作用原理很像是一个 EventEmitter,它有一个 next 方法,当调用该方法时,给它传递一个参数,然后就会触发之前通过 pipe 传递进去的那些函数,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 初始化一个 Subject 对象
searchTerm = new Subject<string>();
// 调用 Subject 的 pipe 方法,放入回调函数
heroes$ = searchTerm.pipe(
// 在用户输入关键字后,等待 300 毫秒,如果用户没有继续输入,再触发关键字搜索
debounceTime(300),
// 如果输入和之前的关键字相同,则不触发搜索
distinctUntilChanged(),
// 当关键字变化时,切换到新的搜索结果
switchMap((term: string) => this.heroService.searchHeroes(term)),
)
// 定义事件处理函数,当事件发生时,调用 Subject 对象的 next 方法,传递最新的参数数据
search(term: string): void {
this.searchTerm.next(term);
}由于 Observable 的 pipe 方法返回的是一个 Observable 对象,因此这里加了特殊的符号 heroes$ 来表示该类型,而不是直接用 heroes 的常见变量名,这也直接导致随后在 Component 的 HTML 模板中绑定该变量时,需要使用 angular 内置的 async 来处理,示例如下:
1
<li *ngFor="let hero of heroes$ | async"></li>
常用命令
创建
- 组件:ng generate component
- 服务:ng generate service
- 模块:ng generate module
–flat –module=app - –flat:表示不需要为该模块创建单独的文件夹,而是放在根文件夹下面,即 src/app 文件夹中;
- –module=app:表示将该模块注册登录在 AppModule 模块中(即在 AppModule 模块中 import);
启动调试服务器
ng serve –open