前言
Angular相比于原生js有什么优势? 什么是单页面应用(SPA)呢?
优势:
- 项目架构很清晰
- 代码的复用性强
- 代码的可测试强,可以添加e2e测试,单元测试
单页面应用相比较之前请求多个html页面的优势
- 用户体验好,一次性加载所有的js和css,内容的改变不需要重新加载整个页面
- 前后端分离
Angular开发环境搭建
在原生js里一般通过<Script>
标签引入一些第三方库,随着框架的的普及,之前引入的方式略low,工作中也很少用到这种方式,在学习Angular时候,我们就采用脚手架形式的安装。这就需要我们安装Node.js
,用里边的npm
来进行安装。
node安装
可以通过 node -v 以及 npm -v 的形式来检查是否安装成功
脚手架安装
node安装成功之后,可以使用npm命令安装脚手架
npm install -g @angular/cli // 装的是最新的版本
npm install -g @angular/cli@xx.xx.x // 也可以安装指定版本
创建一个新项目并执行
ng new project_name // project_name用户可以自定义项目名
cd project_name
ng serve
浏览器输入http://localhost:4200 便可看到相关页面
[图片上传失败...(image-b13261-1637659554980)]
输入命令之后,按照接下来的提示,进行选择,第一个是否选择在严格模式下创建这个项目;第二是否在创建项目的时候帮你把router文件一并创建出来;第三是用那种css类型进行编码。可以按照上图的选择情况进行创建。
项目目录介绍
[图片上传失败...(image-6ff86f-1637659554980)]
组件介绍
组件的概念
[图片上传失败...(image-537545-1637659554980)]
介绍组件之前,我们先来介绍一下组件
和模块
之前的关系,框架都是推崇组件化开发的,页面上所有的元素都可以看做一个组件,具体页面中组件划分的粒度是多大,还是按照自己的个人开发习惯来进行。在angular中我们知道了app模块
是我们的根模块,他管理了整个单页面应用中涉及到其他模块
和组件
。我们来熟悉一下app.module.ts这个根模块文件。
[图片上传失败...(image-702fde-1637659554980)]
@NgModule是 angular中的装饰器,是需要从'@angular/core'导入进来
作用: 帮助开发者组织业务代码,开发者可以利用 NgModule 把关系比较紧密的组件组织到一起,他不仅可以控制组件,还可以控制指令,管道等(本期我们只要先学会组件,指令和管道后期会讲到)
declarations:用来放
组件、指令、管道
的声明, 组件、指令、管道都必须属于一个模块,而且只能属于一个
模块。imports:用来导入
外部模块
而非组件。exports: 我们这个模块需要导出的一些
组件,指令,模块
等; 如果别的模块导入了我们这个模块,那么别的模块就可以直接使用我们在这里导出的组件,指令模块等.providers:需要使用的
Service
都放在这里。指定应用程序的根级别需要使用的servicebootstrap:定义启动组件。你可能注意到了这个配置项是一个数组,也就是说可以指定做个组件作为启动点。一般这个不需要改动。
由上述可见: angular中单页面是有一个根模块的,这个模块中包含了很多组件,也就是说多个组件组成了模块。
hello world以及组件讲解
首先我们先将app.component.html中的内容全部清空,为我们接下来的组件学习做准备。
1. 在app文件夹下,新建一个组件文件,文件名为hello-world.component.ts
[图片上传失败...(image-b51833-1637659554980)]
从上图片中,我们可以得知组件的组成有以下三部分组成:
类 (类名的命名是根据组件的文件名来决定的,首字母大写的驼峰命名方式)
装饰器@component, 需要从'@angular/core'中导入,其作用是把某个类标记为Angular组件,并为他配置一些元数据,目前这里只涉及到3个元数据,其中
selector
也称作是选择器,我们可以把他理解成我们自定义组件的名字,一般他的命名也是app-组件文件
的名字,更多的元数据应用可以参照官网。HTML模板,就是template。
2. 上边已经提到到,组件必须在模块中声明才可以正常使用,所以我在根模块声明一下
[图片上传失败...(image-e5e65a-1637659554980)]
3. 我们将定义好的组件在app的html中中引用一下
[图片上传失败...(image-2029ac-1637659554980)]
这个时候页面上已经可以完美的展现出 hello world了,这样一个简单的组件也就封装好了。
组件中的插值表达式应用
假如有个用户在上述的组件中不希望展示”hello world“,怎么样动态灵活的显示在上述组件中呢?
其实在angular中我们可以使用
插值
的方式,将一些变量或者表达式
嵌入到文本中,默认是用{{}}来作为定界符
// hello-world.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: '<h1> {{title}} </h1>',
styles: ['h1 { color: red; }']
})
export class HelloWorldComponent {
public title = '学习Angular第一课';
}
此时打开浏览器页面,就可以看到页面上展示” 学习Angular第一课“
组件中内置指令的介绍
首先我们来看一下什么是指令?其实指令是为 Angular 应用程序中的元素
添加额外行为的类
。
这里主要介绍一下属性型指令
和结构型指令
。
属性型指令:
-
NgClass
: 动态添加和删除一组css类
// NgClass与表达式一起使用,通过ngClassHasColor来决定是否添加ngclass-color这个类
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<div [ngClass]="ngClassHasColor ? 'ngclass-color' : ''">练习Ngclass的使用</div>
`,
styles: [
'h1 { color: red; }',
'.ngclass-color {color: green}'
]
})
export class HelloWorldComponent {
public ngClassHasColor = true;
}
-
NgStyle
: 动态添加和删除一组内联样式
1. import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<div [ngStyle]="{'color': 'black', 'font-size': '18px'}">练习Ngstyle的使用</div>
`,
styles: []
})
export class HelloWorldComponent {}
// 或者我们可以将多个内联样式封装到一个对象中,这样我们就可以动态控制对象中的值,
// 只要将对象的变量直接赋值给[ngStyle]
2. import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<div [ngStyle]=ngStyleObj>练习Ngstyle的使用</div>
`,
styles: []
})
export class HelloWorldComponent {
public ngStyleObj = {
'color': 'black',
'font-size': '18px'
};
}
-
NgModel
: 将数据的双向绑定添加到HTML表单元素上
结构型指令: -
NgFor
: 重复循环渲染某一个节点
// 在js中,如果需要渲染列表,我们是需要手动遍历数据,然后动态生成元素,这样才能实现列表的渲染
// 在angular中,我们仅仅需要使用NgFor这个语法糖就可以实现数据的循环展示
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<ul>
<li *ngFor="let subject of studySubjects">{{subject}}</li>
</ul>
`,
styles: []
})
export class HelloWorldComponent {
public studySubjects = [
'环境搭建',
'项目目录介绍',
'组件介绍',
'ng-zorro组件库的引入介绍'
];
}
// 除此之外,我们还可以获取到循环对象的角标index,并在模板中使用它,如下所示:
<ul>
<li *ngFor="let subject of studySubjects; let i=index">{{ i+1 }} - {{ subject }}</li>
</ul>
循环结果如下所示:
[图片上传失败...(image-91ad8a-1637659554980)]
-
NgIf
: 从模板中创建或销毁子视图,通俗说是控制组件或者元素的显示和隐藏
// 假如有个需求:
// 说是用户身份是’backend‘的就可以看到上述的循环列表;其他的用户都不能看到,在Angular中改怎么实现呢?
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<ul *ngIf="userRole === 'backend'">
<li *ngFor="let subject of studySubjects; let i=index">{{ i+1 }} - {{ subject }}</li>
</ul>
`,
styles: []
})
export class HelloWorldComponent {
public userRole='backend'; // 只要userRole的值不是backend,那么上述的列表就不会展示
}
-
NgSwitch
: 类似于js中的switch的逻辑,也是按照满足固定的条件显示某些模板/视图
NgSwitch是一组属性(三个) 分别是:
NgSwitch
;NgSwitchCase
;ngSwitchDefault
// 现在假如有一个需求:
// 说是假如用户的年龄是12则显示小学毕业
// 假如用户的年龄是18则显示高中毕业
// 假如用户的年龄是22则显示大学毕业
// 其他情况全都显示未知
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<div [ngSwitch]="age">
<span *ngSwitchCase="'12'">小学毕业</span>
<span *ngSwitchCase="'18'">高中毕业</span>
<span *ngSwitchCase="'22'">大学毕业</span>
<span *ngSwitchDefault>未知</span>
</div>
`,
styles: []
})
export class HelloWorldComponent {
public age = 18; // 页面显示”高中毕业“
}
组件中属性的绑定
在上述内容中,我们发现很多用[]
包裹起来的值,比如之前提到的[ngClass]
,[ngStyle]
,其实这些被中括号包裹起来的,我们就称作是属性绑定
。他的作用就是动态设置 HTML 元素或指令
的属性值
。接下来我们介绍一下在开发中常见的属性值。
基本属性绑定
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<img [src]="itemImageUrl">
<button [disabled]="isDisabledBtn">Disabled Button</button>
<span [innerHtml]="spanText"></span>
`,
styles: []
})
export class HelloWorldComponent {
public itemImageUrl = '../../assets/bg.png';
public isDisabledBtn = false;
public spanText='通过属性绑定来展示span标签的内容';
}
绑定到class的Attribute(平时开发推荐使用,且用的频率很高)
此方式可以动态的添加和删除单个或者多个类,与上述的[ngClass]很类似,但是用法更加的简单
//动态添加单个class和多个class
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<div [class.textcolor]="isSingleClass">尝试绑定单个class</div>
<div [class]="multipleClass">尝试绑定多个class</div>
`,
styles: [
'.bgColor{background-color: yellow}',
'.textcolor{color: red}'
]
})
export class HelloWorldComponent {
public isSingleClass: boolean;
public multipleClass = { // 所以这里生效的只有bgColor这个类
bgColor: true,
textcolor: false,
};
}
// 同理属性style也是一样的也可以像class一样
组件中管道的使用
管道用来对字符串、货币金额、日期和其他显示数据
进行转换和格式化
。它是一些简单的函数,angular中提供了一下内置的管道方法,同时我们也可以自定义管道。
-
DatePipe
:根据本地环境中的规则格式化日期值。 -
UpperCasePipe
:把文本全部转换成大写。 -
LowerCasePipe
:把文本全部转换成小写。 -
CurrencyPipe
:把数字转换成货币字符串,根据本地环境中的规则进行格式化。 -
DecimalPipe
:把数字转换成带小数点的字符串,根据本地环境中的规则进行格式化。 -
PercentPipe
:把数字转换成百分比字符串,根据本地环境中的规则进行格式化。
// 以date这个内置管道作为例子
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<p>小明的生日是{{ birthday | date }}</p>
<p>小明的生日是{{ birthday | date:"MM/dd/yy" }}</p>
`,
styles: []
})
export class HelloWorldComponent {
public birthday = new Date(1988, 3, 15);
}
//结果
小明的生日是Apr 15, 1988
小明的生日是04/15/88
该组件的
birthday
值通过管道操作符(|)流向date
函数。
date: 后边的值是传入到这个管道的参数
,如果有多个参数可以用多个:
来分割传入
组件的事件绑定-方法和参数
组件的事件绑定确实比原生js的事件绑定简便很多,我们先来介绍一下angular中是怎么样实现事件绑定的吧~
- 按钮的事件绑定
//我们希望通过点击按钮实现一个累加器
import { Component } from '@angular/core';
@Component({
selector: 'app-hello-world',
template: `
<button (click)="addCount()">当前数值加1</button>
<span>当前值是:{{curNumber}}</span>
`,
styles: []
})
export class HelloWorldComponent {
public curNumber = 1;
public addCount(): void {
this.curNumber++;
}
}
- 事件绑定中的$event对象
在编写响应事件时,是可以接受一个$event参数的,这个参数就是关于响应事件的一些内容.
<button (click)="addCount($event)">当前数值加1</button>
public addCount(event): void {
console.log(event);
this.curNumber++;
}
- 事件的传参
<button (click)="addCount($event, 5)">当前数值加1</button>
public addCount(event, paramsNumber: number): void {
this.curNumber = this.curNumber + paramsNumber; // 可以实现每次加5
}
- 一个按钮同时调用两个方法
// 通过分号去分割两个方法
<button (click)="addCount($event, 5);consoleCount()">当前数值加1</button>
// 定义的两个方法
public addCount(event: any, paramsNumber: number): void {
this.curNumber = this.curNumber + paramsNumber;
}
public consoleCount(): void {
console.log(this.curNumber);
}
组件的声明方式
上述示例中,我们是把html,css已经ts的逻辑写到同一个文件中的,在真正开发的过程中,如果逻辑较复杂这种内联式的组件声明是不推荐的,一般情况下我们常用的是外部引用文件的形式
,如下所示
// template和style都是通过引入外层的文件,这样就可以把html和css的代码放到单独的文件进行开发
import { Component } from '@angular/core';
@Component({
selector: 'app-outside-introduction',
templateUrl: './outside-introduction.component.html',
styleUrls: ['./outside-introduction.component.scss']
})
export class OutsideIntroductionComponent {}
具体的组件OutsideIntroductionComponen
创建如下:
[图片上传失败...(image-3d8197-1637659554980)]
组件的父子组件传值
@Input()
和@Output()
为子组件提供了一种与其父组件通信的方法。@Input()
允许父组件更新子组件中的数据。相反,@Output()
允许子组件向父组件发送数
父组件向子组件里传值是通过属性
进行传值的
// app-outside-introduction是app.component的子组件,现在通过这个组件来学习一下父子传值
// outside-introduction.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-outside-introduction',
templateUrl: './outside-introduction.component.html',
styleUrls: ['./outside-introduction.component.scss']
})
export class OutsideIntroductionComponent {
// @Input()是专门用来实现传值的,需要提前在'@angular/core引入
// 这句声明,表示希望在父组件引入子组件的html页面中,从父组件中传入一个值给到showNumber
@Input() public showNumber: number;
}
// outside-introduction.component.html
<div>显示父组件传进来的值{{showNumber}}</div>
// 父组件app.component.html
<div>父组件中的值:{{defaultNum}}</div>
<app-outside-introduction [showNumber]="defaultNum"></app-outside-introduction>
// 父组件app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
public defaultNum = 5;
}
上述核心代码其实只要两步:
1. 子组件通过@Input声明要进行通信的变量
@Input() public showNumber: number;
2.父组件在引入的时候通过属性绑定来传递在ts中定义好的变量
<app-outside-introduction [showNumber]="defaultNum"></app-outside-introduction>
export class AppComponent {
public defaultNum = 5;
}
子组件向父组件传值,通过事件的形式来实现
假如上述子组件有个累加器的功能,希望将内部不断累计的东西传递给父组件,并在父组件显示改怎么做呢?
子组件
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-outside-introduction',
templateUrl: './outside-introduction.component.html',
styleUrls: ['./outside-introduction.component.scss']
})
export class OutsideIntroductionComponent {
@Input() public showNumber: number;
// 下边的逻辑主要实现自组价向父组件emit一个值
@Output() public curCountChange = new EventEmitter<number>();
public curCount = 1;
public addCount(): void {
this.curCount++;
this.curCountChange.emit(this.curCount);
}
}
父组件
// 这里的事件名curCountChange必须和子组件定义@Output()的名字是一样的,=后边的方法名可以自己随意定义
// `$event`是子组件传过来的值
<app-outside-introduction [showNumber]="defaultNum" (curCountChange)="handlCurCountChange($event)"></app-outside-introduction>
public handlCurCountChange(value: number): void {
// 这里的value就是子组件传过来的值
this.valueFromChild = value;
}
详细介绍一下子组件中的@output
@Output()
- 一个装饰器函数,它把该属性标记为数据从子组件进入父组件的一种途径curCountChange
- 这个@Output()
的名字EventEmitter<number>
- 这个@Output()
的类型,就是子组件传给父组件的数据类型new EventEmitter<number>()
- 使用 Angular 来创建一个新的事件发射器,它发出的数据是number
类型的。curCountChange.emit()
- 通过emit方法来向父组件传递值
最终父组件通过事件的形式接受子组件穿过来的值
组件的双向绑定
其实双向绑定就是特殊一点或者是一个更加简洁的父子组件传值,他是通过[()]
语法来组合属性和事件绑定,唯一特殊的点在于他们的命名,为了使双向数据绑定有效,@Output()
属性的名字必须遵循 inputChange
模式,其中 input
是相应 @Input()
属性的名字。例如,如果 @Input()
属性为 size
,则 @Output()
属性必须为 sizeChange
。
在这里我们不做过多的赘述,直接通过表单元素
中使用最多的[(ngModel)]
来认识双向绑定。
// 这个input其实是angular封装过的一个组件
// 这里用到双向绑定[(ngModel)]的前提是,需要在根组件中先将FormsModule导入进来
// import { FormsModule } from '@angular/forms';
<input [(ngModel)]="inputValue">
export class OutsideIntroductionComponent {
public inputValue = 5;
}
针对双向绑定要注意的点:
[(ngModel)]
语法只能设置数据绑定属性。
如果用户想在input框中的值更改的时候做一些额外的事情,应该怎么办? ===> 我们可以把这个组合模式拆解开来,具体如下:
<input [(ngModel)]="inputValue" (ngModelChange)="consoleValue($event)">
public consoleValue(value: string): void {
console.log(value);
}
组件的生命周期
angular组件的生命周期共有9个,但是在真正开发过程中用到只有3-4个,今天我们主要讲一下常用的生命周期吧~
[图片上传失败...(image-903c24-1637659554980)]
具体的生命周期请参考:生命周期详情
ts在angular组件中的使用
其实在上述所有的文件中,我们经常看到一些public,void这种关键字的声明,包括我们组件中用到的class,这些都是typeScript中的一些内容,不过我们常用的还是ts中一些类型的定义,在angular我们是要求所有的变量,函数都是要用类型定义的~
首先我们先来简单的认识一下ts
ts是js的
superType
,也就是说他更加的规范,而且可以在编译阶段
就能针对不规范的代码进行提示.
[图片上传失败...(image-4ec2c1-1637659554981)]
常见的一些类型定义:
以下为类型注解/类型定义
public a: number;
public b: boolean;
public c: string;
public d: any;
public e: number[] = [1, 2, 31];
public f: any[] = [1, true,'a', false];
//枚举定义
enum Color {
Red,
Green,
Blue,
}
public backgroundColor = Color;
// 如果使用backgroundColor.Red 返回的值就是0, 因为enum默认就是按照顺序排列里边的内容的
类型推断,像这些简单的类型,如果直接赋值的话,那么ts可以智能推断出这个什么类型
public curCount = 1;
public inputValue = 5;
[图片上传失败...(image-282c2d-1637659554981)]
在项目中引入ng-zorro组件库
- 在本项目下安装指定版本的组件库 npm install ng-zorro-antd@8.5.2 --save
npm install安装文件的具体位置分析
npm install xxx
: 安装项目到项目目录下,不会将模块依赖写入devDependencies
或dependencies
。npm install -g xxx
:-g
是将模块安装到全局,具体安装到磁盘哪个位置,要看npm cinfig prefix
的位置npm install -save xxx
:-save
的意思是将模块安装到项目目录下,并在package
文件的dependencies
节点写入依赖。当是在项目所必须的,则用这个命令npm install -save-dev xxx
:-save-dev
的意思是将模块安装到项目目录下,并在package
文件的devDependencies
节点写入依赖。当是开发过程中所依赖的需要用这个命令
- 在app.module中引入这个组件
[图片上传失败...(image-650ccc-1637659554981)]
- 在全局的style.scss文件中引入组件样式
/* You can add global styles to this file, and also import other style files */
@import "~ng-zorro-antd/style/index.min.css"; /* 引入基本样式 */
@import "~ng-zorro-antd/button/style/index.min.css"; /* 引入组件样式 */
- 在angular.json文件中引入svg和样式
{ "assets":[
...
{ "glob": "**/*",
"input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
"output": "/assets/" }
],
"styles": [
...
"node_modules/ng-zorro-antd/ng-zorro-antd.min.css"]
}
如想了解更加详细的引入UI组件,请参考快速安装ng-zorro-antd