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

Angular:前端开发的新选择

2023-01-105.9k 阅读

Angular 基础架构

Angular 是一款功能强大的前端开发框架,由 Google 维护。它构建在 TypeScript 之上,拥有一套完整的架构体系,这使其在大型项目开发中表现出色。

模块(Modules)

Angular 应用由模块组成。模块是一个容器,用于分组相关的组件、指令、管道和服务。例如,我们有一个简单的待办事项应用,可能会有一个核心模块 AppModule

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';

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

在上述代码中,@NgModule 装饰器定义了一个模块。imports 数组指定了该模块依赖的其他模块,这里引入了 BrowserModule,它包含了在浏览器中运行应用所需的基础功能。declarations 数组声明了该模块内的组件、指令和管道,这里只有 AppComponentbootstrap 数组指定了应用的根组件,即 AppComponent

组件(Components)

组件是 Angular 应用的基本构建块,负责处理视图和应用逻辑。每个组件都有一个模板(HTML 片段)、一个类(包含组件逻辑)和一个可选的样式表。

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

@Component({
  selector: 'app - todo - item',
  templateUrl: './todo - item.component.html',
  styleUrls: ['./todo - item.component.css']
})
export class TodoItemComponent {
  task: string = '默认任务';
  isCompleted: boolean = false;

  toggleCompletion() {
    this.isCompleted =!this.isCompleted;
  }
}

上述 TodoItemComponent 组件,@Component 装饰器配置了组件的元数据。selector 是组件在模板中使用的标签名,如 <app - todo - item>templateUrl 指向组件的模板文件,styleUrls 指向组件的样式文件。在组件类中,定义了 taskisCompleted 两个属性,以及 toggleCompletion 方法用于切换任务的完成状态。

其对应的模板文件 todo - item.component.html 可能如下:

<li [ngClass]="{completed: isCompleted}">
  <input type="checkbox" [(ngModel)]="isCompleted" (change)="toggleCompletion()">
  {{task}}
</li>

这里使用了 Angular 的模板语法,[ngClass] 根据 isCompleted 的值来添加或移除 completed 类,[(ngModel)] 实现了双向数据绑定,将 input 的选中状态与组件的 isCompleted 属性绑定,(change) 事件绑定到 toggleCompletion 方法。

数据绑定

数据绑定是 Angular 实现视图与组件之间交互的重要机制。

单向数据绑定

单向数据绑定分为从组件到视图和从视图到组件两种。

从组件到视图,通常使用插值表达式或属性绑定。例如在组件类中有一个属性 message

export class HelloComponent {
  message: string = 'Hello, Angular!';
}

在模板中可以使用插值表达式显示该属性:

<p>{{message}}</p>

也可以通过属性绑定设置元素的属性,比如设置 img 元素的 src 属性:

export class ImageComponent {
  imageUrl: string = 'https://example.com/image.jpg';
}
<img [src]="imageUrl" alt="示例图片">

从视图到组件则通过事件绑定实现。比如有一个按钮,点击时调用组件的方法:

export class ButtonComponent {
  onClick() {
    console.log('按钮被点击了');
  }
}
<button (click)="onClick()">点击我</button>

双向数据绑定

双向数据绑定结合了从组件到视图和从视图到组件的单向绑定。在表单元素中经常使用,如 input 元素。

export class FormComponent {
  username: string = '';
}
<input [(ngModel)]="username" placeholder="请输入用户名">
<p>你输入的用户名是: {{username}}</p>

这里 [(ngModel)] 指令实现了双向数据绑定,输入框的值会实时更新组件的 username 属性,同时 username 属性的变化也会反映在输入框中。

指令(Directives)

指令是 Angular 中用于修改 DOM 元素行为的代码。分为属性指令和结构指令。

属性指令

属性指令用于改变元素的外观或行为。例如 NgStyleNgClass

export class StyleComponent {
  isSpecial: boolean = true;
}
<div [ngStyle]="{color: isSpecial? 'blue' : 'black'}">
  根据条件改变颜色
</div>
<div [ngClass]="{special: isSpecial}">
  根据条件添加类
</div>

[ngStyle] 根据 isSpecial 的值设置 div 的颜色,[ngClass] 根据 isSpecial 的值添加或移除 special 类。

结构指令

结构指令用于改变 DOM 的结构,如添加或移除元素。常见的结构指令有 NgIfNgFor

export class ListComponent {
  items: string[] = ['item1', 'item2', 'item3'];
}
<div *ngIf="items.length > 0">
  <ul>
    <li *ngFor="let item of items">{{item}}</li>
  </ul>
</div>

*ngIf 根据 items 数组的长度决定是否渲染内部的 div 及其子元素。*ngFor 则遍历 items 数组,为每个元素创建一个 li 元素。

服务(Services)

服务是在应用中可共享的对象,用于处理业务逻辑、数据访问等。例如,我们创建一个简单的 TodoService 来管理待办事项数据。

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

@Injectable({
  providedIn: 'root'
})
export class TodoService {
  private todos: string[] = [];

  addTodo(todo: string) {
    this.todos.push(todo);
  }

  getTodos() {
    return this.todos;
  }
}

@Injectable 装饰器使该类成为一个服务,providedIn: 'root' 表示该服务在应用的根模块中提供,可在整个应用中共享。

在组件中使用该服务:

import { Component } from '@angular/core';
import { TodoService } from './todo.service';

@Component({
  selector: 'app - todo - list',
  templateUrl: './todo - list.component.html'
})
export class TodoListComponent {
  constructor(private todoService: TodoService) {}

  newTodo: string = '';

  addNewTodo() {
    if (this.newTodo) {
      this.todoService.addTodo(this.newTodo);
      this.newTodo = '';
    }
  }

  getTodos() {
    return this.todoService.getTodos();
  }
}

在组件的构造函数中注入 TodoService,通过服务的方法来添加和获取待办事项。

路由(Routing)

路由在单页应用(SPA)中用于导航不同的视图。Angular 提供了强大的路由功能。

首先,在模块中配置路由。假设我们有两个组件 HomeComponentAboutComponent

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

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

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

这里定义了两条路由,空路径 '' 对应 HomeComponent/about 路径对应 AboutComponentRouterModule.forRoot(routes) 用于在根模块中配置路由。

在模板中使用路由链接:

<ul>
  <li><a routerLink="/">首页</a></li>
  <li><a routerLink="/about">关于</a></li>
</ul>
<router - outlet></router - outlet>

routerLink 指令创建了路由链接,router - outlet 是路由出口,用于显示匹配路由的组件。

依赖注入(Dependency Injection)

依赖注入是 Angular 中用于管理组件和服务之间依赖关系的机制。例如,前面提到的 TodoListComponent 依赖于 TodoService

export class TodoListComponent {
  constructor(private todoService: TodoService) {}
}

当 Angular 创建 TodoListComponent 实例时,它会自动创建 TodoService 的实例并注入到 TodoListComponent 的构造函数中。这使得组件的依赖关系更加清晰,也便于测试。

管道(Pipes)

管道用于对数据进行转换和格式化。例如,DatePipe 用于格式化日期。

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

@Component({
  selector: 'app - date - example',
  templateUrl: './date - example.component.html'
})
export class DateExampleComponent {
  today: Date = new Date();
}
<p>格式化后的日期: {{today | date:'yyyy - MM - dd'}}</p>

这里使用 date 管道将 today 日期对象格式化为 yyyy - MM - dd 的形式。

Angular 与其他框架的比较

与 React 相比,Angular 是一个完整的框架,提供了模块、服务、路由等一整套解决方案,适合大型企业级应用开发。而 React 更像是一个视图库,需要开发者自己集成路由、状态管理等工具。在数据绑定方面,Angular 的双向数据绑定更为直接,而 React 主要通过单向数据流和 setState 方法来更新视图。

与 Vue.js 相比,Vue.js 语法相对简单,上手容易,适合快速开发小型项目。Angular 则在大型项目的架构设计、代码的可维护性和可扩展性方面表现出色。Vue.js 的组件化相对更轻量级,而 Angular 的组件系统更加严格和规范。

性能优化

在 Angular 应用开发中,性能优化至关重要。

变更检测(Change Detection)

Angular 使用变更检测机制来检查组件数据的变化并更新视图。默认情况下,Angular 使用 Default 策略,会检查组件树中的所有组件。对于一些性能敏感的组件,可以使用 OnPush 策略。

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

@Component({
  selector: 'app - performance - component',
  templateUrl: './performance - component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PerformanceComponent {
  data: any;

  constructor() {
    this.data = { value: '初始值' };
  }
}

使用 OnPush 策略后,只有当组件的输入属性引用发生变化,或组件接收到事件时,才会触发变更检测,从而提高性能。

懒加载(Lazy Loading)

对于大型应用,懒加载模块可以提高应用的加载性能。在路由配置中,可以实现模块的懒加载。

const routes: Routes = [
  {
    path: 'feature',
    loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
  }
];

这样,当用户访问 /feature 路径时,FeatureModule 才会被加载,而不是在应用启动时就加载所有模块。

测试

Angular 提供了强大的测试支持,包括单元测试和集成测试。

单元测试

使用 Jasmine 和 Karma 进行单元测试。例如,对 TodoService 进行单元测试:

import { TestBed } from '@angular/core/testing';
import { TodoService } from './todo.service';

describe('TodoService', () => {
  let service: TodoService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(TodoService);
  });

  it('should add a todo', () => {
    const initialLength = service.getTodos().length;
    service.addTodo('新任务');
    const newLength = service.getTodos().length;
    expect(newLength).toBe(initialLength + 1);
  });
});

describe 块定义了测试套件,beforeEach 方法在每个测试用例执行前初始化 TodoServiceit 块定义了具体的测试用例,这里测试了 addTodo 方法是否正确添加任务。

集成测试

集成测试用于测试组件之间的交互。例如,测试 TodoListComponentTodoService 的集成:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TodoListComponent } from './todo - list.component';
import { TodoService } from './todo.service';
import { By } from '@angular/platform - browser';

describe('TodoListComponent', () => {
  let component: TodoListComponent;
  let fixture: ComponentFixture<TodoListComponent>;
  let todoService: TodoService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TodoListComponent],
      providers: [TodoService]
    });
    fixture = TestBed.createComponent(TodoListComponent);
    component = fixture.componentInstance;
    todoService = TestBed.inject(TodoService);
    fixture.detectChanges();
  });

  it('should add a new todo', () => {
    const initialLength = todoService.getTodos().length;
    const inputElement = fixture.debugElement.query(By.css('input'));
    inputElement.nativeElement.value = '新任务';
    inputElement.nativeElement.dispatchEvent(new Event('input'));
    const buttonElement = fixture.debugElement.query(By.css('button'));
    buttonElement.nativeElement.click();
    fixture.detectChanges();
    const newLength = todoService.getTodos().length;
    expect(newLength).toBe(initialLength + 1);
  });
});

在这个集成测试中,模拟了用户在输入框输入内容并点击按钮添加任务的操作,测试了 TodoListComponentTodoService 的交互是否正确。

通过以上对 Angular 各个方面的深入介绍,我们可以看到 Angular 在前端开发中提供了丰富且强大的功能,无论是构建小型应用还是大型企业级项目,都能为开发者提供良好的支持和开发体验。在实际项目中,开发者可以根据项目需求充分利用 Angular 的特性,打造出高性能、可维护的前端应用。