Angular入门初探

195 阅读7分钟

Angular入门

本人对前端技术并不精通,所以文章主要以笔记的形式记录。

环境搭建

首先安装Node,上官网下载:nodejs.org/zh-cn/

然后安装angular:

npm install -g @angular/cli // 最新版本

npm install -g @angular/cli@x.x.x // 指定版本

创建angular项目:

ng new project_name // 创建angular项目

cd project_name // 进入根目录

ng serve // 启动服务

安装的时候根据需要选择,我选择了CSS的。

在浏览器输入:http://localhost:4200 即可看到页面

目录介绍

- node_modules	# 项目依赖包
- src	# 代码模块
	- app	# 业务代码
		app-routing.module.ts
		app.component.css
		app.component.html
		app.component.ts
		app.module.ts
	- assets	# 静态资源
	- environments	# 项目环境配置
	favicon.ico	# 浏览器tab标签栏的图标
	index.html	# 单页面应用的入口
	main.ts	# 项目入口,是项目启动的地方
	style.css	# 全局公共样式
package-lock.json # npm包依赖的配置文件
package.json	# 同上
其他没讲的暂时用不上

根模块

首先来看看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';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • declarations:用来存放组件、指令

组件

组件是Angular应用的主要构造块,每个组件包括如下部分:

  • 一个HTML模板,用于声明页面要渲染的内容
  • 一个用于定义行为的Typescript类
  • 一个CSS选择器,用于定义组件在模板中的使用方式
  • (可选)要应用在模板上的CSS样式

自动创建组件

使用Angular Cli创建组件十分简单:

  1. 在终端中,进入到应用目录,我放在/src/app里

  2. 运行:

    ng generate component hello-world

默认情况下,该命令会在app目录下创建以下内容:

  • 以该组件命名的文件夹
  • 组件文件:hello-world.component.ts
  • 模板文件:hello-world.component.html
  • CSS文件:hello-world.component.css
  • 测试文件:hello-world.component.spec.ts

然后我们进入app.module.ts文件,可以看到Angular Cli创建组件的时候会自动帮我们在该配置文件中导入:

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

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HelloWorldComponent } from './hello-world/hello-world.component'; // 1. 导入hello-world组件

@NgModule({
  declarations: [
    AppComponent,
    HelloWorldComponent	// 2. 声明hello-world组件
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

手动创建组件

手动创建组件的话,创建上面说明的文件,然后手动在app.module.ts中导入(import)和声明(declarations)即可。

使用组件

编辑hello-world模板的内容:

hello-world.component.html

<p>hello-world works!</p>

启动服务后默认显示的内容是app.component.html,现在将这里面的内容清空掉,改成:

<app-hello-world></app-hello-world>

进入网页,可以看到刚刚的修改生效了,页面显示的是hello-world模板的内容。

image-20220112151551257

模板

插值和表达式,在hello-world.component.html中可以通过插值和表达式来显示值:

hello-world.component.ts组件中定义一个变量:

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

@Component({
  selector: 'app-hello-world',
  templateUrl: './hello-world.component.html',
  styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
  public title = "这里是一段插值"
}

然后在hello-world.component.html模板中使用:

<p>hello-world works!</p>
<p>{{ title }}</p>

显示效果:

image-20220112151620569

除了导入文件,可以在当前ts文件内写一些简单的模板内容或者CSS样式:

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

@Component({
  selector: 'app-hello-world',
  template: '<h1>{{title}}</h1>',
  styles: ['h1 { color: red;}']
})
export class HelloWorldComponent implements OnInit {}

模板表达式

模板表达式会产生一个值并绑定给目标的某个属性:

<p>The sum of 1 + 1 is {{1 + 1}}.</p>

也可以调用宿主组件的方法:

<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>

表达式也可以引用模板中的属性

<h4>{{ h4Value }}</h4>
<img [src]="imgUrl">

命名冲突

在属性命名中要注意防止冲突,避免不必要的麻烦。示例:

hello-world.component.ts

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

@Component({
  selector: 'app-hello-world',
  templateUrl: './hello-world.component.html',
  styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
  public customers = [{value: 'Ebony'}, {value: 'Chiho'}]
  public customer = 'Padma'
}

hello-world.component.html

<h4>{{ h4Value }}</h4>
<img [src]="imgUrl">

<div>
    <h1>Hello, {{ customer }}</h1>
    <ul>
        <li *ngFor="let customer of customers">{{ customer.value }}</li>
    </ul>
</div>

ngFor中的customer处于的上下文中,所以指向的是数组中的customer,即Ebony 和 Chiho。经过测试,当数组中的customer修改为customer2,则插值中的customer会指向customer属性,即Padma,所以customer.value会报错。

模板语句

在模板中调用组件的方法

hello-world.component.ts

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

@Component({
  selector: 'app-hello-world',
  templateUrl: './hello-world.component.html',
  styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
  public deleteHero(): void {
    console.log("deleteHero");
  }
}

hello-world.component.html

<button (click)="deleteHero()">Delete Hero</button>

点击按钮时,Angular就会调用组件中的deleteHero方法。

管道

管道用来对字符串、货币金额、日期和其他显示数据进行转换和格式化。

示例:使用管道将日期进行格式化

hello-world.component.ts

public birthday = new Date(1988, 3, 15);

hello-world.component.html

<p>日期从{{ birthday }}转换成{{ birthday | date}}</p>
<p>日期从{{ birthday }}转换成{{ birthday | date: 'MM/dd/yy' }}</p>

显示如下:

image-20220112160514614

属性绑定和传值

属性绑定

要绑定到元素的属性,需要将其括在方括号内:[],该括号会将属性标为目标属性,目标属性就是要对其进行赋值的DOM属性。例如下面代码中的目标属性是img元素的src属性:

<img [src]="imgUrl">

传值

传值一般有三种情况:

  • 父组件向子组件传值
  • 子组件向父组件传值
  • 双向传值

使用**@Input()@Output()**,@Input()允许父组件更新子组件中的数据,@Output()允许子组件向父组件发送数据。

**注意:**在这个部分里,我们会用到Input,Output和EventEmitter模块,所以需要在import里面导入,这一点别忘了。

父组件向子组件传值

因为是在app.component.html中引用hello-world模板,所以在这里app是父组件hello-world是子组件

首先是子组件:

hello-world.component.ts

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

@Component({
  selector: 'app-hello-world',
  templateUrl: './hello-world.component.html',
  styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
  // 子控件的值
  @Input() public sonNumber = 1;
    
  // 随机改变值
  public changeSonNumber() {
    this.sonNumber = Math.random();
  }
}

hello-world.component.html

<div>子组件中的值:{{ sonNumber }}</div>
<button (click)="changeSonNumber()">点击改变子组件的值</button>

然后是父组件:

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public defaultNumber = 0;

  public changeDefaultNumber() {
    this.defaultNumber = Math.random();
  }
}

app.component.html

<app-hello-world [sonNumber]="defaultNumber"></app-hello-world>

<div>父组件中的值:{{ defaultNumber }}</div>
<button (click)="changeDefaultNumber()">点击改变父组件的值</button>

显示效果:

image-20220112170020183

此时点击按钮《点击改变子组件的值》,只有子组件在变化,

点击按钮《点击改变父组件的值》,父组件和子组件都会变化。

子组件向父组件传值

首先是子组件:

hello-world.component.ts

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

@Component({
  selector: 'app-hello-world',
  templateUrl: './hello-world.component.html',
  styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
  // 子控件的值
  @Input() public sonNumber = 1;
  // 命名规范要注意是:xxxChange
  // 因为子控件的值是sonNumber,所以这里就要命名成:sonNumberChange
  @Output() public sonNumberChange = new EventEmitter<number>();

  // 随机改变值
  public changeSonNumber() {
    this.sonNumber = Math.random();
    // 发送值改变的信号
    this.sonNumberChange.emit(this.sonNumber)
  }
}

hello-world.component.html

和前面一样,没有改变,为了方便阅读也贴出来

<div>子组件中的值:{{ sonNumber }}</div>
<button (click)="changeSonNumber()">点击改变子组件的值</button>

然后是父组件:

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public defaultNumber = 0;

  public changeDefaultNumber() {
    this.defaultNumber = Math.random();
  }
    
  // 接收到子组件传来的值
  public handleSonNumberChange(value: any) {
    this.defaultNumber = value
  }
}

app.component.html

<app-hello-world [sonNumber]="defaultNumber" (sonNumberChange)="handleSonNumberChange($event)"></app-hello-world>

<div>父组件中的值:{{ defaultNumber }}</div>
<button (click)="changeDefaultNumber()">点击改变父组件的值</button>

显示效果一样,但是:

点击按钮《点击改变子组件的值》,父组件和子组件都会变化,

点击按钮《点击改变父组件的值》,父组件和子组件都会变化。

由此就实现了子组件向父组件传值的目的。

双向绑定

在上面我们可以发现,子组件向父组件传值比较麻烦,Angular提供了一个更加方便的双向绑定功能,语法是方括号和圆括号的组合:[()]

首先是子组件:

hello-world.component.ts

我们用一个新的变量sonSize来做

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

@Component({
  selector: 'app-hello-world',
  templateUrl: './hello-world.component.html',
  styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
  // 双向绑定
  @Input() public sonSize = 1;
  @Output() public sonSizeChange = new EventEmitter<number>();

  // 随机改变值
  public changeSonSize() {
    this.sonSize = Math.random();
    // 发送值改变的信号
    this.sonSizeChange.emit(this.sonSize);
  }
}

hello-world.component.html

和前面一样,没有改变,为了方便阅读也贴出来

<div>点击改变子组件中sonSize的值:{{ sonSize }}</div>
<button (click)="changeSonSize()">点击改变子组件中sonSize的值</button>

然后是父组件:

app.component.ts

父组件

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public defaultSize = 0;
  public changeDefaultSize() {
    this.defaultSize = Math.random();
  }
}

app.component.html

<app-hello-world [(sonSize)]="defaultSize"></app-hello-world>
<div>父组件中defaultSize的值:{{ defaultSize }}</div>
<button (click)="changeDefaultSize()">点击改变父组件defaultSize的值</button>

显示效果:

image-20220112173531932

双向绑定能够实现子组件和父组件之间的双向通讯。

双向绑定对比上面,在父组件中省略了一些代码,通过[()]语法将父组件的属性和事件一并绑定了。

生命周期

列举一些常用的:

app.component.ts

import {
  AfterViewInit,
  Component,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  SimpleChanges,
} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent
  implements OnChanges, OnInit, AfterViewInit, OnDestroy
{
  public ngOnChanges(changes: SimpleChanges): void {
    // @Input()属性值变更的时候触发此函数
    console.log('ngOnChanges');
  }

  public ngOnInit(): void {
    // 组件初始化的时候调用一次
    console.log('ngOnInit');
  }

  public ngAfterViewInit(): void {
    // 试图更新之后或者说DOM元素渲染之后调用一次
    console.log('ngAfterViewInit');
  }

  public ngOnDestroy(): void {
    // 组件销毁的时候调用,在这里清除定时器或者异步
    console.log('ngOnDestroy');
  }
}

参考资料

Angular 中文文档

Angular基础教程