Angular基础教程

·  阅读 2078

前言

Angular相比于原生JS有什么优势? 什么是单页面应用(SPA)呢?

优势:

  1. 项目架构很清晰
  2. 代码的复用性强
  3. 代码的可测试强,可以添加e2e测试,单元测试

单页面应用相比与请求多个html页面的优势

  1. 用户体验好,一次性加载所有的js和css,内容的改变不需要重新加载整个页面
  2. 前后端分离

Angular开发环境搭建

在原生js里一般通过<Script>标签引入一些第三方库,随着框架的的普及,之前引入的方式略low,工作中也很少用到这种方式,在学习Angular时候,我们采用脚手架的形式进行安装。这就需要我们安装Node.js,用里边的npm来进行安装。

node安装

nodejs.cn/

可以通过 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 便可看到相关页面

图片 1.png

输入命令之后,按照接下来的提示,进行选择,第一个是否选择在严格模式下创建这个项目;第二是否在创建项目的时候帮你把router文件一并创建出来;第三是用那种css类型进行编码。可以按照上图的选择情况进行创建。

项目目录介绍

屏幕快照 2021-11-23 上午10.09.39.png

组件介绍

组件的概念

介绍组件之前,我们先来介绍一下组件模块之前的关系,框架都是推崇组件化开发的,页面上所有的元素都可以看做一个组件,具体页面中组件划分的粒度是多大,还是按照自己的个人开发习惯来进行。在Angular中我们知道了app模块是我们的根模块,他管理整个单页面应用中涉及到的其他模块组件。我们来熟悉一下app.module.ts这个根模块文件。

屏幕快照 2021-11-23 上午11.07.11.png

@NgModule是 Angular中的装饰器,是需要从'@angular/core'导入进来
作用: 帮助开发者组织业务代码,开发者可以利用 NgModule 把关系比较紧密的组件组织到一起,他不仅可以控制组件,还可以控制指令,管道等(本期我们只要先学会组件,指令和管道后期会讲到)

  1. declarations:用来放组件、指令、管道的声明, 组件、指令、管道都必须属于一个模块,而且只能属于一个模块。

  2. imports:用来导入外部模块而非组件。

  3. exports: 我们这个模块需要导出的一些组件,指令,模块等; 如果别的模块导入了我们这个模块,那么别的模块就可以直接使用我们在这里导出的组件,指令,模块等.

  4. providers:需要使用的Service都放在这里。指定应用程序的根级别需要使用的service

  5. bootstrap:定义启动组件。你可能注意到了这个配置项是一个数组,也就是说可以指定这个组件作为启动点。一般这里不需要做改动。

WechatIMG4.jpeg

由上述可见: angular中单页面是有一个根模块的,这个模块中包含了很多组件,也就是说多个组件组成了模块。

Hello World以及组件讲解

首先我们先将app.component.html中的内容全部清空,为我们接下来的组件学习做准备。

1. 在app文件夹下,新建一个组件文件,文件名为hello-world.component.ts

屏幕快照 2021-11-23 下午12.05.00.png
从上图片中,我们可以得知组件的组成有以下三部分组成:

  • (类名的命名是根据组件的文件名来决定的,首字母大写的驼峰命名方式)

  • 装饰器@component, 需要从'@angular/core'中导入,其作用是把某个类标记为Angular组件,并为他配置一些元数据,目前这里只涉及到3个元数据,其中selector也称作是选择器,我们可以把他理解成我们自定义组件的名字,一般他的命名也是app-组件文件的名字,更多的元数据应用可以参照官网。

  • HTML模板,就是template。

2. 上边已经提到,组件必须在模块中声明才可以正常使用,所以我在根模块声明一下

屏幕快照 2021-11-23 下午12.05.49.png
3. 我们将定义好的组件在app的html中引用一下

屏幕快照 2021-11-23 下午12.05.39.png

这个时候页面上已经可以完美的展现出 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 应用程序中的元素添加额外行为的类。 这里主要介绍一下属性型指令结构型指令

属性型指令

  1. 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;
}

  1. 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'
  };
}


  1. NgModel: 将数据的双向绑定添加到HTML表单元素上

结构型指令

  1. 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>
 
 // 其实除了常用的index,ngFor还提供了其他的一些局部变量,我们可以通过给局部变量起别名从而来使用它
 <ul>
    <li *ngFor="let subject of studySubjects;
                let i  = index;
                let counts = count;
                first as isFirst;
                even as isEven;
                odd as isOdd">
    {{ i+1 }} - {{ subject }}
    <span>总条数{{counts}}</span>
    <span *ngIf="isOdd">是奇数</span>
    <span *ngIf="isEven">是偶数</span>
    </li>
 </ul>
//常用变量如下: (index: number; count: nuber; first: boolean; last: boolean; last: boolean; even: boolean,odd: boolean)

循环结果如下所示:

屏幕快照 2021-11-26 下午12.20.43.png
2. 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,那么上述的列表就不会展示
}

  1. 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标签的内容';
}

当绑定的数据类型是字符串的时候,我们同样可以使用插值表达式来代替,比如<img [src]="itemImageUrl">就等价于<img src={{itemImageUrl}}>

绑定到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中是怎么样实现事件绑定的吧~

  1. 按钮的事件绑定
//我们希望通过点击按钮实现一个累加器
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++;
  }
}

  1. 事件绑定中的$event对象

在编写响应事件时,是可以接受一个$event参数的,这个参数就是关于响应事件的一些内容.

<button (click)="addCount($event)">当前数值加1</button>

 public addCount(event): void {
    console.log(event);
    this.curNumber++;
  }
  1. 事件的传参
<button (click)="addCount($event, 5)">当前数值加1</button>

 public addCount(event, paramsNumber: number): void {
     this.curNumber = this.curNumber + paramsNumber; // 可以实现每次加5
  }
  1. 一个按钮同时调用两个方法
// 通过分号去分割两个方法
<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创建如下:

屏幕快照 2021-11-23 下午3.04.08.png

组件的父子组件传值

@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框中的值更改的时候做一些额外的事情,应该怎么办? ===> 我们可以把这个组合模式拆解开来,具体如下:

// html
<input [(ngModel)]="inputValue" (ngModelChange)="consoleValue($event)">

// ts
  public consoleValue(value: string): void {
    console.log(value);
  }

组件的生命周期

Angular中组件的生命周期共有9个,但是在真正开发过程中用到只有3-4个,今天我们主要讲一下常用的生命周期吧~

屏幕快照 2021-11-23 下午4.16.49.png

具体的生命周期请参考:生命周期详情

TS在Angular组件中的使用

其实在上述所有的文件中,我们经常看到一些public,void这种关键字的声明,包括我们组件中用到的class,这些都是TypeScript中的一些内容,不过我们常用的还是TS中一些类型的定义,在Angular我们是要求所有的变量,函数都是要用类型定义的~
首先我们先来简单的认识一下ts

TS是JS的superType,也就是说他更加的规范,而且可以在编译阶段就能针对不规范的代码进行提示.

屏幕快照 2021-11-16 下午2.52.00.png
常见的一些类型定义:

 // 以下为类型注解/类型定义
  
  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];
  
  // interface的使用
  interface IPerson {
      age: number;
      gender: string;
      job: string;
      address: string;
      money: number;
 }

interface IDeveloper extends IPerson { // interface可以实现继承
  code: boolean;
}
  
  //枚举定义
  enum Color {
   Red,
   Green, 
   Blue,
 }
  public backgroundColor = Color;
  // 如果使用backgroundColor.Red 返回的值就是0, 因为enum默认就是按照顺序排列里边的内容的
// 类型推断,像这些简单的类型,如果直接赋值的话,那么ts可以智能推断出这个什么类型
public curCount = 1;
public inputValue = 5;
【暂时先了解】
类型断言: 就是手动去指定一个值的类型
用法 ①: 值 as 类型  ②:<类型>值

屏幕快照 2021-11-23 下午4.36.30.png

在项目中引入ng-zorro组件库

  1. 在本项目下安装指定版本的组件库 npm install ng-zorro-antd@8.5.2 --save

npm install安装文件的具体位置分析

  • npm install xxx: 在node5之前使用是表示:安装项目到项目目录下,不会将模块依赖写入devDependenciesdependencies; node5之后使用其作用和npm install -save xxx一样。

  • npm install -g xxx-g是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm cinfig prefix的位置

  • npm install -save xxx-save的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。当是在项目所必须的,则用这个命令

  • npm install -save-dev xxx-save-dev的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。当是开发过程中所依赖的需要用这个命令

  1. 在app.module中引入这个组件

屏幕快照 2021-11-23 下午4.57.55.png 3. 在全局的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"; /* 引入组件样式 */
  1. 在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

后续进阶教程请参阅 Angular进阶教程1 - (路由 + 表单)

分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改