MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Angular介绍及入门指南

2023-02-183.9k 阅读

一、Angular 基础概念

Angular 是一款由 Google 维护的开源 JavaScript 框架,用于构建高效、复杂且可维护的单页应用程序(SPA)。它基于 TypeScript 构建,为开发者提供了一套完整的解决方案,涵盖了从视图到数据模型,再到应用架构的各个方面。

1.1 框架核心特性

  • 组件化架构:Angular 应用由一系列组件构成。每个组件都是一个独立的、可复用的代码块,包含自己的 HTML 模板(用于定义视图)、CSS 样式(用于定义外观)以及 TypeScript 类(用于处理业务逻辑)。例如,我们可以创建一个 HeaderComponent 用于显示应用的头部,一个 ProductListComponent 用于展示商品列表。
// 简单的组件示例
import { Component } from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent {
  title = 'My Angular App';
}
  • 双向数据绑定:这是 Angular 的一个强大功能。它允许在模型(数据)和视图之间自动同步变化。也就是说,当模型数据发生改变时,视图会自动更新;反之,当用户在视图中进行操作(如输入文本、点击按钮等)导致视图变化时,模型数据也会相应更新。例如,在一个输入框中输入内容,绑定的变量值会实时更新,同时如果在代码中改变该变量值,输入框中的内容也会立即改变。
<!-- 双向数据绑定示例 -->
<input [(ngModel)]="userName" />
<p>You entered: {{userName}}</p>
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-input',
  templateUrl: './user - input.component.html'
})
export class UserInputComponent {
  userName = '';
}
  • 依赖注入:Angular 的依赖注入系统允许开发者将组件所需的服务或对象“注入”到组件中,而不是让组件自己去创建这些依赖。这使得代码更加模块化、可测试,并且易于维护。例如,一个 UserService 用于处理用户相关的业务逻辑,其他组件如果需要使用用户相关功能,只需要通过依赖注入获取 UserService 的实例,而不需要关心它是如何创建的。
// 定义一个服务
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  getUserName() {
    return 'John Doe';
  }
}

// 在组件中使用依赖注入
import { Component } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - user - info',
  templateUrl: './user - info.component.html'
})
export class UserInfoComponent {
  constructor(private userService: UserService) {}

  ngOnInit() {
    console.log(this.userService.getUserName());
  }
}

二、环境搭建

在开始 Angular 项目开发之前,需要搭建合适的开发环境。这主要包括安装 Node.js、npm(Node 包管理器)以及 Angular CLI(命令行界面工具)。

2.1 安装 Node.js 和 npm

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,npm 是 Node.js 的默认包管理器。可以从 Node.js 官方网站(https://nodejs.org/)下载并安装最新版本的 Node.js。安装过程中,npm 会自动一并安装。安装完成后,可以在命令行中输入以下命令验证安装是否成功:

node -v
npm -v

这两个命令分别会输出版本号,如果能正常显示版本号,则说明安装成功。

2.2 安装 Angular CLI

Angular CLI 是 Angular 官方提供的命令行工具,用于初始化项目、生成组件、服务等各种代码结构,以及进行项目的开发、测试和部署等操作。使用 npm 安装 Angular CLI,在命令行中执行以下命令:

npm install -g @angular/cli

-g 选项表示全局安装,安装完成后,可以通过以下命令验证 Angular CLI 是否安装成功:

ng version

如果能显示 Angular CLI 的版本号,则安装成功。

三、创建第一个 Angular 项目

使用 Angular CLI 创建项目非常简单。在命令行中,进入到你想要创建项目的目录,然后执行以下命令:

ng new my - first - app

这里 my - first - app 是项目的名称,可以根据实际需求进行修改。执行该命令后,Angular CLI 会自动创建项目的基本结构,并安装项目所需的依赖包。这个过程可能需要一些时间,取决于网络速度。

项目创建完成后,进入项目目录:

cd my - first - app

然后可以通过以下命令启动项目的开发服务器:

ng serve --open

--open 选项会自动在浏览器中打开项目,默认访问地址是 http://localhost:4200。此时,你会看到一个默认的 Angular 欢迎页面,说明项目已经成功启动。

四、Angular 组件深入

如前文所述,组件是 Angular 应用的核心构建块。下面深入探讨组件的一些重要概念和特性。

4.1 组件的生命周期

Angular 组件具有自己的生命周期,从组件创建、初始化、更新到销毁,每个阶段都提供了相应的生命周期钩子函数,开发者可以在这些钩子函数中执行特定的操作。

  • ngOnInit:在组件初始化完成后调用,通常用于进行数据获取、初始化变量等操作。例如,在一个 ProductListComponent 中,可以在 ngOnInit 中从服务器获取商品列表数据。
import { Component, OnInit } from '@angular/core';
import { ProductService } from './product.service';

@Component({
  selector: 'app - product - list',
  templateUrl: './product - list.component.html'
})
export class ProductListComponent implements OnInit {
  products = [];

  constructor(private productService: ProductService) {}

  ngOnInit() {
    this.products = this.productService.getProducts();
  }
}
  • ngOnChanges:当组件的输入属性(@Input() 装饰的属性)发生变化时调用。比如,一个 ProductDetailComponent 接收一个 productId 作为输入属性,当 productId 改变时,可以在 ngOnChanges 中重新获取对应的商品详细信息。
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ProductService } from './product.service';

@Component({
  selector: 'app - product - detail',
  templateUrl: './product - detail.component.html'
})
export class ProductDetailComponent implements OnChanges {
  @Input() productId;
  product;

  constructor(private productService: ProductService) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes.productId) {
      this.product = this.productService.getProductById(this.productId);
    }
  }
}
  • ngOnDestroy:在组件销毁时调用,常用于清理资源,如取消订阅 Observable 等。例如,如果组件中订阅了一个事件流,在组件销毁时需要取消订阅,以避免内存泄漏。
import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { ProductService } from './product.service';

@Component({
  selector: 'app - product - updates',
  templateUrl: './product - updates.component.html'
})
export class ProductUpdatesComponent implements OnDestroy {
  productUpdateSubscription: Subscription;

  constructor(private productService: ProductService) {
    this.productUpdateSubscription = this.productService.getProductUpdates().subscribe(data => {
      console.log('Product updated:', data);
    });
  }

  ngOnDestroy() {
    this.productUpdateSubscription.unsubscribe();
  }
}

4.2 组件间通信

在大型 Angular 应用中,组件之间通常需要进行通信。Angular 提供了多种方式来实现组件间通信。

  • 父子组件通信:父组件向子组件传递数据通过 @Input() 装饰器。子组件可以通过 @Output() 装饰器和 EventEmitter 来向父组件发射事件。例如,有一个 ParentComponent 和一个 ChildComponent,父组件要传递一个 message 给子组件,子组件在按钮点击时向父组件发射一个事件。
<!-- ParentComponent.html -->
<app - child - component [message]="parentMessage" (childEvent)="onChildEvent($event)"></app - child - component>
// ParentComponent.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app - parent - component',
  templateUrl: './parent - component.html'
})
export class ParentComponent {
  parentMessage = 'Hello from parent';

  onChildEvent(eventData) {
    console.log('Received from child:', eventData);
  }
}
<!-- ChildComponent.html -->
<p>{{message}}</p>
<button (click)="sendEvent()">Send Event</button>
// ChildComponent.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app - child - component',
  templateUrl: './child - component.html'
})
export class ChildComponent {
  @Input() message;
  @Output() childEvent = new EventEmitter();

  sendEvent() {
    this.childEvent.emit('Hello from child');
  }
}
  • 非父子组件通信:对于非父子关系的组件通信,可以使用服务(Service)来实现。例如,创建一个 SharedService,两个非父子组件都依赖这个服务,通过服务中的属性或方法来共享数据或传递事件。
// SharedService.ts
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SharedService {
  private messageSource = new Subject<string>();
  message$ = this.messageSource.asObservable();

  sendMessage(message: string) {
    this.messageSource.next(message);
  }
}
// ComponentA.ts
import { Component } from '@angular/core';
import { SharedService } from './shared.service';

@Component({
  selector: 'app - component - a',
  templateUrl: './component - a.html'
})
export class ComponentA {
  constructor(private sharedService: SharedService) {}

  sendSharedMessage() {
    this.sharedService.sendMessage('Message from ComponentA');
  }
}
// ComponentB.ts
import { Component, OnInit } from '@angular/core';
import { SharedService } from './shared.service';

@Component({
  selector: 'app - component - b',
  templateUrl: './component - b.html'
})
export class ComponentB implements OnInit {
  receivedMessage;

  constructor(private sharedService: SharedService) {}

  ngOnInit() {
    this.sharedService.message$.subscribe(message => {
      this.receivedMessage = message;
    });
  }
}

五、模板语法

Angular 的模板语法是用于定义组件视图的一种强大语言,它结合了 HTML 和 Angular 特定的指令、插值等语法。

5.1 插值

插值是在模板中显示数据的一种简单方式,使用双花括号 {{}}。例如,在组件类中有一个变量 name,可以在模板中通过 {{name}} 来显示其值。

<p>My name is {{name}}</p>
import { Component } from '@angular/core';

@Component({
  selector: 'app - name - display',
  templateUrl: './name - display.component.html'
})
export class NameDisplayComponent {
  name = 'Alice';
}

5.2 指令

指令是 Angular 模板语法的核心部分,分为结构指令和属性指令。

  • 结构指令:用于改变 DOM 的结构。例如,*ngIf 用于根据条件决定是否渲染一个元素,*ngFor 用于遍历数组并为每个元素创建一个模板实例。
<!-- *ngIf 示例 -->
<div *ngIf="isLoggedIn">
  <p>Welcome, user!</p>
</div>

<!-- *ngFor 示例 -->
<ul>
  <li *ngFor="let product of products">{{product.name}}</li>
</ul>
import { Component } from '@angular/core';

@Component({
  selector: 'app - conditional - display',
  templateUrl: './conditional - display.component.html'
})
export class ConditionalDisplayComponent {
  isLoggedIn = true;
  products = [
    { name: 'Product 1' },
    { name: 'Product 2' }
  ];
}
  • 属性指令:用于改变元素的外观或行为。例如,ngModel 用于实现双向数据绑定,ngClass 用于动态添加或移除 CSS 类。
<!-- ngModel 示例 -->
<input [(ngModel)]="userEmail" />

<!-- ngClass 示例 -->
<div [ngClass]="{ 'active': isActive }">Content</div>
import { Component } from '@angular/core';

@Component({
  selector: 'app - attribute - directives',
  templateUrl: './attribute - directives.component.html'
})
export class AttributeDirectivesComponent {
  userEmail = '';
  isActive = true;
}

六、服务的使用

服务在 Angular 中是一个广义的概念,它可以是任何具有特定功能的类,用于封装业务逻辑、处理数据获取等操作。

6.1 创建服务

使用 Angular CLI 可以很方便地创建服务。在项目目录下执行以下命令:

ng generate service user

这会在 src/app 目录下生成一个 user.service.ts 文件,内容如下:

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor() {}
}

@Injectable() 装饰器用于标记该类为可注入的服务,providedIn: 'root' 表示该服务在应用的根模块中提供,整个应用都可以使用。

6.2 使用服务

在组件中使用服务,需要通过依赖注入将服务实例注入到组件的构造函数中。例如,在 UserListComponent 中使用 UserService 来获取用户列表。

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app - user - list',
  templateUrl: './user - list.component.html'
})
export class UserListComponent implements OnInit {
  users = [];

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.users = this.userService.getUsers();
  }
}

UserService 中可以实现 getUsers 方法来从服务器获取数据或返回模拟数据:

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor() {}

  getUsers() {
    // 这里可以是实际的 API 调用,暂时返回模拟数据
    return [
      { name: 'User 1' },
      { name: 'User 2' }
    ];
  }
}

七、路由

路由是单页应用程序(SPA)中实现页面导航和视图切换的关键功能。Angular 提供了强大的路由模块来管理应用的导航。

7.1 配置路由

首先,使用 Angular CLI 创建一个新的模块来管理路由:

ng generate module app - routing --flat --module=app

--flat 选项表示将路由模块文件放在 src/app 目录下,而不是创建一个单独的子目录,--module=app 表示将该路由模块导入到 AppModule 中。

在生成的 app - routing.module.ts 文件中配置路由:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

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

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

这里定义了两个路由,一个是空路径('')对应 HomeComponent,另一个是 about 路径对应 AboutComponent

7.2 使用路由

app.component.html 中添加路由出口,用于显示匹配路由的组件:

<router - outlet></router - outlet>

然后可以添加导航链接:

<ul>
  <li><a routerLink="">Home</a></li>
  <li><a routerLink="about">About</a></li>
</ul>

当用户点击链接时,Angular 会根据路由配置加载相应的组件到路由出口中。

八、表单处理

Angular 提供了强大的表单处理功能,分为模板驱动表单和响应式表单。

8.1 模板驱动表单

模板驱动表单是通过在模板中使用指令(如 ngModelngForm 等)来创建和管理表单。例如,创建一个简单的登录表单:

<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" [(ngModel)]="username" name="username" required />
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" [(ngModel)]="password" name="password" required />
  </div>
  <button type="submit">Submit</button>
</form>
import { Component } from '@angular/core';

@Component({
  selector: 'app - login - form',
  templateUrl: './login - form.component.html'
})
export class LoginFormComponent {
  username = '';
  password = '';

  onSubmit(form) {
    if (form.valid) {
      console.log('Form submitted:', this.username, this.password);
    }
  }
}

8.2 响应式表单

响应式表单通过在组件类中构建表单模型来管理表单。例如,同样是登录表单:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app - reactive - login - form',
  templateUrl: './reactive - login - form.component.html'
})
export class ReactiveLoginFormComponent {
  loginForm: FormGroup;

  constructor() {
    this.loginForm = new FormGroup({
      username: new FormControl('', Validators.required),
      password: new FormControl('', Validators.required)
    });
  }

  onSubmit() {
    if (this.loginForm.valid) {
      console.log('Form submitted:', this.loginForm.value);
    }
  }
}
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" formControlName="username" />
    <div *ngIf="loginForm.get('username').hasError('required') && (loginForm.get('username').touched || loginForm.get('username').dirty)">
      Username is required
    </div>
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" formControlName="password" />
    <div *ngIf="loginForm.get('password').hasError('required') && (loginForm.get('password').touched || loginForm.get('password').dirty)">
      Password is required
    </div>
  </div>
  <button type="submit">Submit</button>
</form>

通过以上内容,我们对 Angular 的核心概念、组件、服务、路由、表单等方面有了较为深入的了解,这些知识为进一步开发复杂的 Angular 应用奠定了坚实的基础。在实际开发中,还需要不断实践,结合具体业务需求,充分发挥 Angular 的优势,构建高效、可靠的前端应用程序。同时,随着 Angular 的不断发展,新的特性和功能也会不断推出,开发者需要持续学习和跟进,以保持技术的先进性。例如,Angular 一直致力于优化性能,通过 Ivy 渲染引擎等技术提升应用的加载速度和运行效率;在与其他技术栈的集成方面,Angular 也在不断探索,以便更好地适应多样化的开发场景。在构建大型企业级应用时,Angular 的模块化架构和依赖注入机制能够帮助团队更好地组织代码,提高代码的可维护性和可扩展性。例如,不同的业务模块可以封装成独立的 Angular 模块,通过路由和服务进行交互,使得整个应用的结构更加清晰。另外,Angular 与后端服务的集成也是开发中的重要环节,通常会使用 HTTP 客户端模块(如 @angular/common/http)来进行数据的获取和提交。在实际项目中,还需要考虑数据的安全性、缓存策略以及错误处理等方面,以确保应用的稳定性和可靠性。例如,在发送 HTTP 请求时,可以设置合适的请求头来进行身份验证,同时对可能出现的网络错误、服务器错误等进行统一的处理,给用户提供友好的提示信息。总之,掌握 Angular 的核心知识并不断在实践中积累经验,是成为优秀 Angular 开发者的必经之路。