Angular的基本介绍

前言

Angular 是一个流行的开源前端框架,用于构建现代化的、单页面 Web 应用程序和动态 Web 页面。最开始由一个小的团队将其开发,后被Google收购,之后Google负责后期的开发和维护,并被广泛使用在许多大型的企业级应用程序中,我在这里的话分享一些经常使用的,所以肯定没有官方文档那样全面,如有我没分享到的,可以去官方文档查看,在正片开始前,先了解一点其他的内容

尤雨溪与Vue:

最开始的Angular是叫做AngularJs是用js来开发,后来被谷歌收购,由于其很复杂,很难理解,谷歌决定将其框架源码进行简化,成为2.x的版本,此时就不叫AngularJs了,而叫Angular,在简化期间,其开发团队的一位成员尤雨溪还是觉得2.x还是很复杂,想进一步简化,但是没有得到公司同意,于是自己将其简化,得到的产物就是最初始的Vue并将其开源免费,因此它也失去了在谷歌的工作,后来阿里巴巴向其发出邀请,希望他继续开发Vue,但他拒绝了,因为他想Vue是一个开源免费的,后其维护的工作一直由社区进行,直到现在....
注意:

本文根据代码所实现的效果可能跟使用的angular版本不一致和文件夹的层次不同而得到不同的效果

一、设计原则

说明: 在正式开始之前我们先来了解几个写代码时应该注意的常用原则,分别是YAGNIDRYOCPLow Coupling, High CohesionDimeter Law这五个,当你在开发的过程中都没有违背这些法则的话,你会发现后面自己的代码维护起来要容易的多

1.YAGNI

说明: YAGNI原则是敏捷软件开发中的一项原则,它强调只关注当前需求,而不去实现未来可能需要但尚未具体需求的功能。该原则的核心思想是避免过度设计和开发不必要的功能,以减少开发时间和复杂性。

理念: 在软件开发过程中,应该专注于解决当前存在的业务需求,而不去考虑将来可能出现的需求。因为未来的需求很难预测和确定,可能在实际使用中变化、增加或者完全被抛弃。因此,将精力放在当前确切的需求上,可以减少不必要的开发工作,并避免浪费时间和资源。

注意: YAGNI 原则并不是说不考虑未来的需求,而是在当前需求满足后再考虑未来的需求。当确实需要未来的功能时,可以通过重构、扩展和迭代的方式逐步引入,而不是过早地进行预先设计和开发。这样可以更好地应对变化、降低风险,并且按需添加功能,使软件更具灵活性和可维护性。

2.DRY

说明: 该原则认为,相同的代码片段不应该在系统中出现多次,而应该封装成可重用的模块或函数。

好处:

  1. 可维护性:当需要修改某个功能或修复Bug时,只需要在封装好的代码位置做一次修改,而不是在多个重复的地方都进行修改,从而减少了出错的机会和工作量。
  2. 可读性:通过封装重复代码,使代码更加简洁、清晰,避免了重复的冗余代码,提高了代码的可读性。
  3. 可扩展性:通过将通用的功能封装成可重用的模块,可以更方便地扩展应用程序,以满足未来的需求。

注意: DRY 原则并不是要求完全避免重复,因为某些情况下重复可能是合理的。例如,相同的逻辑在不同的上下文中可能有不同的实现方式。在这种情况下,DRY 原则的重点是避免无谓的重复,尽量找到合适的抽象和封装方式。

3.OCP

原则: 对于修改封闭,对于扩展开放

好处:

  1. 可维护性:通过遵循 OCP 原则,不修改现有代码,只扩展功能,减少了对现有代码的依赖和可能的破坏,增强了代码的稳定性和可维护性。
  2. 可扩展性:通过扩展接口和实现类,系统的功能可以方便地进行扩展和变化,而不会影响到其他部分的代码和功能。
  3. 可复用性:通过抽象接口的使用,可以提高代码的复用性,因为不同的实现可以在不同的上下文中重复使用。

注意: OCP 原则并不是说完全避免修改已有代码,而是尽量减少对已有代码的修改,并通过接口和抽象来隔离变化。这样能够使软件系统更加灵活、可扩展,并更好地应对变化需求。

4.Low Coupling, High Cohesion

目的: 提高系统的可维护性、可重用性和扩展性

说明: Low Coupling指的是模块之间的相互依赖性应尽可能低。模块之间的耦合度越低,它们之间的关系越独立,就越容易修改和替换其中的一个模块,而不会对其他模块产生影响。低耦合度可以减少系统中的依赖关系,增强模块的灵活性和可重用性。High Cohesion指的是模块内部的元素之间紧密相关,共同完成一个任务或功能。高内聚的模块具有清晰的职责,遵循单一责任原则,每个模块专注于自己的功能,并且不涉及与其他模块无关的任务。高内聚度可以提高模块的可读性、可维护性和重用性,因为模块内部的元素具有逻辑上的紧密连接。

注意: 低耦合和高内聚是相辅相成的。低耦合度和高内聚度使得软件系统的各个模块更加独立和集中,使得系统的扩展和修改更加容易,并且减少了变更对其他模块的影响,通过实践低耦合和高内聚原则,可以提高系统的稳定性、可维护性和可扩展性。这些原则在面向对象设计和模块化开发中都具有重要的指导意义,可以帮助开发人员构建高质量的软件系统。

5.Dimeter Law

核心: 一个模块(类、对象等)应当尽可能少地依赖其他模块。具体而言,一个对象应当仅与其直接的朋友交互,而不与朋友的朋友交互。

对象的方法限制:

  1. 对象本身的方法。

  2. 通过参数传递给方法的对象的方法。

  3. 作为该对象实例变量的直接属性的方法。
    好处:

  4. 降低耦合度:模块之间的耦合度低,修改一个模块不会对其他模块造成过多的影响。

  5. 提高可维护性:模块更加独立,易于理解和修改,减少代码的相互依赖。

  6. 增强可测试性:模块之间解耦,易于进行单元测试和集成测试。

二、项目的初始化

说明: Angular的项目初始化的工具是Angular CLI,使用前需要先安装,不过在安装之前需要先查看自己node的版本,需要LTS稳定的版本,因为使用LTS版本是为了确保稳定性、兼容性和安全性,以提供一个可靠的开发环境和更长期的支持,具体的下载方法可以参考官网也可以参考我下面的内容。

1.安装angular cli

js 复制代码
// 先检查自己node的版本,看与官网的LTS版本是否相同,推荐使用nvm
// 这个在我前面所分享的node一文中存在
node -v

// node安装完毕之后你就可以使用npm包管理工具了,推荐使用yarn,这个在
// node一文中也提到过,这里查看是否存在包管理工具npm
npm -v
js 复制代码
// 全局安装angular cli,方便自己下一次初始化项目的时候就不用再去安装了
npm install -g @angular/cli

2.初始化项目

说明: angular初始化项目的命令是ng new后面加上项目的名字来初始化一个项目,在初始化的过程中,Angular CLI 会安装必要的 Angular npm 包和其它依赖包,花费的时间可能会久一点,毕竟angular的实现也借助了其它的框架

js 复制代码
// 这里初始化一个demo的项目
ng new demo

vue cli初始化项目和angular cli初始化项目类似,都是以问答的方式产生的,在angular中,你可能会遇到以下几个问题

js 复制代码
// 问题一:就是你想你这个项目中与文件有关的内容能够与它们分享嘛
 Would you like to share pseudonymous usage data about this project
 with the Angular Team at Google under Google's Privacy Policy at      https://policies.google.com/privacy. For more
details and how to change this setting, see 
https://angular.io/analytics
js 复制代码
// 问题二:是否使用angular中的路由
 Would you like to add Angular routing?
js 复制代码
// 问题三:css的预编译工具使用哪一个
 Which stylesheet format would you like to use?

3.项目的运行

说明: 在包都安装完毕之后可以使用cd 项目名称这个命令进入到该项目文件夹,之后执行code .就会在编辑器中打开这个项目

js 复制代码
// -open会自动的打开浏览器,一般默认的端口号是 4200
ng serve --open

4.项目结构的介绍

说明: 这里介绍一下在项目初始化中常出现的文件,当然angular中的配置文件很多,具体可以点击此处去官方文档查看

js 复制代码
// 某个vscode创建的项目文件实例:
src/
  |- app/
  |   |- app.component.ts
  |   |- app.component.html
  |   |- app.component.css
  |   |- app.module.ts
  |- assets/
  |   |- logo.png
  |- environments/
  |   |- environment.ts
  |   |- environment.prod.ts
  |- index.html
  |- main.ts
styles.scss
tsconfig.json
angular.json
  • src/app/: 应用程序的根目录,包含了组件、模块和其他 Angular 相关的代码文件。

    • app.component.ts: 根组件的 TypeScript 文件。
    • app.component.html: 根组件的 HTML 模板。
    • app.component.css: 根组件的 CSS 样式文件。
    • app.module.ts: 应用程序的主模块,用来导入和配置其他模块和组件。
  • src/assets/: 存放静态资源文件的目录,比如图片、字体等。

    • logo.png: 一个示例图片文件。
  • src/environments/: 存放不同环境的配置文件的目录.

    • environment.ts: 开发环境的配置文件。
    • environment.prod.ts: 生产环境的配置文件。
  • src/index.html: 应用程序的主 HTML 文件,在这里加载应用程序的根组件。

  • src/main.ts: 应用程序的主入口文件,启动应用程序的代码。

  • styles.scss: 全局样式文件,可以定义全局的 CSS 样式。

  • tsconfig.json: TypeScript 配置文件,用于配置 TypeScript 编译器的选项。

  • angular.json: Angular 项目的配置文件,包含了构建、部署和其他项目配置选项。

关于样式,angularvue不同在于如果你angular中的样式是像我下面那样写的话,每个组件的样式都是自己独立的,不会与其它组件的样式混合在一起,而vue则会出现样式混合的问题,但可以通过在style上面加上scoped来解决这个问题,如果需要任何组件都可以使用的样式,那么就可以在styles.scss中去设置

5.项目启动的过程

说明:Angular项目是如何启动运行的,需要从angular.json配置文件来入手,在我看来,它和vuevue.config.js配置文件的作用是一致的,然后在angular.json文件中可以找到一个index配置项和main的配置项,它们分别是是HTML的入口以及JavaScript的入口,不过angular2.x以后,都是用TypeScript来编写的,以至于后缀均是.ts

json 复制代码
// angular.json
"options": {
    "index": "src/index.html",
    "main": "src/main.ts",
 },
html 复制代码
<!-- index配置项所指向的文件(默认) -->

<!doctype html>
<!-- en表示展示的内容英文,中文用zh替换 -->
<html lang="en"> 
<body>
  <!-- 这里会发现一个自定义的组件,Angular推荐大家: -->
  <!-- 自定义组件的时候加上自己的前缀,以此区分是组件还是标签 -->
  <!-- 其次看到 app- 前缀,均有angular提供 -->
  <app-root></app-root>
</body>
</html>
ts 复制代码
// main.ts

import { AppModule } from './app/app.module';

// bootstrapModule表示引导启动的意思,会启动一个AppModule
// 的模块
platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));
ts 复制代码
// app.module.ts

import { AppComponent } from './app.component';

@NgModule({
  // 从上面提示的文件看下来看到这里的bootstrap引导启动
  // 一个AppComponent的组件
  bootstrap: [AppComponent]
})
ts 复制代码
// app.component.ts

import { Component } from '@angular/core';

// 这里会发现很熟悉的东西
@Component({
  // 这个在vue中没见过,但是在JS选择器中看见过selector
  // 所以这个就表示组件所用的标签名,这也就与最开始的
  // 看到的那个组件对应起来了
  selector: 'app-root',
  
  // 看见template,也就联想到vue中的模板了,也就是
  // 存放html的地方,URL就是存放的地址
  templateUrl: './app.component.html',
  
  // 这个就是存放的样式,与上面同理
  styleUrls: ['./app.component.less']
})

// 那export就是写的JavaScript了
export class AppComponent {
  title = 'demo';
}

三、模块

说明: Angular的项目是由一个个模块组成的,但是这个模块并不是ES模块,初始状况下,一个Angular项目只存在一个主模块NgMoudle,这个模块有且仅有一个,它用于启动应用程序,模块就是一组相关功能的集合,专注于某个应用领域,它可以将组件和一组相关的应用关联起来,是应用组织代码的一种方式

这里需要注意的是:组件必须存放在模块里面,书写在Src文件夹之中,并且程序员所写的代码必须存放在app文件夹下面

1.NgMoudle

四、组件

说明: vue中的组件跟angular中的组件是一致的,都会表示一段可以重复使用的页面片段,在vue中,一个组件分为三部分:模板脚本样式,在angular中也是如此,只不过书写的方式会有所区别,但是不管怎样写,一定注意上面的注意事项

1.自定义组件(基础版)

说明: vueangular是类似的,一个组件需要主体部分,而vue是单文件组件,只需要一个.vue格式的文件就足够,但是在angular中规定,一个组件就是一个class,而angular是基于ts开发的,那么一个组件就是一个.ts文件,并且组件是需要供别人使用,从而组件的这一个.ts的文件就是一个ES的模块,若需要供它人使用需要将其导出,其次,单纯一个class,别人会不知道它是用来干嘛的,这时候需要借助装饰器来起到类似注视的作用。

创建组件class

js 复制代码
// src/app/myc01.ts

import { Component } from '@angular/core';

// 装饰器:
// 作用:可以理解为指定class是用来干什么的
// 属性:它的本质是一个函数,参数是一个配置对象,
//       这个配置对象可以指定模板、标签名等等
@Component({
  // 这个就理解为vue中的模板,但是这种书写方式很具有局限性,因此可以拆分
  template: '<h1><h1>',
  
  // 就把.vue文件中的template部分拆分出去,但是组件的模板是唯一的
  templeUrl: '这里写模板存放的地址',
  
  // 这个理解为组件的标签名,这里需要注意selecttor后面的写法
  // (因为在angular中一切皆组件,所以要注意组件表达的内容是什么)
  // myc01:这样写就表示标签名
  // .myc01:这样写就表示一个class
  // [myc01]:这样写就表示一个属性
  selector: 'myc01',
  
  // 样式也是一样的,不过样式会存在很多,并不是唯一的,
  // 所以展示的话使用数组
  styles: [],
  
  // 这个是存放样式文件的地址的,一样可以写多条文件
  styleUrls: []
})

// 这里就是写的js了,因为样式可写可不写,所以这里基本
// 上一个简单的组件就完成了,推荐写类名的时候首字母大写
// 并且类名推荐有意义,这样方便你在模块中很清楚的看清
// 存在那些组件
export class Myc01 {}

如果将一个组件的三部分tstemplatestyle拆分出去的话,由于你写的文件都存放在app目录下面,那么项目一旦大起来,文件多起来,如果文件命名不是很合理,你可能找一个组件会找半天,甚至找不到都有可能,所以在写文件名的时候推荐带上它是什么部分,举个例子,上面我写了一个myc01.ts的文件,别人看的话会不知道它是组件还是模块,但是我知道它是组件,所以我可以将其写成myc01.component.ts,这样我在后面看的时候就知道这个文件是组件还是模块了,其它的以此类推
明白了上面,那既然是一个大型项目,组件肯定会有很多,如果一个组件拆分成三个文件放在app文件夹下,那么100个组件就有300个文件了,到时候你肯定人都麻了,所以每一个组件的三部分可以放在一个文件夹里面,文件夹名推荐为组件的名字,这些操作都是为了你后期能够更好的去维护自己的代码

在模块中注册组件

说明: 由于是刚初始化的一个项目文件,当然模块就只有主模块,也就是app.module.ts这个文件

js 复制代码
// src/app/app.module.ts

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

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

// 引入文件
import { Myc01 } from './myc01';

@NgModule({
  // declarations表示官宣,宣布,发布的意思,也就是在这里来
  // 进行组件的注册,注意,组件注册完毕之后就会变成全局组件
  // 了,在哪里都可以去使用
  declarations: [
    AppComponent,
    Myc01 // 进行注册,注册的名字就是引入的类名
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

组件的使用

说明:vue中,自己定义的组件是在项目创建时创建的一个APP组件中使用的,在angular中也不例外,在项目初始化的时候会生成一个app-root的组件,所以就在这里使用。

js 复制代码
// src/app/app.component.html

// 标签名就是在selector中的内容
<myc01></myc01>

注意:在angular中,禁 止在入口文件src/index.html文件中去使用自定义组件,别问为什么,因为你在vue中也不允许这么干

使用命令创建组件

说明: 看到这里是不是觉得angular创建组件很麻烦,需要建文件夹,建文件然后注册,其实是挺麻烦的,但是官方想到了这一点,它给了我们一个命令ng generate component 组件名来快速创建组件,其可以简写成ng g component 组件名ng命令来自哪里呢,在你安装好angular cli的时候,ng.cmd命令就已经存在了,如果你没有这个命令也没有关系,在你初始化好的项目中,会有一个node_modules文件夹,这个文件夹中有一个.bin的可执行命令的文件夹,里面存在一个ng的命令,其实在安装好的node文件夹里面,你会看到一个npm.cmd的命令,它是用于执行第三方模块的工具的,你还会找到一个npx.cmd的命令,这个适用于执行node_modules中.bin文件夹下面的命令的,所以你可以使用npx ng g component 组件名这个命令来创建组件

js 复制代码
// 安装好angular cli:

ng generate component 组件名
ng g component 组件名
js 复制代码
// 没有安装或安装失败angular cli:

npx ng generate component 组件名
npx ng g component 组件名

在安装成功之后,它会创建一个以组件名为名字的文件夹,在文件夹中会分别创建.ts.html.css的文件,并会更新主模块中的内容,将创建的组件注册进去

警告

说明: 上面说到过每一个组件就是一个函数,那么如果在组件自己的模板中使用组件自己,其等价于在函数中调用函数本身,这样搞会形成递归调用,最后浏览器就挂掉了

五、指令

说明: 由于angular中的指令非常多,这里就分享几个最常用的,也就是在vue中常用的那几个

angular指令的分类:

1.结构型指令(*开头):会影响DOM树的结构

2.属性型指令(用[]):只会影响元素的外观或者行为

3.组件指令:组件是一种特殊的指令

1.HTML数据绑定

说明: vue中的双括号语法{{ }}就是借鉴于angular中的ng表达式,也叫做大胡子语法,都是用{{ }}来表示,使用方法都差不多,下面列举一些常用常用的写法,后面如果我学到有特殊的写法我会列举出来

js 复制代码
// src/app/myc02/myc02.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc02',
  templateUrl: './myc02.component.html',
  styleUrls: ['./myc02.component.less'],
})
export class Myc02Component {
  name = 'zhangsan';
  age = 18;
}
html 复制代码
<!-- src/app/myc02/myc02.component.html -->

<!-- zhangsan -->
<div>name属性的值是: {{ name }}</div>
<!-- 18 -->
<div>age属性的值是: {{ age }}</div>
<!-- 20 -->
<div>能够使用表达式吗?: {{ age + 2 }}</div>
<!-- true -->
<div>能够使用条件判断吗?: {{ age >= 18 }}</div>
<!-- 成年 -->
<div>能够使用三木运算符吗?: {{ age >= 18 ? '成年了' : '未成年' }}</div>
<!-- true -->
<div>能够使用连接符吗?: {{ age >= 18 && age <= 20 }}</div>
<!-- 8 -->
<div>能够使用字符串的属性吗?: {{ name.length }}</div>
<!-- zhangsan -->
<div>能够使用字符串的方法吗?: {{ name.toString() }}</div>

<!-- 报错 -->
<div>能够使用new关键字吗? : {{ new Object() }}</div> 
<!-- 报错 -->
<div>能够使用JSON的方法吗? : {{ JSON.stringify({}) }}</div> 

不适用{{}}的情况:

1.new关键字,因为new关键字在angular中是禁止使用的

2.JSON的方法,因为JSON在插值语法中是undefined

2.属性绑定

说明:angular中,属性绑定有两种写法,一种是直接在属性值上面直接使用插值语法{{ }},一种是使用[]将属性值包裹起来,这个就相当于vue中的v-bind

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 绑定的属性是变量的写法: -->
<!-- 在鼠标移动上去之后都会显示出:这是我写的一段文字 -->
<p title ="{{ name }}">你觉得这个是不是我写的</p>
<p [title] ="name">这个呢</p>
html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 绑定的属性存在常量的写法:需要用到字符串的拼接,注意单双引号交替 -->
<p [title] ="'你是谁' + Name">这个呢</p>
html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 然后我试了一下能不能在这个地方能不能使用字符串模板,答案是不行 -->
<p [title] ="`你是谁${Name}`">这个呢</p>

注意一下下面这种情况:

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 在使用图片的时候,由于图片都会存放在一个文件夹下,那么引入图片的 -->
<!-- 路径开头都是一致的,比如说:'../assets/' + '图片名称'这种格式的 -->
<!-- 那么在使用的时候就可以只将图片名称定义为变量,因为后台可能也不知 -->
<!-- 道你图片会放在什么位置,那能够确定的就只有图片的名称,所以这样写 -->
<!-- 方便了你,也方便了别人,就像下面这样: -->
<img [src] ="'../assets/' + 'imgUrl'">图片路径的拼接</img>
ts 复制代码
// src/app/myc03/myc03.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc03',
  templateUrl: './myc03.component.html',
  styleUrls: ['./myc03.component.less']
})
export class Myc03Component {
  name = '这是我写的一段文字'
  Name = '我是一个字符串'
  imgUrl = '1.jpg'
}

3.事件绑定

说明: angular中事件的绑定并不会像vue那样使用@事件名,而是使用(事件名)来代替,其次,事件名不需要像vue那样加上on,直接像(click)就好,这个就相当于vue中的v-on

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<button (click)="onSave()">Save</button>
ts 复制代码
// src/app/myc03/myc03.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc03',
  templateUrl: './myc03.component.html',
  styleUrls: ['./myc03.component.less']
})
export class Myc03Component {
  onSave() {
    console.log('我写的angular的第一个事件')
  }
}

这里有一点需要注意的是,angular中绑定的事件函数后面是需要加上括号的,不然会检测不到这个事件导致其失效

4.循环

说明: angular中循环的指令是*ngFor,后面跟一个循环的式子,与vuev-for相比,我觉得还是稍微复杂点

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<ul>
  <li *ngFor="let item of data">{{ item }}</li>
  <li *ngFor="let item of data; index as i">{{ i }}</li>
  <li *ngFor="let item of data; let i = index">{{ i }}</li>
</ul>

注意:

1.上面式子中只要你需要声明变量,你就必须使用let关键字

2.*ngFor这个指令循环只能使用of,不可以使用in

3.如果需要取索引它是不允许你直接使用index,需要你用变量重写

4.重写的方式的话推荐上面的两种写法

ts 复制代码
// src/app/myc03/myc03.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc03',
  templateUrl: './myc03.component.html',
  styleUrls: ['./myc03.component.less']
})
export class Myc03Component {
  data = ['数据一', '数据二', '数据三'];
}

5.条件

说明: 这里介绍的指令是*ngIf,它的后面可以是一个处理函数,可以是一个表达式,可以是一个值等等,但是前提是能够得到布尔值,最后就是vuev-if跟这个指令差不多,也就是显示隐藏的实质是是否将元素从dom中删除

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 这样在页面上你就看不见任何元素了,在dom树中你也看不见 -->
<ul *ngIf="true">这里展示与v-if相类似的功能</ul>

注意这两种写法:
*ngIf会销毁和创建元素,[ngIf]只是添加或移除元素的属性

说明: 既然vue中存在v-ifv-else,那么angular也会存在自己的v-else

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<ul *ngIf="false; else elseBlock">这里展示与v-if相类似的功能</ul>
<ng-template #elseBlock>
  <p>这里展示与v-else相类似的功能</p>
</ng-template>

注意:

1.else使用的格式是*ngIf="布尔值表达式; else else展示的内容"

2.else展示的内容是通过 # 来进行关联的

3.else展示的内容和if中间是不允许有其他代码的

4.else展示的部分必须使用ng-template,这个标签是一个容器,这个容器不会在dom结构中生成,但是它的内容可以

6.样式

说明:angular中控制样式有两种方式,一种是控制少量样式使用[ngStyle]="样式对象"的方法,这种方法虽然可行但是不太稳妥,一般推荐另外一种方法就是[ngClass]="class配置对象"

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 样式绑定的是使用ngStyle -->
<p [ngStyle]="styleControl">这里使用style来控制样式</p>

<!-- 方法一定要加(),否则方法不会执行 -->
<button (click)="toggle()">更改样式</button>
ts 复制代码
// src/app/myc03/myc03.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc02',
  templateUrl: './myc02.component.html',
  styleUrls: ['./myc02.component.less'],
})
export class Myc02Component {
  // 一般要控制样式的话需要将样式写在这里,因为如果你讲css写在html里面,
  // 是获取不到的,只有写在这里才能够去写方法去更改
  styleControl = {
    color: 'red',
    // 一般像这种中间存在-的css样式需要用''将其包裹起来
    'border-color': 'red',
    // 这种驼峰命名的css是不被angular所识别的,需要将其改写成上面的-链接的形式
    backgroundColor: 'red',
  };

  toggle() {
    // 更改上面写的样式,达到使用style控制样式的目的
    this.styleControl.color = 'blue';
  }
}

上面这种使用style进行样式控制会存在一个问题,就是我在JavaScript中去书写了很多的css样式代码,这样使样式的可复用性比较差,同时也违背了高内聚低耦合的理论,所以一般推荐使用class来控制样式

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 样式绑定的是使用ngClass -->
<p [ngClass]="classControl">这里使用class来控制样式</p>

<!-- 方法一定要加(),否则方法不会执行 -->
<button (click)="toggle()">更改样式</button>
less 复制代码
// src/app/myc03/myc03.component.less

.tColor {
  background-color: red;
}
ts 复制代码
// src/app/myc03/myc03.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc02',
  templateUrl: './myc02.component.html',
  styleUrls: ['./myc02.component.less'],
})
export class Myc02Component {
  // 在class的这个配置对象里面,每一对键值对的键代表类名,其值是一个布尔值、
  // 一个表达式返回布尔值,一个函数执行返回布尔值等等都可以,前提是能够得到
  // 布尔值,如果是true,则该样式生效,否则就失效,那么用这种方法你就算书写
  // 大量样式而我只需要控制类名就好
  classControl = {
    tColor: this.fn(),
  };

  toggle() {
    // 更改上面写的样式,达到使用class控制样式的目的
    this.classControl.tColor = false;
  }

  // 函数执行返回一个布尔值true
  fn() {
    return true;
  }
}

7.双向绑定

前提: angular中的指令实质上是一个个的class,它们按照功能会分别封装在不同的模块里面,想要使用必须导入才行,导入的话在主模块app.moudle.ts@NgMoudle里面的imports中导入,就像上面介绍的6个指令,它们都是存放在CommonModule这个模块之中,如果没有导入模块你就不能够使用,其次,双向绑定使用的是([ngModel]),这个指令存放在FormsModule模块中,这个模块存放在@angular/forms里面

js 复制代码
// src/app/app.moudle.ts

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

// 有这个模块才能够使用双向绑定
import { FormsModule } from "@angular/forms";

@NgModule({
  imports: [
    // 这是一个浏览器的模块,只要你的angular项目是用于这个范围的,
    // 这个模块就必须存在,其次这个模块默认会导入CommonModule,
    // 这也是为什么你可以使用ngif、ngfor这些命令的
    BrowserModule,
    FormsModule
  ],
})
export class AppModule {}
html 复制代码
// src/app/myc03/myc03.component.html

<input type="text" [(ngModel)]="pname">
<p>pname的值是: {{ pname }}</p>
ts 复制代码
// src/app/myc03/myc03.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc03',
  templateUrl: './myc03.component.html',
  styleUrls: ['./myc03.component.less'],
})
export class Myc03Component {
  pname = 'zhangsan';
}

简单实现:vue中,v-model的原理默认情况下可以理解为一个value的属性和一个input的事件,既然vue可以,那么angular也可以使用这种方法将其实现出来,模型层到视图层可以使用[value]代替,视图层到模型层可以用(input)来解决,这也说明了为什么用[(ngModel)]来表示双向数据绑定了

html 复制代码
// src/app/myc03/myc03.component.html

<input type="text" [value]="pname" (input)="changeValue($event)">
<p>pname的值是: {{ pname }}</p>
ts 复制代码
// src/app/myc03/myc03.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc03',
  templateUrl: './myc03.component.html',
  styleUrls: ['./myc03.component.less'],
})
export class Myc03Component {
  pname = '1';

  changeValue(event: any) {
    this.pname = event.target.value;
  }
}

关于$event和event:可以理解为作用一样但长相有区别

1.$event是Angular模板中用于访问事件对象的特殊变量

2.event是JavaScript中的普通事件对象,可以在组件类中直接使用。

8.自定义指令

命令:

ts 复制代码
// 完整写法
ng g directive 指令名称

// 简写形式
ng g d 指令名称

默认生成的内容及使用:

html 复制代码
<!-- src/app/myc03/myc03.component.html -->

<!-- 指令是作为元素的属性来使用的 -->
<input type="text" appFirstDirective>
ts 复制代码
// src/app/first-directive.directive.ts

import { Directive } from '@angular/core';

// 命名的是指令,所以这里不用component
@Directive({
  // 前面的指令在使用的时候可以看出,指令是作为元素的属性来使用的,
  // 所以在命名的时候需要使用到[],其次指令默认属性只有selector,
  // 跟组件相比,它没有样式style和模板template,根据对象间的父子
  // 判定,指令是父,组件是子,因此组件是一种特殊的指令
  selector: '[appFirstDirective]'
})
export class FirstDirectiveDirective {
  // 每一次调用指令都会执行一次这个constructor这个构造函数,函数的参数
  // 可以获取到是那个元素使用了这个指令,获得的结果是一个配置对象
  constructor(el: ElementRef) { 
      // 这里就可以拿到上面input的信息了
      console.log(el)
  }
}
ts 复制代码
// src/app/app.model.ts

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

import { FirstDirectiveDirective } from './first-directive.directive';

@NgModule({
  // 既然组件是默认的指令,那么指令的注册方式肯定也是一致的
  declarations: [FirstDirectiveDirective],
  imports: [
    BrowserModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

六、管道

说明: 在最开始是叫过滤器的,只不过到angular2.x的时候就改名叫做管道(Pipe)了,vue中使用的过滤器filter跟这个是一模一样的,至于怎么理解管道呢,可以理解为它是一种将多个处理函数组织起来的方式,可以有序地执行多个任务,而普通的处理函数更加独立,只负责完成单一的功能。

1.自定义管道

说明: 一个管道可以理解它是一个class,所以在使用的时候需要创建一个.ts的文件,对于文件的命名,由于定义的组件文件的名字是组件名称.component.ts这种格式,以此类推,定义的管道文件的名字可以写成管道名称.pipe.ts这种格式

定义

ts 复制代码
// src/demo.pipe.ts

import { Pipe } from "@angular/core";

// 定义的是管道的话装饰器需要使用Pipe这个,其次导入的地方是从@angular/core
// 这里导入的,而不是从第三方库(例如rxjs)导入的,Pipe这个装饰器函数的参数
// 有一个,就是定义管道的名字,这个名字就是使用在管道符(|)后面的名字
@Pipe({
  name: 'fitterDemo'
})
export class DemoPipe {
  // 管道中,执行过滤任务的是一个固定的函数,这个函数就是transform,可以把
  // 这个函数当做成一个主函数,通过这个函数作扩展的过滤行为,其次,函数的
  // 第一个参数是默认参数,这个参数是不需要传递的,它在进行过滤的时候就会拿到,
  // 值是管道符(|)左边需要进行过滤的值,然后,函数的返回值就是经过过滤后的值,
  // 最后,这个函数的参数可以存在多个,不过在参数对应的时候需要注意一点
  transform(val: any, flag = true) {
    console.log(val, flag)
  }
}

注意:

根据 Angular 的管道规范,transform 方法应该在所有可能的代码路径上都返回一个有效的值,而不仅仅是在某些分支或条件下返回值,简单来讲就是transform方法的返回值应该是满足所有条件,而不仅仅只是部分条件

ts 复制代码
// 这样写transform函数就会有问题,当flag传递的是false的时候管道的返回值
// 是undefined,这可能会导致潜在的 bug 或意外的行为
transform(val: any, flag = true) {
    if(flag) {
        console.log(val, flag)
    }
}

注册

js 复制代码
// src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { DemoPipe } from 'src/demo.pipe';

import { Myco1Component } from './myco1/myco1.component';

@NgModule({
  // 管道在使用之前也是需要注册的,注册方式跟组件,指令是一致的,导入 + 注册
  declarations: [
    DemoPipe,
    Myco1Component
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  providers: [],
  bootstrap: []
})
export class AppModule { }

使用

说明: 在使用的时候需要使用到管道符|,一般看到的格式为{{ 变量 | 管道 }},这样写之后这个变量就会自动的去执行定义的管道中的transform函数,将函数执行后所得到的值会覆盖变量原有的值

html 复制代码
<!-- src/app/app.component.html -->

<app-myco1></app-myco1>
html 复制代码
<!-- src/app/myc01/myc01.component.html -->

<input type="text" [(ngModel)]="text">

<!-- 这里传递的false其实对应的是transform函数的第二个参数flag, -->
<!-- 而不是对应第一个参数val -->
<span>{{ text | fitterDemo : false }}</span>
ts 复制代码
// src/app/myc01/myc01.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  text = 1
}

这里值得注意的在于管道传递的参数,管道传递参数的方式是使用:传递的参数,存在多个就继续:传递的参数的方式进行传值就好了,其次,由于transform函数的第一个参数是默认就有的,不需要传,所以这里传递的参数的数量是从transform函数的第一个参数开始算起的

创建命令

ts 复制代码
// 完整命令:
ng g pipe 管道名称

// 简写命令:
ng g p 管道名称

在初始化好的管道文件中需要注意的地方:

ts 复制代码
// src/app/myc04.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'myco4'
})

// 关于 implements PipeTransform 这类似的东西:
// 如果是自己手动写指令、组件、管道的文件,都不会存在这个,因为
// 这个不是必须的,它的作用是进行类似检查的功能,就比如说如果这里
// 下面的transform 你写成了 transfrom 的话,这里是会报错的,
export class Myco4Pipe implements PipeTransform {
  transform(value: unknown, ...args: unknown[]): unknown {
    return null;
  }
}

2.预定义管道

基本使用

说明: 这个类似于Lodash这个JavaScript库一样,它将一些你经常会进行的操作封装成管道,让你直接使用就可以,其次,这些管道是存放在CommonModule模块里面,也就是你不需要引入就可以使用了,至于为什么我在双向绑定那里提到过,最后这里用UpperCasePipe这个管道举个例子,在文档中你看到的预定义管道后面都会加上一个pipe的描述,在使用的时候这个是需要去掉的,然后呢,如果需要查看更多跟预定义管道的内容呢,戳此处去文档查看

html 复制代码
<!-- src/app/myc01/myc01.component.html -->

<!-- 通过这个管道的操作后小写的english就会变成大写的ENGLISH了 -->
{{ value_expression | uppercase }}
ts 复制代码
// src/app/myc01/myc01.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  value_expression = 'english'
}

七、服务与依赖注入

服务是不需要在app.module.ts中进行注册的

1.自定义服务

场景导入

说明: 以购物网站为例,与删除商品数据有关的操作是很敏感的,一般的人是没有这个权限的,如果删除了数据的话,这个操作会被记录下来,在开发的过程中,记录这样的信息称为日志,这里简单模拟一下这个行为,比如我添加或者删除数据的时候,记录其操作的相关信息

简单实现

html 复制代码
<!-- src/app/myc05/myc05.component.html -->

<h2>商品管理</h2>

<button (click)="doAdd()">添加商品</button>
<button (click)="doDelete()">删除商品</button>
ts 复制代码
// src/app/myc05/myc05.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc05',
  templateUrl: './myc05.component.html',
  styleUrls: ['./myc05.component.less'],
})
export class Myc05Component {
  doAdd() {
    console.log('正在执行数据库的添加');
    let uname = 'admin2';
    let time = new Date().getTime();
    let action = '添加了新的商品:xxxx';
    console.log(`管理员:${uname} 时间:${time} 动作:${action}`);
  }

  doDelete() {
    console.log('正在执行数据库的删除');
    let uname = 'admin3';
    let time = new Date().getTime();
    let action = '删除了新的商品:xxxx';
    console.log(`管理员:${uname} 时间:${time} 动作:${action}`);
  }
}

存在优化

问题: 上面这样写虽然可以实现功能,但是你会发现如果我有很多的地方需要去做日志的话,那么写出来的代码会存在很多相同的内容,不够简洁,并且在后期如果日志需要做出相同的更改,那么需要更改很多地方,这也说明代码的可维护性很差

解决办法:

方案一: 自己把重复的部分提取起来,封装成方法,然后在需要使用的地方进行导入,使用new关键字进行注册,最后调用方法实现功能。

提供方案一

说明: 将日志的公共部分提取出来,并将其封装成一个方法用于调用,这样可以解决前面代码不宜维护的缺点

ts 复制代码
// src/app/myc06.service.ts

export class Myc06Service {
  // 提供一个做日志的方法,参数为做日志的行为
  doLog(action: any) {
    let uname = 'admin2';
    let time = new Date().getTime();
    console.log(`管理员:${uname} 时间:${time} 动作:${action}`);
  }
}
html 复制代码
<!-- src/app/myc05/myc05.component.html -->

<h2>商品管理</h2>

<button (click)="doAdd()">添加商品</button>
<button (click)="doDelete()">删除商品</button>
ts 复制代码
// src/app/myc05/myc05.component.ts

import { Component } from '@angular/core';
import { Myc06Service } from '../myc06.service';

@Component({
  selector: 'app-myc05',
  templateUrl: './myc05.component.html',
  styleUrls: ['./myc05.component.less'],
})
export class Myc05Component {
  doAdd() {
    console.log('正在执行数据库的添加');
    let action = '添加了新的商品:xxxx';
    new Myc06Service().doLog(action);
  }

  doDelete() {
    console.log('正在执行数据库的删除');
    let action = '删除了新的商品:xxxx';
    new Myc06Service().doLog(action);
  }
}

缺点: 很麻烦,每次都需要自己手动去new一个对象,如果日志做多了一样有点吃不消,这里推荐第二种解决方法
方案二: 省略使用new关键字的操作,直接声明依赖,然后由模块提供相应的内容来达到你的目的,在给出方案二之前,需要先了解一个概念,就是服务

引出服务

概念: angular认为:组件是与用户进行交互的一种对象,其中的内容都必须和用户有关,而与用户没有关系的内容和操作都应该剥离出去,放在服务对象里面

关系:

graph TD 服务提供者 -- 依赖注入 --> 组件 组件 -- 声明依赖 --> 服务 服务提供者 -- 创建 --> 服务

理解: 首先,在开发过程中,组件可能会产生实现类似日志功能的需求,这种需求会被理解为声明依赖,然后这个功能会存放在服务之中,然后既然有人存在需求,那就需要有人来解决需求,它就是服务提供者,它会收到你的服务需求,将其创建出来,之后将它传递给需要服务的组件,这个过程称为依赖注入

提供方案二

下面这种写法是angular6以后在根模块注册服务的方法
注意: 这种根模块的服务对象只会在应用程序启动时创建,也就是整个应用程序共享同一个服务对象实例,如果需要使用这类服务对象,条件是创建的服务中不存在变化的属性,因为如果多个组件共用这一个实例,使用时会出现数据的问题

ts 复制代码
// src/app/myc06.service.ts

import { Injectable } from '@angular/core';

// 1.服务的注册不是使用的servers,而是Injectable,
// 表示所有的服务对象是'可被注入的'
@Injectable({
  // 2.providedIn表示这个服务是由哪一个模块提供的,
  // 默认情况下由主模块AppModule提供,也就是后面的值传root,
  // 这样的服务有点全局的意思,在哪都可以使用
  providedIn: 'root',
})
export class Myc06Service {
  doLog(action: any) {
    let uname = 'admin2';
    let time = new Date().getTime();
    console.log(`管理员:${uname} 时间:${time} 动作:${action}`);
  }
}
html 复制代码
<!-- src/app/myc05/myc05.component.html -->

<h2>商品管理</h2>

<button (click)="doAdd()">添加商品</button>
<button (click)="doDelete()">删除商品</button>
ts 复制代码
// src/app/myc05/myc05.component.ts

import { Component } from '@angular/core';
import { Myc06Service } from '../myc06.service';

@Component({
  selector: 'app-myc05',
  templateUrl: './myc05.component.html',
  styleUrls: ['./myc05.component.less'],
})
export class Myc05Component {
  log = null;
  // 3.声明依赖的话需要在constructor构造函数的参数中进行声明,
  // 这个变量名称没有规定,但是它的类型必须是你创建的服务,
  // 是这个依赖需要的那个服务,比如这里我依赖所需要的服务
  // 是Myc06Service,所以我定义变量的名称的类型也必须是
  // 这个Myc06Service,不然会完不成你想要的任务
  constructor(servers: Myc06Service) {
    // 4.在组件中声明依赖,服务会被服务提供者自动注入
    // 进来,组件直接使用服务对象就可以,这里打印servers
    // 是有值的,使用变量log存储一下是为了方便这个对象
    // 可以在这个类的任意地方都可以使用
    this.log = servers;
  }

  doAdd() {
    console.log('正在执行数据库的添加');
    let action = '添加了新的商品:xxxx';
    // 5.使用服务
    this.log.doLog(action);
  }

  doDelete() {
    console.log('正在执行数据库的删除');
    let action = '删除了新的商品:xxxx';
    this.log.doLog(action);
  }
}

下面这是angular6前注册服务的写法,这种和上面那种注册的方式的效果是一致的,

ts 复制代码
// src/app/app.module.ts

import { Myc06Service } from './myc06.service';

@NgModule({
  declarations: [],
  imports: [],
  providers: [ Myc06Service ],
  bootstrap: [],
})
export class AppModule {}

提供方案三

当然,如果需要创建组件自己的服务对象,可以以下面这个例子为例,组件中提供的服务对象是在每个组件实例化时创建的,每个组件都有自己的服务对象实例,这样即使服务中存在数据变量这些内容,它也不会影响组件内部的数据

ts 复制代码
// src/app/myc06.service.ts

import { Injectable } from '@angular/core';

// 1.这里的装饰器不需要传递参数对象
@Injectable()
export class Myc06Service {
  doLog(action: any) {
    let uname = 'admin2';
    let time = new Date().getTime();
    console.log(`管理员:${uname} 时间:${time} 动作:${action}`);
  }
}
html 复制代码
<!-- src/app/myc05/myc05.component.html -->

<h2>商品管理</h2>

<button (click)="doAdd()">添加商品</button>
<button (click)="doDelete()">删除商品</button>
ts 复制代码
// src/app/myc05/myc05.component.ts

import { Component } from '@angular/core';
import { Myc06Service } from '../myc06.service';

// 2.在组件的装饰器哪里传递一个参数对象,里面的 providers 
// 就是为当前组件提供服务的地方,可以理解为这个服务只能够在
// 当前组件内部才能够使用,有点局部的味道
@Component({
  selector: 'app-myc05',
  templateUrl: './myc05.component.html',
  styleUrls: ['./myc05.component.less'],
  providers: [ Myc06Service ],
})
export class Myc05Component {
  constructor(servers: Myc06Service) {
    this.log = servers;
  }

  doAdd() {
    console.log('正在执行数据库的添加');
    let action = '添加了新的商品:xxxx';
    this.log.doLog(action);
  }

  doDelete() {
    console.log('正在执行数据库的删除');
    let action = '删除了新的商品:xxxx';
    this.log.doLog(action);
  }
}

方案二和方案三的区别在于创建的服务对象里面是否存在属性,由于方案二可以理解为全局服务对象,方案三理解为局部服务对象,那么服务中存在属性的话,全局的每次都会初始化这个属性或者是更改这个属性,这样数据就会遭到污染,那么方案二也就不再适用了,该用方案三的局部来代替。

创建命令

命令:

ts 复制代码
// 完整写法
ng g service 服务名称

// 简写形式
ng g s 服务名称

2.HttpClient

说明: 这是一个angular内置的服务,从名字就可以看出它是与请求有关的,在使用的时候需要注意它需要在app.module.ts里面将模块HttpClientModule@angular/common/http导入并注册该模块,一般情况下 ,由于这个服务是angular中内置的,因此并不需要为其独自创建一个.service.ts的文件,直接在组件中使用就好

基本使用

导入: 先导入需要的模块并将其进行注册

ts 复制代码
// src/app/app.module.ts

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

import { AppComponent } from './app.component';
import { Myc06Component } from './myc06/myc06.component';
// 1.首先需要从指定的模块中导入 HttpClientModule 模块
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    Myc06Component,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    // 2.将这个模块导入注册进来
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

使用: 在指定的组件之中去使用这个服务

html 复制代码
<!-- src/app/myc06/myc06.component.html -->

<button (click)="Load()">进行加载</button>
ts 复制代码
// src/app/myc06/myc06.component.ts 

// 4.将angular内置的服务 HttpClient 导入进来,注意导入的路径
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-myc06',
  templateUrl: './myc06.component.html',
  styleUrls: ['./myc06.component.less'],
})
export class Myc06Component {
  constructor(private api: HttpClient) {}

  Load() {
    // 这个请求我是从百度搜索哪里随便找的一个,
    // 所以在发请求的时候会存在跨域的问题
    let url =
      '/api/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&sugsid=39311,39398,39396,39407,39097,39413,39436,39358,39307,39451,39464,39233,39249,26350,39426&wd=dew&his=%5B%7B%22time%22%3A1692619187%2C%22kw%22%3A%22%E6%8A%96%E9%9F%B3%22%7D%2C%7B%22time%22%3A1692675866%2C%22kw%22%3A%22%E6%8E%98%E9%87%91%22%7D%2C%7B%22time%22%3A1692675877%2C%22kw%22%3A%22%E7%A8%80%E5%9C%9F%E6%8E%98%E9%87%91%22%7D%2C%7B%22time%22%3A1692694636%2C%22kw%22%3A%22single%20element%20css%20spinners%22%7D%2C%7B%22time%22%3A1692757556%2C%22kw%22%3A%22bilibil%22%7D%2C%7B%22time%22%3A1692765426%2C%22kw%22%3A%22%E5%85%B8%E7%B1%8D%E9%87%8C%E7%9A%84%E4%B8%AD%E5%9B%BD%22%7D%2C%7B%22time%22%3A1692849120%2C%22kw%22%3A%22%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%22%2C%22fq%22%3A4%7D%2C%7B%22time%22%3A1692926175%2C%22kw%22%3A%22%E8%8A%B1%E7%93%A3%22%7D%2C%7B%22time%22%3A1692947784%2C%22kw%22%3A%22%E5%88%87%E5%B0%94%E8%AF%BA%E8%B4%9D%E5%88%A9%20%E7%90%B4%E4%BC%A6%22%2C%22fq%22%3A2%7D%2C%7B%22time%22%3A1695978309%2C%22kw%22%3A%22%E7%99%BE%E5%BA%A6%22%7D%5D&req=2&bs=%E4%B9%A0%E8%BF%91%E5%B9%B3%E5%BC%BA%E8%B0%83%E5%9B%A2%E7%BB%93%E5%B0%B1%E6%98%AF%E5%8A%9B%E9%87%8F&csor=3&pwd=de&cb=jQuery1102007980192120627838_1696042595801&_=1696042595804';
    // this.api表示那个内置的请求服务对象,后面跟请求的方式
    // 你所见的所有请求方式都可以使用,这里以get请求为例,
    // 它有两个参数,第一个是请求的地址,第二个是一个配置对象,
    // 用于设置请求头什么的,如果请求方式后面不跟subscribe
    // 这个方法的话,那么当前的请求是不会生效的,如果跟这个
    // 方法,方法有两个函数参数,一个是成功时执行,一个是失败时
    // 执行,跟promise的那两个函数参数使用的方法是一致的,
    // 下面这种写法是最新的写法
    this.api.get(url).subscribe({
      next: (res) => {
        console.log(res);
      },
      error: (err) => {
        console.log(err);
      },
    });
  }
}

private: 访问修饰符可以提供封装性,确保类的内部细节对外部是不可见的。这有助于实现信息隐藏和数据封装的原则,以便更好地控制类的行为和状态。

代理: 这里简单介绍一下代理是怎么配置的吧,代理的话实际上还是与webpack相关,我在webpack里面代理的具体配置我已经写好了

angularwebpack配置文件是proxy.conf.json

json 复制代码
// proxy.conf.json(这个文件在根文件夹下)

{
  // 使用正则进行匹配,如果匹配到的 /api 这个路径,那么
  // 它就会将请求转接到 下面的 target 路径下面,但此时
  // 路径中 还是会存在 /api 这个字符串,它会影响到转接的
  // 效果,所以需要使用 pathRewrite 路径重写将其去除掉
  "/api": {
    "target": "https://www.baidu.com",
    "secure": false,
    "changeOrigin": true,
    // 这个是当匹配到 /api 的时候会将其去除掉
    "pathRewrite": {
      "^/api": ""
    }
  }
}

八、组件的生命周期

1.介绍

说明: 介绍的顺序按照执行的顺序来的,使用的话跟vue中是一样的,区别在于触发的时机以及函数的参数不一样。

constructor

说明: 组件的构造函数,在创建组件实例时被调用,通常用于依赖注入。

ngOnChanges

说明: 在输入属性发生变化时被调用,在组件初始化时也会被调用一次。

ts 复制代码
import { Component, OnChanges } from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: `
    <h1>{{ title }}</h1>
  `
})

// implements OnChanges:这个不是必须的,如果存在的话,没有
//                       OnChanges是会报错的

export class MyComponent implements OnChanges {
  title: string;

  constructor() {
    this.title = 'My Component';
    console.log('constructor');
  }

  // 这是唯一一个有参数的周期函数,它会在组件的输入
  // 属性发生变化时调用,changes是一个对象,包含了
  // 输入属性的变化信息。
  ngOnChanges(changes: SimpleChanges) {
    console.log('ngOnChanges');
  }
}

使用场景:监听输入属性的变化,一旦输入属性发生变化,我们可以根据需要执行相应的操作,比如重新计算或更新其他相关的属性。

ngOnInit

说明: 在组件初始化完成后被调用,这个可以理解为vue中的mounted周期

使用场景:初始化组件的属性和数据,确保组件在渲染前已准备好所需的数据。

ngDoCheck

说明: 在每次 Angular 执行变更检测时被调用,在数据绑定发生变化时触发。

使用场景: 用于自定义的变更检测逻辑,主要用于性能优化、检测复杂对象的变化等。

ngAfterContentInit

说明: 在组件内容(例如子组件)初始化完成后被调用。

ngAfterContentChecked

说明: 在每次 Angular 执行内容检测后被调用,用于响应内容变化的操作。

使用场景:该方法在组件内容变更后被调用,可以执行与内容相关的操作,比如更新模板中的投影内容。

ngAfterViewInit

说明: 在组件的视图初始化完成后被调用。

ngAfterViewChecked

说明: 在每次 Angular 执行视图检测后被调用,用于响应视图变化的操作。

使用场景:该方法在视图变更后被调用,可以执行与视图相关的操作,比如处理 DOM 元素、调用第三方库等。

ngOnDestroy

说明: 在组件销毁时被调用。

使用场景:可以进行一些清理操作,如取消订阅,释放资源,避免内存泄漏和性能问题。

九、组件的数据传递

说明: 理解上跟vue是类似的,区别在于使用,在angular中,父组件可以通过属性绑定将数据传递给子组件,子组件通过@Input()装饰器接收数据并进行处理。而子组件可以通过@Output()装饰器和事件发射器EventEmitter将数据发送回父组件。

1.父组件给子组件传递数据

父组件:

html 复制代码
<!-- src/app/myc01/myc01.component.html -->

<app-myc02 [demoText]="text"></app-myc02>
ts 复制代码
// src/app/myc01/myc01.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  // 定义一个需要传递的数据
  text = 3
}

子组件:

html 复制代码
<!-- src/app/myc02/myc02.component.html -->

<p>{{ demoText }}</p>
ts 复制代码
// src/app/myc02/myc02.component.ts

// 注意input装饰器导入的路径
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-myc02',
  templateUrl: './myc02.component.html',
  styleUrls: ['./myc02.component.less']
})
export class Myc02Component {
  // 普通的属性是不能够被父组件传值的,如果需要的话,需要定义一个
  // 输入属性,很简单,就是使用input装饰器装饰一下就可以,其次,
  // 一个装饰器只能够装饰一个属性,并且装饰器和输入属性之间是
  // 不能够存在其他的内容的,最后,输入属性和装饰器之间是一一对应
  // 关系的
  @Input()
  demoText:any = null
}

2.父组件直接获取子组件的引用

父组件:

说明: 这种方式主要是使用#这个标识符来标记子组件,然后在父组件中定义变量,将这个变量通过@ViewChild装饰器与对应的子组件连接起来,此时,这个变量的值会变成指定子组件的引用,当然,装饰器与变量还是一对一的关系

html 复制代码
<!-- src/app/myc01/myc01.component.html -->

<!-- 将子组件myc02标记为#c01 -->
<app-myc02 #c01></app-myc02>

<button (click)='print()'>打印出子组件</button>
ts 复制代码
// src/app/myc01/myc01.component.ts

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

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  // 这个装饰器是有参数的,第一个参数表示定义的变量需要与那个子组件的
  // 关联起来,上面定义的名字是#c01,那么这里关联的标识符就是c01,第
  // 二个参数是一个配置对象,里面的static的取值是一个布尔值,表示标识符
  // 所在的这个元素是动态的还是静态的,动态的意思是它可能会显示,也可能
  // 不会显示,比如使用ngif这样的指令时,静态的话就是表示当前这个子组件
  // 一直是显示的状态,
  @ViewChild('c01', { static: true })
  private firstChild
  
  print() {
      // 这里的变量的值就是指定子组件的引用对象了
      console.log(this.firstChild)
  }
}

子组件:

html 复制代码
<!-- src/app/myc02/myc02.component.html -->

<p>这是子组件</p>
ts 复制代码
// src/app/myc02/myc02.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myc02',
  templateUrl: './myc02.component.html',
  styleUrls: ['./myc02.component.less']
})
export class Myc02Component {}

3.子组件给父组件传递数据

子组件:

html 复制代码
<!-- src/app/myc02/myc02.component.html -->

<!-- 子组件向上传递数据的话还是通过出发事件来完成任务的 -->
<button (click)="demo()">点击向父组件传递数据</button>
ts 复制代码
// src/app/myc02/myc02.component.ts

import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-myc02',
  templateUrl: './myc02.component.html',
  styleUrls: ['./myc02.component.less']
})
export class Myc02Component {
  // 其次由于是需要向外传递数据,那就不是使用input
  // 装饰器定义输入属性了,需要使用output定义输出
  // 属性,这个属性的值是一个触发器,通过它来完成
  // 传递数据的任务
  @Output()
  private demoEvent = new EventEmitter()

  private data = 2

  demo() {
    // 通过触发器的emit方法来传递,方法的参数就是
    // 传递的数据
    this.demoEvent.emit(this.data)
  }
}

父组件:

html 复制代码
<!-- src/app/myc01/myc01.component.html -->

<span>{{ text }}</span>

<!-- 在父组件上面需要定义一个自定义事件,事件的名字 -->
<!-- 需要和子组件中定义的那个触发器的名字相同, -->
<!-- 这样就接收到了子组件的数据,数据会存放在$event里面 -->
<app-myc02 (demoEvent)="doSomething($event)"></app-myc02>

关于$event:

如果事件是原生事件,$event表示事件对象event

如果事件是自定义事件,$event表示获取的数据

ts 复制代码
// src/app/myc01/myc01.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  text = 3

  doSomething( e: number ) {
    // 这里的参数名字可以随便写,其值的话就是传递过来的数据
    this.text = e
  }
}

十、路由与导航

1.路由的使用

创建路由规则

说明: 在路由规则比较少,复杂程度不是很大的话,可以将路由规则放在app.module.ts文件里面,但是不可以放在模块里面,其次,路由规则是一个数组,数组的每个元素是一个对象,对象由两部分组成,一个是path,这个是路由的路径,也就是在地址栏中 / 后面的内容,它是一个字符串,另一个是component,它是path路径对应的组件,它的值是上面导入进来的组件名

ts 复制代码
// src/app/app.module.ts

import { AppComponent } from './app.component';
import { Myc02Component } from './myc02/myc02.component';

// 一般情况这个路由规则数组的名称叫 router,也是习惯吧
let router = [
  {
    path: 'index',
    // path: '/index', --> 错误写法
    // path: 'index/', --> 错误写法
    // path: 'index/index', --> 正确写法
    component: Myc02Component,
  },
];

@NgModule({
  declarations: [],
  imports: [],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
html 复制代码
<!-- src/app/myc02/myc02.component.html -->

<p>这是index页面</p>

注意:

path的值不能够使用/开头,也不能够使用/结尾,如果非要使用,只能够在中间

注册路由

说明: 注册路由需要使用到RouterModule这个模块,注意导入的路径是@angular/router

ts 复制代码
// src/app/app.module.ts

import { AppComponent } from './app.component';
import { Myc02Component } from './myc02/myc02.component';
import { RouterModule } from '@angular/router';

let router = [
  {
    path: 'index',
    component: Myc02Component,
  },
];

@NgModule({
  declarations: [],
  imports: [
      // 这个模块有两个静态方法forRoot和forChild用来
      // 配置路由规则,一个是规则在主模块中注册,一个
      // 是在指定的模块中注册,可以理解为全局路由和局部
      // 路由吧,这里就是在主模块中注册
      RouterModule.forRoot(router)
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

forRoot 方法:

  • 用于在应用程序的主模块中配置主要的路由规则。

  • 仅在应用程序的主模块中调用一次。

  • 会创建并返回一个带有路由配置的 RouterModule 实例,并将其提供给注入器。
    forChild 方法:

  • 用于在其他模块中配置附加的路由规则。

  • 在应用程序的任何模块中都可以调用多次。

  • 会创建并返回一个带有路由配置的 RouterModule 实例,但不会将其提供给注入器。

  • 根据 forChild 方法调用的顺序,路由规则将按照各个模块的顺序进行处理。

路由出口

说明: vue中这个出口是用router-view来完成的,这里也类似,只不过是用router-outlet来替代

html 复制代码
<!-- src/app/app.component.html -->

<!-- 然后切换的页面会在这里显示 -->
<router-outlet></router-outlet>

特殊的路由path

ts 复制代码
// src/app/app.module.ts

import { AppComponent } from './app.component';
import { Myc02Component } from './myc02/myc02.component';
import { Routes, RouterModule } from '@angular/router';

// Route类型:表示只存在一条路由规则
// Routes类型:表示一个数组,可以存放多条路由规则
let router: Routes = [
  // 路由重定向:当访问的路径为空的时候,重定向到路径为
  //            index的组件,如果是重定向,那么汽配规则
  //            pathMatch为必填项,其值有两个,一个是  
  //            prefix前缀匹配,也就是开头如果满足匹配
  //            条件就算匹配上了,另一个是完全匹配full,
  //            也就是需要路径完全一致才可以,一般重定向
  //            放在最开头
  {
    path: '',
    redirectTo: 'index',
    pathMatch: 'full',
  },
  {
    path: 'index',
    component: Myc02Component,
  },
  
  // 可以理解为默认展示的页面
  {
    path: '',
    component: Myc02Component,
  },
  
  // 匹配不存在的页面:**在angular中表示匹配所有,也就是
  //                  匹配所有组件,路由一般是满足一个条
  //                  件的匹配就不会继续往下进行,所以这
  //                  个匹配的规则放在最后面,不然所有的
  //                  路径都会匹配到这一个组件
  {
    path: '**',
    component: Myc03Component,
  },
];

@NgModule({
  declarations: [],
  imports: [],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

路由部分单独分离出来

说明: 如果程序很复杂,很庞大,那路由规则也会很多,可能也很复杂,这样就可以将每个部分的路由规则抽离出来,这样也便于去维护,比如像下面这样

ts 复制代码
// 在路由模块中定义路由:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
ts 复制代码
// 在主模块中导入路由模块并将其添加到 imports 数组中:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';

@NgModule({
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    HomeComponent,
    AboutComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

路由的嵌套

说明: 实现路由的嵌套只需要在创建的路由规则中添加一个children的属性就可以,它的值是一个数组,数组的每一个元素就是嵌套路由中的每一条路由规则,之后,嵌套的路由出口是放在那个存在路由嵌套的组件中

ts 复制代码
// src/app/app.module.ts

import { AppComponent } from './app.component';
import { Myc02Component } from './myc02/myc02.component';
import { Myc03Component } from './myc02/myc03.component';
import { RouterModule } from '@angular/router';

let router = [
  {
    path: '/index',
    component: Myc02Component,
    // 这里的本质是在组件Myc02Component里面还会存在路由,
    // 也就是路由的嵌套了
    children: [
        {
          // 没有 / 表示 组件Myc03Component的路由path是
          // '/index/index',它会自动的进行拼接
          path: 'index',
          component: Myc03Component,
        },
    ]
  },
];

@NgModule({
  declarations: [],
  imports: [
      RouterModule.forRoot(router)
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
html 复制代码
<!-- src/app/app.component2.html -->

<!-- 上面是component2组件中存在路由的嵌套, -->
<!-- 所以路由出口放在component2里面 -->
<router-outlet></router-outlet>

2.导航

声明式导航

说明: 在 HTML 中使用 routerLink 指令创建一个路由链接。这个指令会为元素自动添加点击事件,并根据指定的路由路径进行导航,值得注意的是,这个指令可以用在任意的标签上面

ts 复制代码
// src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { Myco1Component } from './myco1/myco1.component';
import { RouterModule } from '@angular/router';

let router = [
  {
    path: 'index',
    component: Myco1Component
  }
]

@NgModule({
  declarations: [
    AppComponent,
    Myco1Component,
  ],
  imports: [
    RouterModule.forRoot(router)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
html 复制代码
<!-- src/app/app.component.html -->

<p>路由部分</p>
<router-outlet></router-outlet>
html 复制代码
<!-- src/app/myco1/myco1.component.html -->

<!-- 这两种写法的跳转结果会存在一些差异 -->
<button routerLink="index">点击进行路由跳转(相对路径)</button>
<button routerLink="/index">点击进行路由跳转(绝对路径)</button>
  1. <a routerLink="/home">Home</a>:这种方式指定了完整的绝对路由路径 /home。当用户点击这个链接时,Angular 路由器会导航到根路径下的 home 路由。
  2. <a routerLink="home">Home</a>:这种方式指定的是相对路径 home。当用户点击这个链接时,Angular 路由器会根据当前激活路由的上下文进行导航。如果当前激活的路由路径是 /about,那么该链接会导航到 /about/home 路由。

编程式导航

说明: 编程式导航是指通过编写代码来实现路由导航,而不是使用声明式导航。在 Angular 中,可以通过使用 Router 服务来实现编程式导航。

(1)navigateByUrl

作用: 用于通过指定的 URL 导航到一个新的路由。

参数:

ts 复制代码
navigateByUrl(url: 'URL地址', extras?: { 
    // (对象)用于设置查询参数
    queryParams: { type: 'electronics' },  
    // (字符串)用于设置 URL 的片段标识符
    fragment: 'details',  
    // (字符串)定义在导航过程中如何处理现有的查询参数。可选值包括
    // `"preserve"`、`"merge"` 或 `"preserveFragment"`。
    queryParamsHandling: 'merge',  
    // (布尔值)指示是否保留现有的片段标识符。
    preserveFragment: true,  
    // (布尔值)指示是否跳过将当前 URL 添加到浏览器的历史记录。
    skipLocationChange: true,  
    // (布尔值)指示是否要替换当前 URL 的历史记录,而不是添加新的 URL 到历史记录。
    replaceUrl: true 
} )

queryParamsHandling 的三个取值:

  • "preserve":该值表示保留现有的查询参数。在导航过程中,新的查询参数将被忽略,仅保留现有的查询参数。例如,如果当前 URL 是 /products?type=electronics,并且你使用 queryParams 设置了新的查询参数 { brand: 'Samsung' },则导航后的 URL 仍然是 /products?type=electronics

  • "merge":该值表示合并新的查询参数与现有的查询参数。在导航过程中,新的查询参数将与现有的查询参数合并。例如,如果当前 URL 是 /products?type=electronics,并且你使用 queryParams 设置了新的查询参数 { brand: 'Samsung' },则导航后的 URL 将会是 /products?type=electronics&brand=Samsung

  • "preserveFragment":该值表示保留现有的片段标识符(URL 中以 # 开头的部分)。在导航过程中,片段标识符将被保留,不会受到新的查询参数的影响。例如,如果当前 URL 是 /products#details,并且你使用 queryParams 设置了新的查询参数 { type: 'electronics' },则导航后的 URL 仍然是 /products#details

使用:

html 复制代码
<!-- src/app/myco1/myco1.component.html -->

<button (click)="jump()">点击实现编程式导航</button>
ts 复制代码
// src/app/myco1/myco1.component.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  // 只能够在构造函数这里注入服务,普通函数是注入不了的
  constructor(private router: Router) {}

  jump() {
    // 注意:地址有 / 和没有 / 是两码事
    this.router.navigateByUrl('/index')
  }
}

navigateByUrl的参数URL是不能够使用外部的URL的,像https://www.baidu.com/这样,它并不适用,它的参数还是可以理解为定义的路由匹配规则中path的取值

(2)navigate

说明: 他的作用与上面方法的作用一致,区别在于它可以传递路径参数,但是上面哪一种不行

参数:

ts 复制代码
navigate(['路由地址', 路径参数1, 路径参数2...], {
    // (对象)传递查询参数
    queryParams: queryParams,
    // (字符串)设置 URL 的片段标识符(通常是页面内的锚点)
    fragment: 'section1',
    // (布尔值)决定是否保留现有的查询参数。
    preserveQueryParams: true,
    // (字符串)决定如何处理现有的查询参数
    queryParamsHandling: 'merge',
    // (布尔值)决定是否保留现有的 URL 片段标识符。
    preserveFragment: true,
    // (ActivatedRoute实例)用于指定相对于当前活动路由的导航目标。
    relativeTo: this.activatedRoute,
    // (布尔值)是否替换当前 URL 而不是创建新的浏览历史记录项。
    replaceUrl: false,
    // (对象)用于传递任意的导航状态数据
    state: { data: 'some state data' }
})

使用: 使用的方法跟上面的方法是一致的,只是参数不同而已(以上面的例子为例)

ts 复制代码
// src/app/myco1/myco1.component.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  // 只能够在构造函数这里注入服务,普通函数是注入不了的
  constructor(private router: Router) {}

  jump() {
    // 注意:地址有 / 和没有 / 是两码事
    this.router.navigate(['/index'])
  }
}

3.路由参数

路径参数

说明: vueparams参数使用跟这个是类似的,也就是参数需要定义在路由配置对象里面,像/:参数名这样的格式来定义在path路径之后,传值使用/参数值的方式传递

(1)定义参数

ts 复制代码
// src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { Myco1Component } from './myco1/myco1.component';
import { RouterModule } from '@angular/router';

let router = [
  {
    // 这里给路径为index的路由定义了一个id的路径参数,
    // 如果定义多个的话,注意传值的顺序,避免传错
    path: 'index/:id',
    component: Myco1Component
  }
]

@NgModule({
  declarations: [
    AppComponent,
    Myco1Component,
  ],
  imports: [
    RouterModule.forRoot(router)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

(2)传递参数

html 复制代码
<!-- src/app/myco1/myco1.component.html -->

<!-- 声明式导航 -->
<!-- 这里给上面定义的路径参数id传值为1 -->
<button routerLink="/index/1">点击进行路由跳转(绝对路径)</button>
ts 复制代码
// src/app/myco1/myco1.component.ts

// 编程式导航
import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-myco1',
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})
export class Myco1Component {
  // 只能够在构造函数这里注入服务,普通函数是注入不了的
  constructor(private router: Router) {}

  jump() {
    // 暂时了解到可以传递路径参数的方法只有navigate,
    // 传递的位置在第一数组参数里面,数组的第一个元素为
    // 路由地址,从第二个参数开始就是传递的路径参数的值了
    // 注意传递的顺序,避免穿错
    this.router.navigate(['/index',1])
  }
}

(3)参数的接受

ts 复制代码
// src/app/myco1/myco1.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-myco1'
  templateUrl: './myco1.component.html',
  styleUrls: ['./myco1.component.less']
})

export class Myco1Component implements OnInit {
  // 当跳转到此页面的时候,我需要知道传递了什么参数,
  // 对于路由来说,肯定需要路由服务来支持的,但是对于
  // 查看某个页面所需要的路由参数,我只需要当前页面路
  // 由信息就足够了,也就是说不需要去使用路由器对象了,
  // 当前路由信息通过ActivatedRoute服务对象获取
  constructor(private route: ActivatedRoute) {}

  // 一般参数获取实在页面加载完毕之后进行的,
  // 也就是类似vue中的mounted生命周期,
  // angular用ngOnInit来代替
  ngOnInit() {
    
    // this.route.params可以拿到传递的参数,
    // 但是其类型是可订阅的,所以需要去订阅它
    // 去获取并且通过回调函数来对参数进行自定义操作
    this.route.params.subscribe((data) => {
      console.log(data)
    })
  }
}

(4)注意事项

  • 如果在路由配置对象中某个路径他的路径参数被定义了,那么这个路由参数是一个必传的值,也就是如果不传,路由跳转就会报错

4.路由守卫

说明: 路由守卫是一种用于保护和控制导航的机制。它允许开发者在路由导航之前和之后执行相应的操作,常用的守卫有三个,分别是CanActivateCanActivateChildCanDeactivate,特殊的有两个,分别是ResolveCanLoad

创建命令

ts 复制代码
// 完整写法
ng generate guard 守卫名称

// 简洁写法
ng g guard 守卫名称

守卫介绍

(1)CanActivate

说明: 用于确定是否允许进入特定路由。它可以通过实现CanActivate接口来创建一个守卫。当导航到某个路由时,Angular会调用CanActivate接口中的canActivate方法。如果该方法返回true,导航将继续;如果返回false,导航将被取消。

ts 复制代码
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';

// 可以看出路由守卫的本质是一个服务类
@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(private router: Router) {}

  canActivate(
    // 表示当前路由的快照信息,可以通过它获取路由的参数、
    // 查询参数等。
    route: ActivatedRouteSnapshot,
    // 表示当前路由状态的快照信息,可以通过它获取当前的 URL、
    // 导航方向等。
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // 在这里编写守卫的逻辑
    // 如果满足条件,返回 true表示授权通过,导航继续
    
    // 如果不满足条件,返回 false 或者使用 
    // this.router.navigate 方法进行重定向

    // 或者返回 false 或者返回 this.router.navigate 方法
    return true; 
  }
}
ts 复制代码
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
import { AdminComponent } from './admin.component';
import { AuthGuard } from './auth.guard';

const routes: Routes = [
  { 
    path: '', 
    component: HomeComponent 
  },
  {
    path: 'admin',
    component: AdminComponent,
    // 一个组件可以使用多个守卫来进行授权
    canActivate: [AuthGuard]
  }
];

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

(2)CanActivateChild

说明: 类似于CanActivate,但是它用于确定是否允许进入特定路由的子路由。它通过实现CanActivateChild接口来创建一个守卫。

ts 复制代码
import { Injectable } from '@angular/core';
import { CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable()
export class ParentGuard implements CanActivateChild {
  constructor(private router: Router) {}

  canActivateChild(
    // 表示即将激活的子路由的快照信息,
    // 可以通过它获取子路由的参数、查询参数等。
    childRoute: ActivatedRouteSnapshot,
    // 表示当前路由状态的快照信息,可以通过它获取
    // 当前的 URL、导航方向等。
    state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    // 在这里编写授权逻辑,判断是否允许激活子路由
    // 根据需要可以使用 childRoute 和 state 参数

    const user = // 获取用户信息的逻辑...

    if (user.isAdmin) {
      return true; // 允许激活子路由
    } else {
      return this.router.parseUrl('/forbidden'); // 重定向到禁止访问页面
    }
  }
}
ts 复制代码
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ParentGuard } from './parent.guard';

const routes: Routes = [
  {
    path: 'admin',
    // 注意每种路由守卫挂载到组件上面的属性是不一致的
    canActivateChild: [ParentGuard], // 使用 CanActivateChild 守卫
    children: [
      // 子路由配置...
    ]
  },
  // 其他路由配置...
];

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

(3)CanDeactivate

说明: 用于确定是否允许从当前路由离开。它可以通过实现CanDeactivate接口来创建一个守卫。当要离开当前路由时,Angular会调用CanDeactivate接口中的canDeactivate方法。如果该方法返回true,导航将继续;如果返回false,导航将被取消。

ts 复制代码
import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<any> {
  canDeactivate(
    // 表示当前要离开的组件实例。
    component: any,
    // 表示当前路由的快照信息。
    currentRoute: ActivatedRouteSnapshot,
    // 表示当前路由状态的快照信息。
    currentState: RouterStateSnapshot,
    // 表示即将导航到的下一个路由状态的快照信息
    // (可选参数,如果它是 undefined,则表示没有下一个路由)。
    nextState?: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    // 在这里编写离开路由的逻辑
    // 根据组件和路由状态判断是否允许离开路由

    if (component.canLeave()) {
      return true; // 允许离开路由
    } else {
      return confirm('确定要离开此页面吗?'); // 弹出确认对话框
    }
  }
}
ts 复制代码
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CanDeactivateGuard } from './can-deactivate.guard';
import { MyComponent } from './my.component';

const routes: Routes = [
  {
    path: 'my',
    component: MyComponent,
    canDeactivate: [CanDeactivateGuard], // 使用 CanDeactivate 守卫
  },
  // 其他路由配置...
];

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

(4)Resolve

说明: 使用 resolve 可以在路由激活之前获取所需的数据,并等待数据可用后再加载路由组件。这样可以确保组件在渲染之前具有所需的数据,以避免在组件中处理数据加载的异步问题。

ts 复制代码
// 创建一个服务来获取预先需要的数据
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { DataService } from './data.service';

@Injectable()
export class MyResolver implements Resolve<any> {
  constructor(private dataService: DataService) {}

  // route:它包含了当前激活的路由的信息,包括路由
  //        参数、查询参数、路由路径等。
  // state:它表示当前的路由状态,包含了路由树的快照信息,
  //        可以用于获取当前的路由路径、URL 等。
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    // 在这里通过调用数据服务获取数据
    return this.dataService.getData();
  }
}
ts 复制代码
// 在路由配置中使用 `resolve`
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { MyResolver } from './my.resolver';

const routes: Routes = [
  {
    path: 'home',
    component: HomeComponent,
    resolve: {
      resolvedData: MyResolver // 使用 MyResolver 获取数据
    }
  },
  // 其他路由配置...
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
ts 复制代码
// 在组件中访问预先解析的数据:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  ...
})
export class HomeComponent implements OnInit {
  resolvedData: any;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.resolvedData = this.route.snapshot.data.resolvedData;
  }
}

(5)CanLoad

说明: 当使用惰性加载(Lazy Loading)模块时,可以使用 CanLoad 守卫来控制是否允许加载该模块。CanLoad 守卫用于在加载惰性模块之前执行某些验证或权限检查

ts 复制代码
// 创建一个实现了CanLoad接口的守卫类
import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable()
export class MyModuleGuard implements CanLoad {
  constructor(private authService: AuthService) {}

  // route: 表示正在加载的惰性模块的路由配置。
  // segments: 一个数组,包含未处理的 URL 片段对象,
  //           表示正在加载的惰性模块的 URL 片段。
  canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean {
    // 根据权限检查用户是否可以加载模块
    return this.authService.checkUserPermissions(); 
  }
}
ts 复制代码
// 在路由配置中使用 CanLoad守卫。
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MyModuleGuard } from './my-module.guard';

const routes: Routes = [
  {
    path: 'my-module',
    canLoad: [MyModuleGuard], // 使用 MyModuleGuard 来控制是否允许加载模块
    loadChildren: () => import('./my-module/my-module.module').then(m => m.MyModule)
  },
  // 其他路由配置...
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
ts 复制代码
// 配置守卫后,可以在AuthService中实现具体的权限检查逻辑。
@Injectable()
export class AuthService {
  // ...

  checkUserPermissions(): Observable<boolean> {
    // 进行具体的权限检查,返回 Observable<boolean> 或 Promise<boolean>
  }
}
相关推荐
前端小小王33 分钟前
React Hooks
前端·javascript·react.js
迷途小码农零零发43 分钟前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀1 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef3 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6414 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云4 小时前
npm淘宝镜像
前端·npm·node.js
dz88i84 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr4 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook