Anguar17都更新了,还不试试?Angular保姆级入门教程(一)

What's new in Angular 17 Anguar17更新后小编看DevUI社区群里发起了激烈的讨论,相比其他版本评价只能用"除却巫山不是云"来形容,可能略有夸张但是整体看确实让开发者体验提升不少。DevUI也借此契机将Angular的入门教程同步更新,为了让更多小伙伴可以快速上车~

由于Angular的学习曲线初始阶段比较陡峭,一些重要概念不是很好理解入门,因此,计划开展Angular培训系列课程,由浅入深的帮助大家掌握Angular的基本概念、重要特性、核心思想,最终达到能正确灵活运用Angular、高效开发前端页面的目的。

感谢我们的DevUI社区贡献者smileHoo提供的优质文章!

本篇是该系列课程的第一期内容。

一、了解Angular

What is Angular

Angular 是一个应用设计框架与开发平台,旨在创建高效而精致的单页面应用

那么什么是单页面应用呢?

(单页面结构视图)

(多页面结构视图)

如图。单页面(SPA),顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。浏览器一开始会加载必需的公共资源,当进行跳转的时候,仅刷新局部资源;多页面(MPA)跳转刷新所有资源,每个公共资源(js、css等)需选择性重新加载。所以SPA会有较好的用户体验。

Why use Angular

优势:

  • 模板功能强大丰富,自带了丰富的angular指令。还可以根据需要自定义指令。
  • Angular CLI这个命令行工具,可快速轻松地搭建应用程序。使用简单的命令,可以启动项目、生成组件、库和服务、添加功能、测试和更新当前项目。
  • 是一个比较完善的前端框架,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等诸多功能。

学习难点:

  • 框架大,知识范围广,学习成本高,上手难度大。
  • 灵活度高,对程序员本身要求高。
  • 国内应用度较小,网上相关教程文档少,加大了学习难度。

二、搭建环境

前提条件

  • Nodejs 前往官方网站下载安装包
  • nvm Angular开发经常会涉及到不同环境需要的node版本不同,使用nvm管理node版本
  • Npm 包管理工具, 默认随 Node.js 一起安装

安装Angular CLI

java 复制代码
npm install -g @angular/cli   //安装命令
ng v  //安装成功则显示版本号

创建及运行项目

arduino 复制代码
ng new <app-name>  //创建工作区和初始应用
cd <app-name> 
ng serve --open  //运行应用

ng new后,cli将默认为项目生成一个初始页面,并在运行后于浏览器中展示以下页面:

三、项目结构

这是ng new后cli构建项目的时自动生成的文件,其中的大部分是一些配置相关的文件,初学者可以暂时搁置,值得注意的文件如下:

第一层目录

  • 【node_modules 】 第三方依赖包存放目录
  • 【src 】 应用源代码目录
  • 【package.json 】 这是一个标准的npm工具的配置文件,这个文件里面列出了该应用程序所使用的第三方依赖包。下载完成后会放在node_modules这个文件夹下。
  • 【package-lock.json 】运行npm install时生成的一份文件。用以记录当前状态下实际安装的各个npm 包的具体来源和版本号。

src目录

  • 【 app目录】包含应用的组件和模块,我们要写的代码都在这个目录。项目最初被构建的时候,就自动生成了根组件和根模块,像刚刚图片里渲染的网页内容,就是写在根组件里的。
  • 【 assets目录 】资源目录,存储静态资源的,比如图片,字体等。
  • 【 environments目录 】环境配置。包含为不同构建环境定义的相关的文件,Angular CLI 默认创建了两个环境,分别是开发环境和生产环境。
  • 【 index.html 】整个应用的根html,程序启动就是访问这个页面。
  • 【 main.ts 】整个项目的入口点,Angular通过这个文件来启动项目。
  • 【 polyfills.ts 】与浏览器兼容性相关的文件。
  • 【 styles.css 】主要是放一些全局的样式。
  • 【 test.ts 】单元测试的入口。

四、架构简介

这个架构图展现了 Angular 应用中的 8 个主要构造块:

  • 模块
  • 组件
  • 模板
  • 元数据
  • 数据绑定
  • 指令
  • 服务
  • 依赖注入

工作流程

首先左上角是模块,angular是模块化的,每个模块代表一组功能的集合,在模块当中可以有组件,服务等,当前模块还可能会依赖其他模块。

右下角是组件类,组件类是编写和界面直接相关的业务逻辑的地方,在组件类中可以编写事件处理函数,可以注入服务类的实例对象等等。

左下角就是服务,通常用来处理那些跟视图无关的事情,比如从服务器获取数据等等。

接下来我们看图中的中上部分,template,它表示的是组件的模板,在模板中可以编写html标签描述界面内容,也可以使用指令扩展元素的功能。可以通过数据绑定,实现模板和组件之间的通信,实现在模板中展示甚至修改组件类中的数据,以及进行一些事件处理。

组件类和模板是通过元数据关联起来,这里的元数据,就是传递给component装饰器装饰器的对象参数。

最右边是指令,指令是为模板元素添加额外行为的类,用于扩展DOM 元素或组件的功能。

五、模块

Angular 应用是模块化的,并且 Angular 有自己的模块系统,它被称为 Angular 模块或 NgModul。NgModule 把组件、指令和管道打包成内聚的功能块,每个模块聚焦于一个特性区域、业务领域、工作流或通用工具。

每个 Angular 应用都至少有一个模块,也就是根模块。通过引导这个根模块就可以启动整个项目。

对于那些只有少量组件的简单应用,根模块就是你所需的一切。随着应用的成长,你要把这个根模块重构成一些特性模块,它们代表一组密切相关的功能集。然后你再把这些模块导入到根模块中。

根模块文件

python 复制代码
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

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

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

首先import导入相关的依赖包,在 import 语句之后,是一个带有 @NgModule 装饰器的类。 @NgModule 装饰器表明 AppModule 是一个 NgModule 类。@NgModule 获取一个元数据对象,它会告诉 Angular 如何编译和启动本应用。

  • declarations: 当前模块拥有哪些组件、指令、管道等
  • imports:模块依赖项
  • providers:模块所能提供的服务
  • bootstrap:指定模块加载时,哪些组件需要一起加载

六、组件

组件是 Angular 的核心,其它的一切都是围绕组件设计的:服务为组件提供业务逻辑、模块对组件进行打包。

创建组件

在终端窗口中,导航到要放置你应用的目录。运行如下命令,其中 component-name 是新组件的名字。

xml 复制代码
ng generate component <component-name>

每个组件生成后,包括以下几个文件:

  • css样式表,用来编写组件的私有样式,也可以采用LESS、SCSS等其他的文件格式
  • HTML 模板,用于声明组件要渲染的内容
  • 组件类,用来编写和组件直接相关的界面逻辑,在其中要关联该组件的组件模板和组件样式。

组件类

kotlin 复制代码
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'my-app';
}

首先import导入相关的依赖包,在 import 语句之后,是一个带有@component 装饰器的类。 @component 装饰器表明 AppComponent 是一个组件类。@component 获取一个元数据对象,通过它将模板与组件类关联起来。

元数据中还传入了一个selector参数,这是一个简单的 CSS 选择器,当发现页面有相应选择器时,Angular 会把选择器所在位置替换为组件的模板,也就是说相应的标签实际上就代表了这个组件渲染的内容。

元数据下面就是组件类,组件类里面可以定义相关的属性和方法。

七、生命周期

生命周期函数通俗的讲就是组件或指令创建、更新、销毁的时候会触发的一系列的方法,也就是说只要达到了触发条件,他们就会自动执行。通过它们我们可以在适当的时候对组件或指令实例进行操作。

生命周期函数

  • ngOnChanges :当 Angular 设置或重新设置数据绑定的输入属性时响应。时间上位于OnInit之前。初始化和值发生变化时调用。
  • ngOnInit:用于初始化页面内容,只执行一次。时间上位于OnChanges之后。
  • ngDoCheck:每次变更检测时执行,并会在发生Angular自己本身不能捕获的变化时作出反应。紧跟在ngOnChanges 和 ngOnInit钩子之后执行。慎用。
  • ngAfterContentInit:当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。第一次 ngDoCheck 之后调用,只调用一次。
  • ngAfterContentChecked:每当 Angular 检查完被投影到组件或指令中的内容之后调用。ngAfterContentInit和每次 ngDoCheck之后调用。
  • ngAfterViewInit:当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用。第一次 ngAfterContentChecked之后调用,只调用一次。
  • afterViewChecked:每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。ngAfterViewInit和每次 ngAfterContentChecked 之后调用。
  • ngOnDestroy:每当 Angular 每次销毁指令/组件之前调用。

生命周期的整个流程

首先生命周期函数执行前,会执行构造方法,主要用于注入依赖关系。 接下来进入组件的生命周期,整体可以分为三个步骤,分别是初始化、变化检测、组件销毁。

step1 组件初始化

  • onChanges
  • 初始化
  • 做检查
  • 投影内容初始化
  • 投影内容变更检测
  • 视图初始化
  • 视图变更检测

step2 每次变化检测

  • onChanges
  • 做检查
  • 投影内容变更检测
  • 视图变更检测

step3 组件销毁

  • onDestroy

八、数据绑定

数据不是一成不变的,可能随着用户操作或者一些功能发生变化。数据绑定就是一种使得视图与组件之间的数据能够保持一致的技术。

通常将数据绑定分为两类:单向数据绑定和双向数据绑定。

从这个图片来看一下,要从组件类中输出数据到模板中的html代码中,可以使用字符串插值,或者属性绑定。另一个方向,如果用户单击模板上的按钮,需要在ts代码中触发一些事情,那么这就用到事件绑定,对于用户的行为作出响应。这两种情况是单向数据绑定

还有另一种形式,就是结合了这两个方向,能够同时对事件作出反应并输出一些东西,这就要用到双向绑定

插值绑定( model => view )

插值绑定属于单向数据绑定,值从组件类中流入模板,用于在模板中显示一些组件类中的数据。

语法:{{ data }}

用法:

  • 拼接字符串
  • 调用函数
  • 数学计算
  • 使用管道

示例如下:

html 复制代码
<!--插值绑定-->
<h1>插值绑定</h1>
<!--拼接字符串-->
<h3>Welcome to {{ title }}</h3>
<!--调用函数-->
<h3>{{ getTitle() }}</h3>
<!--数学计算-->
<h3>100x80 = {{100x80}}</h3>
<!--使用管道-->
<h3>pipe: {{title | uppercase}}</h3>

属性绑定( model => view )

属性绑定属于单向数据绑定,值从组件的属性流入目标元素的属性,使用属性绑定将组件属性值设置为元素属性。

语法:[property] = "data" 用法:

  • 普通属性
  • class属性
  • style属性

示例如下:

html 复制代码
<h1>属性绑定</h1>  
<!--普通属性-->  
<button [disabled]="isDisabled">I am disabled</button>  
<!--class属性-->  
<button class="btn btn-primary" [class.btnGreen]="classGreen" > class Binding </button>  
<button class="btn btn-primary" [class]="{'btnGreen': classGreen, 'btnBlue': classBlue}"> class Binding </button>  
<!--style属性-->  
<button [style.color]="styleChange ? 'blue': 'red'"> style Binding </button>  
<button [style]="{'color': 'red'}">style Binding</button>

事件绑定( view => model )

事件绑定属于单向数据绑定,是从视图到组件的数据改变,通常是由用户直接或间接触发的,比如用户的键盘输入、鼠标点击等动作。

语法:(event)="expression"

示例如下:

html 复制代码
<h1>事件绑定</h1>  
<button type="button" (click)="onClick()">点击按钮触发事件</button>

双向绑定 ( view <=> model )

双向数据绑定意味着数据是双向流动的:既可以从组件流向视图,又可以从视图流向组件。当我们在组件中修改了数据时,修改后的数据会自动显示在视图;当用户在视图修改了数据时,修改后的数据会自动同步到组件。这种绑定方式常见于表单中。

表单语法: [(ngModel)] = "data"

示例如下:

html 复制代码
<h1>双向绑定</h1>  
<h3>{{title}}</h3>  
<input [(ngModel)]="title">

九、组件通信

在Angular组件之间共享数据,有以下几种方式:

  1. 父组件至子组件: 通过 @Input 共享数据
  2. 子组件至父组件: 通过 @Output EventEmitter 共享数据
  3. 子组件至父组件: 通过 本地变量共享数据
  4. 子组件至父组件: 通过 @ViewChild 共享数据
  5. 不相关组件: 通过 service 共享数据,

除此之外,还有缓存、广播等。

父组件至子组件: 通过 @Input 共享数据

案例:在子组件中直接列出出父组件中定义的所有的英雄。

parent.component.ts:

ts 复制代码
@Component({
  selector:'parent',
  template: `
    <p>
      <child [heroList]="heroes"></child>
    </p>
  `,
  styles: [],
})

export class ParentComponent {
  heroes: string[] = ['Dr. Nice', 'Bombasto', 'Celeritas', 'Magneta', 'RubberMan'];
  constructor(){}
}

child.component.ts:

ts 复制代码
@Component({
  selector:'child',
  template: `
    <p>父组件至子组件</p>
    <ul>
      <li *ngFor="let hero of heroList">{{hero}}</li>
    </ul>
  `,
  styles: [],
})

export class ChildComponent {
@Input() heroList: any;
  constructor(){}
}

页面效果如下:

子组件至父组件: 通过 @Output EventEmitter 共享数据

案例:子组件提供两个按钮进行投票,而父组件中需要实时显示投票结果。

parent.component.ts:

ts 复制代码
@Component({
  selector:'parent',
  template: `
    <div>
      agreed: {{agreed}}  disagreed: {{disAgreed}}
    </div>
    <child (vote)="onVote($event)"></child>
  `,
  styles: [],
})

export class ParentComponent {
@Output() vote = new EventEmitter<boolean>();
  agreed: number = 0;
  disAgreed: number = 0;

  constructor(){}

  onVote(agree: boolean) {
    agree ? this.agreed++ : this.disAgreed++;
  }
}

child.component.ts:

ts 复制代码
import {Component, EventEmitter, Output} from "@angular/core";

@Component({
  selector:'child',
  template: `
    <p>子组件至父组件</p>
    <div>
      <button (click)="takeVote(true)">Agree</button>
    </div>
    <div>
      <button (click)="takeVote(false)">Disagree</button>
    </div>
  `,
  styles: [],
})

export class ChildComponent {
@Output() vote = new EventEmitter<boolean>();

takeVote(agree: boolean) {
  this.vote.emit(agree);
}
  constructor(){}
}

页面效果如下:

子组件至父组件: 通过本地变量共享数据

案例:父组件里提供样式,但调用子组件方法,点击后设置地址由"上海"变为"北京"

parent.component.ts:

ts 复制代码
@Component({
  selector:'parent',
  template: `
    <child #childComponent></child>
    <div>{{childComponent.address}}</div>
    <button (click)="childComponent.setAddress('Beijing')">click</button>
  `,
  styles: [],
})

export class ParentComponent {
  constructor(){}
}

child.component.ts:

ts 复制代码
@Component({
  selector:'child',
  template: ` `,
  styles: [],
})

export class ChildComponent {

  address = 'Shanghai';
  
  setAddress(address: string): void {
    this.address = address;
  }

  constructor() { }
}

页面效果如下:

局限性:父组件-子组件的连接必须全部在父组件的模板中进行。如果父组件的需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。

子组件至父组件: 通过 @ViewChild 共享数据

父组件同时可以访问子组件中非私有的属性和方法。

parent.component.ts:

ts 复制代码
@ViewChild(ChildComponent) private childComponent!: ChildComponent;

不相关组件: 通过 service 共享数据

案例:点击side-bar-toggle控制side-bar的显示或隐藏。

side-bar-toggle.component.ts:

ts 复制代码
@Component({
  selector: 'app-side-bar-toggle',
  templateUrl: './side-bar-toggle.component.html',
  styleUrls: ['./side-bar-toggle.component.css']
})
export class SideBarToggleComponent {

  constructor(
    private sideBarService: SideBarService
  ) { }

  @HostListener('click')
  click() {
    this.sideBarService.toggle();
  }

}

side-bar.component.ts:

ts 复制代码
@Component({
  selector: 'app-side-bar',
  templateUrl: './side-bar.component.html',
  styleUrls: ['./side-bar.component.css']
})
export class SideBarComponent {

  @HostBinding('class.is-open')
  isOpen = false;

  constructor(
    private sideBarService: SideBarService
  ) { }

  ngOnInit() {
    this.sideBarService.change.subscribe(isOpen => {
      this.isOpen = isOpen;
    });
  }
}

side-bar.service.ts:

ts 复制代码
@Injectable()
export class SideBarService {

  isOpen = false;

  @Output() change: EventEmitter<boolean> = new EventEmitter();

  toggle() {
    this.isOpen = !this.isOpen;
    this.change.emit(this.isOpen);
  }
}

在这个案例中通过toggle的这个组件,实现侧边栏的开关。这里涉及到一个参数isopen的变化,点击toggle这个组件,会改变isopen这个变量的值,而side-bar这个组件,也需要这个变量改变样式,达到一个侧边栏开关的效果。

具体的做法是,将这个变量和改变变量值的方法都放在service里面,在toggle组件类的构造方法里注入这个服务,当监测到点击后,会发送事件流,side-bar这个组件类同样注入这个服务,订阅事件,获取到当前的变量值,从而实现了不相关组件的数据共享。

参考文献

🔥 加入我们

DevUI是面向企业中后台产品的开源前端解决方案,其设计价值观基于高效、开放、可信、乐趣四种自然与人文相结合的理念,旨在为设计师、前端开发者提供标准的设计体系,并满足各类落地场景,是一款企业级开箱即用的产品。

如果你今天刚刚加入我们,可以先看看官网上的示例组件,你可以在左侧导航栏中切换想要查看的组件,然后通过右侧的快速前往在不同Demo之间切换。

如果你准备添加 Vue DevUI,请前往快速开始文档,只需要几行代码。

如果你对我们的开源项目感兴趣,并希望参与共建,欢迎加入我们的开源社区,关注DevUI微信公众号:DevUI 。

文 / DevUI社区贡献者 张笑

相关推荐
fishmemory7sec5 分钟前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec8 分钟前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
JUNAI_Strive_ving1 小时前
番茄小说逆向爬取
javascript·python
看到请催我学习1 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins35202 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky2 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~2 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒2 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
Q_w77423 小时前
一个真实可用的登录界面!
javascript·mysql·php·html5·网站登录