创建项目
npm install -g @angular/cling new my-angular-projectcd my-angular-projectng serveng serve --port 8081
# access in localhost:4200angular-cli
文件生成
Angular CLI 提供了快捷命令来生成各种文件,保持项目结构的一致性。常用的生成命令包括:
-
生成组件:
ng generate component component-name# 或者简写ng g c component-name这将创建一个包含
HTML、CSS、TypeScript和测试文件的组件。 -
生成服务:
ng generate service service-name# 简写ng g s service-name生成的服务文件默认包含注入器,并能方便地被组件使用。
-
生成模块:
ng generate module module-name# 简写ng g m module-name生成的模块可以帮助你将代码逻辑按功能模块划分,便于代码管理和懒加载。
构建项目
使用 ng build 命令来构建项目:
ng build构建后的文件会存放在 dist 目录下。为了发布到生产环境,你可以使用以下命令:
ng build --prod-prod选项会启用生产环境的优化,如代码压缩、混淆、去除调试信息等。
测试
Angular CLI 支持单元测试和端到端测试:
-
单元测试:使用
ng test命令运行单元测试,默认使用 Karma 和 Jasmine。ng test这会在浏览器中打开测试界面,并实时更新测试结果。
-
端到端测试:使用
ng e2e命令运行端到端测试,默认使用 Protractor。ng e2e端到端测试模拟用户行为,确保应用整体功能正常。
生成服务、管道、指令等其他结构
除了组件和模块,CLI 还支持生成其他 Angular 结构:
-
生成指令:
ng generate directive directive-name# 简写ng g d directive-name -
生成管道:
ng generate pipe pipe-name# 简写ng g p pipe-name
配置和优化
- 环境配置
Angular 支持不同环境的配置文件,默认包括 environment.ts(开发环境)和 environment.prod.ts(生产环境)。你可以在 angular.json 文件中定义更多的环境配置,并在构建时选择不同的环境:
ng build --configuration production- 调试和监控
使用 ng serve 时,Angular CLI 会实时监听文件更改,并自动重新构建应用程序。你可以使用 --source-map 选项生成调试信息,以便在浏览器中调试代码:
ng serve --source-map- 预加载和懒加载模块
在大型应用中,使用预加载和懒加载模块可以提高性能。Angular CLI 自动支持懒加载,帮助你按需加载模块,减少初次加载时间。
其他有用的 CLI 命令
-
更新 Angular 项目或依赖:
ng update该命令会检查并更新 Angular 和相关依赖项。
-
分析构建包: 使用
-stats-json选项构建应用,可以生成分析文件,用于检查和优化打包内容。ng build --prod --stats-json -
添加第三方库或工具: 使用
ng add命令,可以快速添加第三方库和插件。例如,添加 Angular Material:ng add @angular/material
angular.json 配置文件
angular.json 是 Angular 项目的全局配置文件,包含项目的所有配置信息。你可以在这里调整构建路径、环境配置、样式和脚本的引入顺序等。
Angular 项目的基础结构
项目目录结构
当你使用 ng new 创建一个新的 Angular 项目后,项目结构会像这样:
my-angular-app/├── e2e/ # 端到端测试目录├── node_modules/ # 项目依赖包目录├── src/ # 应用源代码目录│ ├── app/ # 核心应用目录│ │ ├── app.component.ts # 根组件的逻辑文件│ │ ├── app.component.html # 根组件的模板文件│ │ ├── app.component.css # 根组件的样式文件│ │ └── app.module.ts # 根模块文件│ ├── assets/ # 静态资源目录│ ├── environments/ # 环境配置目录│ ├── index.html # 主 HTML 文件│ ├── main.ts # 应用的主入口文件│ ├── polyfills.ts # 浏览器兼容代码│ ├── styles.css # 全局样式文件│ └── test.ts # 单元测试入口文件├── angular.json # Angular 项目配置文件├── package.json # 项目依赖和脚本├── tsconfig.json # TypeScript 配置文件└── README.md # 项目说明文件目录和文件详解
-
src/目录src/是存放应用源代码的主要目录,Angular 应用的所有核心代码都在这里。app/目录:这是应用程序的主要目录,包含了根模块和根组件。随着项目的开发,你会在这里创建更多的组件、服务、模块等。app.component.ts:定义了根组件的逻辑,是整个应用的起点。app.component.html:根组件的模板文件,用于定义根组件的 HTML 结构。app.component.css:根组件的样式文件。app.module.ts:根模块文件,应用启动时会加载的模块。每个 Angular 应用至少有一个根模块。
assets/目录:存放静态资源(如图片、字体等),构建时会直接复制到构建目录中,可以通过相对路径访问这些资源。environments/目录:包含不同环境的配置文件,例如开发环境和生产环境。默认包含environment.ts(开发环境配置)和environment.prod.ts(生产环境配置)。你可以根据环境条件加载不同的配置。index.html:应用的主 HTML 文件。它是页面的入口,Angular 会把所有组件渲染到这个页面内。main.ts:应用的主入口文件,Angular 应用从这里开始执行。main.ts会引导根模块AppModule,并启动 Angular 应用。polyfills.ts:用于加载不同浏览器的兼容性代码,确保应用在所有浏览器中一致运行。styles.css:全局样式文件,可以在这里定义整个应用的通用样式。test.ts:测试入口文件,用于配置和初始化单元测试。
-
e2e/目录e2e/目录用于存放端到端测试代码。默认使用 Protractor 框架来运行这些测试,用于模拟用户行为并测试整个应用的功能。 -
根目录中的其他文件
angular.json:Angular 项目的配置文件,包含构建和开发服务器的相关配置。可以在这里调整构建输出路径、环境配置、样式和脚本的引入顺序等。package.json:Node.js 项目的配置文件,包含依赖项和运行脚本。所有的依赖库和 CLI 命令都在这里定义和管理。tsconfig.json:TypeScript 的配置文件,定义了编译 TypeScript 代码的规则。README.md:项目的说明文件,可以写入项目的描述、安装步骤和使用说明。
-
node_modules/目录node_modules/目录用于存放项目的依赖包,由npm安装。所有 Angular、TypeScript、编译器等库的代码都在这里。
组件和模块
在 Angular 中,组件和模块是应用的核心架构。组件负责构建页面的各个部分,而模块则帮助组织和管理这些组件。
组件(Component)
组件是 Angular 应用的基本构建块。一个组件通常包括三个部分:
- 模板(Template):定义组件的 HTML 结构。
- 样式(Styles):定义组件的 CSS 样式。
- 逻辑(Class):定义组件的行为和数据。
-
创建组件
Angular CLI 提供了生成组件的命令:
ng generate component component-name# 或者简写ng g c component-name这个命令会在
app目录下创建一个新的组件目录,包含以下文件:component-name.component.ts:组件的逻辑文件,包含组件类和装饰器。component-name.component.html:组件的模板文件。component-name.component.css:组件的样式文件。component-name.component.spec.ts:组件的测试文件。
-
组件的结构
在
component-name.component.ts文件中,组件使用@Component装饰器来定义,结构如下:import { Component } from '@angular/core';@Component({selector: 'app-component-name', // 组件的选择器,用于在模板中引用组件templateUrl: './component-name.component.html', // 组件的模板文件styleUrls: ['./component-name.component.css'] // 组件的样式文件})export class ComponentNameComponent {title = 'Hello, Angular'; // 组件的属性和方法}selector:组件的选择器,用于在其他模板中引用该组件。比如,<app-component-name></app-component-name>。templateUrl和styleUrls:分别定义组件的模板和样式文件路径。
-
组件的数据绑定
Angular 提供了多种数据绑定方式:
- 插值表达式:用于展示组件属性的值,例如
{{ title }}。 - 属性绑定:通过
[]绑定属性,例如<img [src]="imageUrl">。 - 事件绑定:通过
()绑定事件,例如<button (click)="onClick()">Click</button>。 - 双向数据绑定:通过
[(ngModel)]实现数据和视图的双向绑定(需要导入FormsModule)。
- 插值表达式:用于展示组件属性的值,例如
模块(Module)
模块用于组织和管理应用的组件、指令、管道和服务。每个 Angular 应用至少有一个根模块,称为 AppModule,它在 app.module.ts 文件中定义。
-
模块的结构
在
app.module.ts文件中,模块使用@NgModule装饰器定义,结构如下:import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { AppComponent } from './app.component';import { ComponentNameComponent } from './component-name/component-name.component';@NgModule({declarations: [AppComponent,ComponentNameComponent // 声明模块中的组件],imports: [BrowserModule // 导入其他模块],providers: [], // 声明服务的提供者bootstrap: [AppComponent] // 定义根组件,Angular 启动时会加载})export class AppModule { }declarations:模块中声明的组件、指令和管道,只有声明在模块中的组件才能被使用。imports:导入其他模块,例如BrowserModule是用于浏览器应用的核心模块。providers:声明应用中使用的服务提供者。bootstrap:定义应用启动时加载的根组件,通常是AppComponent。
-
特性模块
在较大的应用中,可以创建多个特性模块(Feature Modules)来组织代码,便于分离不同功能和实现懒加载。
ng generate module feature-module# 或者简写ng g m feature-module
组件与模块的关系
- 模块用于组织组件:在 Angular 中,模块用于管理和组织组件,每个模块可以包含多个组件。
- 组件的复用性:一个组件可以在多个模块中声明和使用,但必须先导入包含该组件的模块。
- 根模块和特性模块:根模块(如
AppModule)负责启动应用,而特性模块用于组织应用中的具体功能。
数据绑定
插值绑定(Interpolation)
插值绑定使用 {{ }} 语法来绑定数据,常用于在 HTML 中显示组件的属性值。
示例: 在组件类中定义一个属性 title,然后通过插值绑定将其显示在模板中。
// 在组件的 TypeScript 文件中export class MyComponent { title = 'Hello, Angular!';}<!-- 在组件的 HTML 模板中 --><h1>{{ title }}</h1>这里,{{ title }} 会被替换为 Hello, Angular!。插值绑定通常用于文本内容的展示。
属性绑定(Property Binding)
属性绑定使用方括号 [] 语法,将组件属性的值绑定到 HTML 元素的属性上,例如 src, href, disabled 等。
示例: 假设我们有一个图片链接,可以使用属性绑定将其绑定到 img 元素的 src 属性上。
// 在组件的 TypeScript 文件中export class MyComponent { imageUrl = 'https://example.com/image.jpg';}<!-- 在组件的 HTML 模板中 --><img [src]="imageUrl" alt="Example Image">这里 [src]="imageUrl" 表示将 imageUrl 的值绑定到 img 的 src 属性上。
事件绑定(Event Binding)
事件绑定使用圆括号 () 语法,将视图中的事件(如 click、mouseover)绑定到组件中的方法上,从而触发特定逻辑。
示例: 我们可以在按钮上绑定一个 click 事件,触发组件中的 onClick 方法。
// 在组件的 TypeScript 文件中export class MyComponent { onClick() { console.log('Button clicked!'); }}<!-- 在组件的 HTML 模板中 --><button (click)="onClick()">Click Me</button>这里,(click)="onClick()" 绑定了 click 事件,点击按钮时会执行 onClick 方法,并输出 "Button clicked!"。
双向数据绑定(Two-way Binding)
双向数据绑定使用 [(ngModel)] 语法,将数据和视图双向同步。它允许用户输入的数据自动更新组件属性,组件属性的变化也会自动反映到视图中。双向绑定通常用于表单输入和用户输入场景。
示例: 在输入框中使用双向数据绑定,将 name 属性与 input 输入框的值同步。需要导入 FormsModule。
// 在组件的 TypeScript 文件中export class MyComponent { name = '';}<!-- 在组件的 HTML 模板中 --><input [(ngModel)]="name" placeholder="Enter your name"><p>Hello, {{ name }}!</p>在这里,[(ngModel)]="name" 实现了双向数据绑定,用户在输入框中的输入会立即更新 name,并且 name 的值会立即显示在页面上。
注意:使用双向绑定需要在应用模块中导入 FormsModule,否则会报错。
import { FormsModule } from '@angular/forms';
@NgModule({ imports: [FormsModule], ...})export class AppModule { }数据绑定的好处
- 简化代码:通过绑定可以减少手动更新 DOM 的代码。
- 实时同步:数据和视图保持同步,使得应用更加动态。
- 提高可维护性:将数据和视图分离,更容易维护和扩展应用。
指令(Directives)
指令(Directives)是 Angular 中非常重要的一个特性,允许我们在模板中操作 DOM 元素,控制样式、结构和行为。指令使得 Angular 的模板变得更加动态和灵活。
指令的类型
Angular 中有三种主要的指令类型:
- 组件指令:组件本质上也是一种指令,它拥有模板、样式和逻辑,是指令的扩展形式。
- 结构型指令(Structural Directives):用于添加或移除 DOM 元素,控制页面结构。常见的有
ngIf和ngFor。 - 属性型指令(Attribute Directives):用于更改元素的外观或行为,常见的有
ngClass和ngStyle。
结构型指令
结构型指令用于添加、移除或替换 DOM 元素。使用时需要在指令前加上 * 符号。
-
ngIf根据条件来显示或隐藏 DOM 元素。<div *ngIf="isVisible">This is visible only if isVisible is true.</div>在上面的示例中,
*ngIf="isVisible"控制div元素是否显示,只有当isVisible为true时才会渲染该元素。 -
ngFor用于遍历数组并生成一组 DOM 元素。<ul><li *ngFor="let item of items">{{ item }}</li></ul>在上面的示例中,
*ngFor会遍历items数组,为每个元素生成一个<li>。 -
ngSwitch可以根据不同的条件来渲染不同的元素,通常用于多条件判断。<div [ngSwitch]="value"><p *ngSwitchCase="'one'">Value is one</p><p *ngSwitchCase="'two'">Value is two</p><p *ngSwitchDefault>Value is unknown</p></div>ngSwitchCase用于匹配特定的值,ngSwitchDefault表示默认的情况。
属性型指令
属性型指令用于改变 DOM 元素的外观或行为,它不会创建或移除元素,而是修改现有元素的属性。
-
ngClass用于动态设置元素的 CSS 类。<div [ngClass]="{ 'active': isActive, 'highlight': isHighlighted }">Styled div</div>在上面的示例中,根据
isActive和isHighlighted的值动态添加active和highlight类。 -
ngStyle用于动态设置元素的内联样式。<div [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px' }">Styled div</div>在上面的示例中,
textColor和fontSize会动态控制div元素的颜色和字体大小。
自定义指令
除了 Angular 提供的内置指令,你还可以创建自定义指令来实现特定的行为。通常,自定义指令是属性型指令,用于扩展元素的功能。
-
创建一个自定义指令
使用 Angular CLI 创建一个指令,命令如下:
ng generate directive highlight# 或简写ng g d highlight这个命令会生成一个指令文件
highlight.directive.ts,初始内容如下:import { Directive, ElementRef, Renderer2, HostListener } from '@angular/core';@Directive({selector: '[appHighlight]'})export class HighlightDirective {constructor(private el: ElementRef, private renderer: Renderer2) {}@HostListener('mouseenter') onMouseEnter() {this.highlight('yellow');}@HostListener('mouseleave') onMouseLeave() {this.highlight(null);}private highlight(color: string) {this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', color);}}在这个示例中,自定义的
HighlightDirective指令会在鼠标移到元素上时设置背景颜色为黄色,移出时移除颜色。@Directive装饰器:定义了一个指令,selector: '[appHighlight]'表示它是一个属性型指令,使用时通过appHighlight属性添加到元素上。ElementRef:用于访问指令应用的 DOM 元素。Renderer2:用于安全地操作 DOM 样式,避免直接修改 DOM。@HostListener:监听元素的事件,mouseenter表示鼠标移入,mouseleave表示鼠标移出。
-
使用自定义指令
在模板中使用
appHighlight指令:<p appHighlight>Hover over this text to see the highlight effect.</p>添加
appHighlight属性后,指令会在元素上生效。当鼠标悬停时,背景颜色会变成黄色,移开时恢复原样。
服务(Services)和依赖注入(Dependency Injection)
在 Angular 中,服务(Service)用于封装和共享应用中的逻辑和数据,而依赖注入(DI)系统负责管理和提供这些服务。通过使用服务,你可以将应用的业务逻辑从组件中分离出来,提升代码的复用性和可维护性。依赖注入系统则确保服务可以被组件或其他服务轻松使用。
服务
服务在 Angular 中通常是一个类,用于封装不属于任何组件的逻辑和数据。比如获取数据、处理业务逻辑、管理状态等,服务可以在多个组件之间共享。
- 创建一个服务
使用 Angular CLI 可以快速生成服务:
ng generate service my-service# 或者简写ng g s my-service生成的服务文件 my-service.service.ts 大致如下:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' // 将服务注册在根注入器中,全局可用})export class MyService { constructor() { }
getData() { return 'Hello from MyService!'; }}@Injectable装饰器:标记一个类可以作为依赖被注入到组件或其他服务中。providedIn: 'root':表示该服务在根注入器中提供,整个应用都可以访问。这样可以避免在providers中手动注册服务。
- 在组件中使用服务
服务通常通过依赖注入的方式使用。你可以将服务注入到组件的构造函数中,以便在组件中调用服务方法。
假设我们已经创建了一个名为 MyService 的服务,以下是在组件中使用该服务的示例:
import { Component, OnInit } from '@angular/core';import { MyService } from './my-service.service';
@Component({ selector: 'app-my-component', template: `<p>{{ message }}</p>`})export class MyComponent implements OnInit { message: string;
// 在构造函数中注入服务 constructor(private myService: MyService) {}
ngOnInit(): void { this.message = this.myService.getData(); }}- 构造函数注入:通过在组件的构造函数中定义一个私有变量
myService来注入服务。 - 调用服务方法:在
ngOnInit生命周期钩子中调用服务方法getData()并将返回值赋给message。
- 服务的作用域和提供方式
Angular 的服务提供方式有多种,不同的提供方式会影响服务的作用域和生命周期:
- 根级提供方式
使用 providedIn: 'root' 在根注入器中提供服务,服务实例在整个应用程序生命周期中是单例的。这种方式通常用于全局共享的数据或逻辑。
@Injectable({ providedIn: 'root'})export class MyService { }- 模块级提供方式
如果你希望服务仅在特定模块中可用,可以在该模块的 providers 数组中注册服务。这会让服务的生命周期与模块一致,适合局部共享的数据或逻辑。
import { NgModule } from '@angular/core';import { MyService } from './my-service.service';
@NgModule({ providers: [MyService] // 在模块级提供服务})export class MyModule { }- 组件级提供方式
如果你希望服务的实例仅在单个组件或该组件的子组件中可用,可以在组件的 providers 数组中注册服务。这会让每个组件实例有自己独立的服务实例,适合仅在单个组件内使用的逻辑。
import { Component } from '@angular/core';import { MyService } from './my-service.service';
@Component({ selector: 'app-my-component', template: `<p>My Component</p>`, providers: [MyService] // 在组件级提供服务})export class MyComponent { }依赖注入(Dependency Injection)
依赖注入是一种设计模式,通过将依赖项(例如服务)注入到组件或其他服务中,避免硬编码依赖。Angular 的 DI 系统自动管理依赖项的创建和提供,简化了应用的结构。
- DI 系统的工作方式
当 Angular 检测到某个类需要特定的依赖(比如 MyService),它会在注入器中查找该依赖的实例。如果实例不存在,它会创建实例并返回给组件或服务。
- 自定义注入器
Angular 支持在组件中自定义注入器,从而控制依赖的提供方式。这在大多数应用中不常用,但在需要控制服务作用域或实现特殊依赖时非常有用。
服务的实际应用场景
- 数据共享:将共享的数据存储在服务中,以便多个组件可以访问和更新这些数据。
- HTTP 请求:通过
HttpClient服务从后端 API 获取数据,并将逻辑封装在服务中,便于复用和测试。 - 全局状态管理:在服务中管理应用状态,比如用户认证信息或主题设置。
示例:创建一个简单的数据服务
假设我们要创建一个简单的数据服务 DataService,用于管理一组用户数据,并提供增删改查功能。
- 创建服务
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root'})export class DataService { private users = ['Alice', 'Bob', 'Charlie'];
getUsers() { return this.users; }
addUser(user: string) { this.users.push(user); }}- 在组件中使用服务
import { Component } from '@angular/core';import { DataService } from './data.service';
@Component({ selector: 'app-user-list', template: ` <ul> <li *ngFor="let user of users">{{ user }}</li> </ul> <input [(ngModel)]="newUser" placeholder="Enter name"> <button (click)="addUser()">Add User</button> `})export class UserListComponent { users: string[]; newUser = '';
constructor(private dataService: DataService) { this.users = this.dataService.getUsers(); }
addUser() { if (this.newUser) { this.dataService.addUser(this.newUser); this.newUser = ''; } }}在这个示例中,DataService 提供了用户数据的管理逻辑,UserListComponent 组件从服务中获取数据并展示在视图中,同时可以通过服务添加新用户。
路由(Routing)和导航
Angular 的路由系统让我们可以构建单页应用(SPA),通过 URL 控制页面的不同视图。路由允许应用根据用户导航在页面之间切换,并且支持参数传递、懒加载和路由守卫等高级功能。
什么是路由?
在单页应用中,虽然只有一个实际页面,但用户通过不同的 URL 可以导航到应用的不同部分。Angular 的路由系统让你可以定义 URL 路径与组件之间的映射关系,点击导航链接时会加载对应的组件,而不会刷新整个页面。
设置 Angular 路由
- 导入
RouterModule
在 Angular 应用中设置路由,需要在应用的根模块或相关特性模块中导入 RouterModule,并定义路由配置。
例如,在 standalone 模式中,可以在 main.ts 中配置路由:
import { bootstrapApplication } from '@angular/platform-browser';import { AppComponent } from './app/app.component';import { provideRouter } from '@angular/router';import { HomeComponent } from './app/home/home.component';import { AboutComponent } from './app/about/about.component';
bootstrapApplication(AppComponent, { providers: [ provideRouter([ { path: '', component: HomeComponent }, // 默认路由 { path: 'about', component: AboutComponent } // /about 路由 ]) ]});在这个例子中,我们定义了两个路由:
''表示根路径(/),对应HomeComponent。'about'表示/about路径,对应AboutComponent。
- 创建组件
如果还没有相关组件,可以使用以下命令生成组件:
ng generate component home --standaloneng generate component about --standalone- 在模板中添加路由链接
Angular 提供了 routerLink 指令,用于创建路由链接。可以在 AppComponent 的模板中添加导航链接:
<nav> <a routerLink="/">Home</a> | <a routerLink="/about">About</a></nav><router-outlet></router-outlet>routerLink:用于指定导航链接的路径。例如,routerLink="/"指向根路径。<router-outlet></router-outlet>:路由出口,指定路由组件在页面中显示的位置。<router-outlet>是路由系统的占位符,Angular 会根据当前 URL 加载对应的组件并显示在此处。
- 路由参数传递
Angular 路由支持向 URL 传递参数,并在组件中接收和处理这些参数。例如,我们可以定义一个显示用户详情的路由:
- 定义带参数的路由
在路由配置中使用 :id 作为占位符定义参数:
{ path: 'user/:id', component: UserComponent }- 在模板中添加链接
在模板中可以使用 routerLink 传递参数:
<a [routerLink]="['/user', 1]">User 1</a><a [routerLink]="['/user', 2]">User 2</a>-
在组件中获取参数
在
UserComponent中,可以使用ActivatedRoute来获取路由参数:import { Component, OnInit } from '@angular/core';import { ActivatedRoute } from '@angular/router';@Component({selector: 'app-user',standalone: true,template: `<p>User ID: {{ userId }}</p>`})export class UserComponent implements OnInit {userId: string;constructor(private route: ActivatedRoute) {}ngOnInit(): void {this.userId = this.route.snapshot.paramMap.get('id')!;}}ActivatedRoute:Angular 路由服务,用于获取路由信息。paramMap:包含了路由参数的键值对。get('id')获取当前路由中的id参数。
路由守卫(Route Guards)
路由守卫用于保护路由,确保用户具备访问权限。常用的路由守卫包括:
CanActivate:在导航到某个路由前检查,决定是否允许访问。CanDeactivate:在离开某个路由时检查,决定是否允许离开。
例如,创建一个简单的 AuthGuard 守卫,确保用户登录后才能访问某个路由。
- 生成守卫
使用 CLI 生成守卫:
ng generate guard auth- 实现
AuthGuard的逻辑
在生成的 auth.guard.ts 文件中编写认证逻辑:
import { Injectable } from '@angular/core';import { CanActivate, Router } from '@angular/router';
@Injectable({ providedIn: 'root'})export class AuthGuard implements CanActivate { constructor(private router: Router) {}
canActivate(): boolean { const isAuthenticated = false; // 替换为实际认证逻辑
if (!isAuthenticated) { this.router.navigate(['/login']); return false; } return true; }}- 将守卫应用到路由上
在路由配置中使用 canActivate 属性应用守卫:
{ path: 'protected', component: ProtectedComponent, canActivate: [AuthGuard] }懒加载(Lazy Loading)
懒加载让特定模块在需要时才加载,优化了应用的初始加载速度。可以通过 loadComponent 轻松实现组件的懒加载:
{ path: 'lazy', loadComponent: () => import('./lazy/lazy.component').then(m => m.LazyComponent) }表单(Forms)处理
Angular 提供了强大的表单处理功能,支持创建和验证表单。Angular 表单主要有两种方式:模板驱动表单 和 响应式表单。这两种方式各有优势,适用于不同场景。
模板驱动表单(Template-driven Forms)
模板驱动表单主要通过 HTML 模板来定义表单结构和验证逻辑,适合简单的表单。它使用 Angular 的 FormsModule 来提供数据绑定和验证支持。
- 导入
FormsModule
首先,在模块中导入 FormsModule。如果你的项目是模块化的,打开 app.module.ts 或相应模块文件,添加 FormsModule。
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { FormsModule } from '@angular/forms';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, FormsModule // 导入 FormsModule ], bootstrap: [AppComponent]})export class AppModule {}- 定义模板驱动表单
在模板中创建一个简单的表单,使用 ngModel 指令来实现双向数据绑定。通过 #name="ngModel" 创建一个模板变量,用于访问输入的验证状态。
<form #myForm="ngForm"> <label for="name">Name:</label> <input id="name" name="name" [(ngModel)]="user.name" required> <div *ngIf="!myForm.controls.name?.valid && myForm.controls.name?.touched"> Name is required. </div>
<label for="email">Email:</label> <input id="email" name="email" [(ngModel)]="user.email" required email> <div *ngIf="!myForm.controls.email?.valid && myForm.controls.email?.touched"> Valid email is required. </div>
<button [disabled]="!myForm.valid">Submit</button></form>[(ngModel)]:双向数据绑定,用于同步表单字段和组件中的数据。#myForm="ngForm":创建一个模板引用变量myForm,可以访问表单的状态。required和email验证:Angular 自动提供了 HTML5 的验证规则。- 在组件中定义数据模型
在 app.component.ts 中定义 user 数据模型,与表单绑定。
import { Component } from '@angular/core';
@Component({ selector: 'app-root', templateUrl: './app.component.html'})export class AppComponent { user = { name: '', email: '' };}响应式表单(Reactive Forms)
响应式表单在组件类中定义表单的结构和验证逻辑,更加灵活,适合复杂的动态表单。它使用 ReactiveFormsModule 模块来提供表单控制和验证支持。
- 导入
ReactiveFormsModule
在模块中导入 ReactiveFormsModule。
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { ReactiveFormsModule } from '@angular/forms';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, ReactiveFormsModule // 导入 ReactiveFormsModule ], bootstrap: [AppComponent]})export class AppModule {}- 定义响应式表单
在组件中使用 FormBuilder 定义表单结构和验证规则。
import { Component } from '@angular/core';import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({ selector: 'app-root', templateUrl: './app.component.html'})export class AppComponent { userForm: FormGroup;
constructor(private fb: FormBuilder) { this.userForm = this.fb.group({ name: ['', Validators.required], email: ['', [Validators.required, Validators.email]] }); }
onSubmit() { if (this.userForm.valid) { console.log(this.userForm.value); } }}FormBuilder:Angular 提供的服务,用于简化表单结构的创建。Validators:用于设置表单字段的验证规则。- 在模板中绑定响应式表单
在模板中使用 [formGroup] 绑定整个表单,使用 formControlName 绑定每个表单控件。
<form [formGroup]="userForm" (ngSubmit)="onSubmit()"> <label for="name">Name:</label> <input id="name" formControlName="name"> <div *ngIf="userForm.controls.name.invalid && userForm.controls.name.touched"> Name is required. </div>
<label for="email">Email:</label> <input id="email" formControlName="email"> <div *ngIf="userForm.controls.email.invalid && userForm.controls.email.touched"> Valid email is required. </div>
<button [disabled]="userForm.invalid">Submit</button></form>[formGroup]:将组件中的userForm绑定到模板中的表单。formControlName:将表单控件绑定到userForm中对应的控件。
表单验证
Angular 提供了多种内置的验证器,如 Validators.required, Validators.email,还可以自定义验证器。
- 自定义验证器
可以在组件中定义一个自定义验证器,并将其应用到表单控件中。
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const forbidden = nameRe.test(control.value); return forbidden ? { forbiddenName: { value: control.value } } : null; };}然后在创建表单时应用自定义验证器:
this.userForm = this.fb.group({ name: ['', [Validators.required, forbiddenNameValidator(/bob/i)]], email: ['', [Validators.required, Validators.email]]});HTTP 客户端和 API 通信
在现代 Web 应用中,与后端 API 通信是必不可少的部分。Angular 提供了 HttpClient 模块,简化了与后端 API 的交互。使用 HttpClient 可以轻松地发送 HTTP 请求、处理响应数据、管理错误以及添加拦截器来控制请求和响应的行为。
设置 HttpClient
在 Angular 应用中使用 HttpClient,需要在模块中导入 HttpClientModule。
- 导入
HttpClientModule
在根模块或特性模块中导入 HttpClientModule:
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { HttpClientModule } from '@angular/common/http';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, HttpClientModule // 导入 HttpClientModule ], bootstrap: [AppComponent]})export class AppModule {}- 使用
HttpClient发送请求
HttpClient 提供了多种方法来发送 HTTP 请求,包括 get、post、put、delete 等,适用于不同类型的请求。
- 发送 GET 请求
假设我们要从 API 获取用户列表,可以使用 HttpClient.get 方法发送 GET 请求:
import { HttpClient } from '@angular/common/http';import { Component, OnInit } from '@angular/core';
@Component({ selector: 'app-user-list', template: ` <ul> <li *ngFor="let user of users">{{ user.name }}</li> </ul> `})export class UserListComponent implements OnInit { users: any[] = [];
constructor(private http: HttpClient) {}
ngOnInit(): void { this.http.get<any[]>('<https://jsonplaceholder.typicode.com/users>') .subscribe(data => { this.users = data; }); }}在这个例子中:
this.http.get发送一个 GET 请求。subscribe方法用于处理响应数据,接收的data是一个用户列表,赋值给组件的users属性。- 发送 POST 请求
假设我们要向服务器发送数据,可以使用 HttpClient.post 方法发送 POST 请求。
addUser(newUser: any) { this.http.post('<https://jsonplaceholder.typicode.com/users>', newUser) .subscribe(response => { console.log('User added:', response); });}这里的 post 方法发送一个 POST 请求,将 newUser 数据传递给 API。subscribe 中的 response 包含服务器返回的结果。
错误处理
在实际应用中,API 请求可能会遇到各种错误,如网络超时、服务器错误等。可以通过 catchError 操作符来捕获和处理错误。
- 错误处理示例
使用 catchError 操作符处理请求中的错误:
import { HttpErrorResponse } from '@angular/common/http';import { catchError } from 'rxjs/operators';import { throwError } from 'rxjs';
this.http.get('<https://jsonplaceholder.typicode.com/users>') .pipe( catchError(this.handleError) ) .subscribe( data => console.log('Data:', data), error => console.error('Error:', error) );
handleError(error: HttpErrorResponse) { let errorMessage = 'Unknown error!'; if (error.error instanceof ErrorEvent) { // Client-side error errorMessage = `Error: ${error.error.message}`; } else { // Server-side error errorMessage = `Error Code: ${error.status}\\nMessage: ${error.message}`; } return throwError(errorMessage);}在这里:
catchError操作符捕获错误并调用handleError方法。handleError方法根据错误类型生成错误信息并通过throwError返回。
HTTP 拦截器(Interceptors)
HTTP 拦截器允许我们在请求或响应处理前插入逻辑,例如添加认证 token、记录日志等。
- 创建拦截器
使用 Angular CLI 创建拦截器:
ng generate interceptor auth拦截器文件可能如下所示:
import { Injectable } from '@angular/core';import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
@Injectable()export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) { // 克隆请求并添加认证 token const authReq = req.clone({ setHeaders: { Authorization: `Bearer YOUR_TOKEN_HERE` } }); return next.handle(authReq); }}HttpInterceptor接口的intercept方法在请求发送之前执行。req.clone用于克隆请求对象并添加认证 token。- 注册拦截器
在模块中将拦截器注册为 HTTP_INTERCEPTORS 的多重提供者:
import { HTTP_INTERCEPTORS } from '@angular/common/http';import { AuthInterceptor } from './auth.interceptor';
@NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ]})export class AppModule {}使用 RxJS 进行异步处理
HttpClient 的方法会返回一个 Observable,通过 subscribe 方法订阅数据。可以使用 RxJS 操作符(如 map、switchMap)来处理异步数据流。
- RxJS 操作符示例
假设你希望在发送请求后处理数据,可以使用 map 操作符:
import { map } from 'rxjs/operators';
this.http.get<any[]>('<https://jsonplaceholder.typicode.com/users>') .pipe( map(users => users.filter(user => user.isActive)) // 过滤用户列表 ) .subscribe(data => { this.users = data; });RxJS 和响应式编程
RxJS 是 Angular 的核心库之一,用于处理异步事件和数据流。RxJS 的强大之处在于它提供了丰富的操作符,可以高效地处理流式数据和复杂的异步操作。在 Angular 中,RxJS 广泛应用于 HTTP 请求、表单处理、路由、组件间通信等场景。
RxJS(Reactive Extensions for JavaScript) 是一个用于处理异步数据流的库,提供了可观察对象(Observable)、观察者(Observer)、操作符(Operators)等功能。响应式编程的核心思想是将数据视为流(Stream),应用一系列操作符来组合、过滤和转换数据,从而实现对数据变化的响应。
Observable(可观察对象)
- Observable 是数据流的核心概念。它代表一个异步的数据源,可以是 HTTP 请求、事件、计时器等。
- 使用
Observable.subscribe()来订阅数据流,当数据到达时观察者会被通知。
常用 RxJS 操作符
RxJS 提供了许多操作符来处理数据流。以下是一些常用操作符及其应用场景:
map- 数据转换
map 操作符用于将 Observable 数据流中的每个数据项转换为新的值。
示例:将获取到的用户数组中的每个用户对象映射为用户名。
import { map } from 'rxjs/operators';
this.http.get<any[]>('<https://api.example.com/users>') .pipe( map(users => users.map(user => user.name)) ) .subscribe(names => console.log(names));filter- 数据过滤
filter 用于过滤数据流中不符合条件的数据项。
示例:筛选出 isActive 为 true 的用户。
import { filter } from 'rxjs/operators';
this.http.get<any[]>('<https://api.example.com/users>') .pipe( map(users => users.filter(user => user.isActive)) ) .subscribe(activeUsers => console.log(activeUsers));switchMap- 取消旧的订阅,处理最新数据
switchMap 用于在每次接收到新数据时取消上一个未完成的 Observable,常用于处理嵌套请求或连续事件(如表单输入、路由参数变化)。
示例:根据用户输入的关键字自动搜索,并取消上一次未完成的请求。
import { switchMap, debounceTime } from 'rxjs/operators';import { FormControl } from '@angular/forms';
searchControl = new FormControl();
this.searchControl.valueChanges .pipe( debounceTime(300), // 防抖,避免过于频繁的请求 switchMap(query => this.http.get(`https://api.example.com/search?q=${query}`)) ) .subscribe(results => console.log(results));mergeMap- 并行处理
mergeMap 可以将每个数据项映射到一个新的 Observable,并行处理每个 Observable。
示例:并行请求多个用户的详细信息。
import { mergeMap } from 'rxjs/operators';
const userIds = [1, 2, 3];from(userIds) .pipe( mergeMap(id => this.http.get(`https://api.example.com/users/${id}`)) ) .subscribe(user => console.log(user));catchError- 错误处理
catchError 用于捕获数据流中的错误,进行相应处理。
示例:请求失败时返回一个默认值。
import { catchError } from 'rxjs/operators';import { of } from 'rxjs';
this.http.get('<https://api.example.com/data>') .pipe( catchError(error => { console.error('Error occurred:', error); return of([]); // 返回一个空数组作为默认值 }) ) .subscribe(data => console.log(data));RxJS 中的流控制
RxJS 提供了一些流控制操作符,例如 debounceTime 和 distinctUntilChanged,可以帮助处理用户输入、点击等事件流。
debounceTime- 防抖
debounceTime 用于控制数据的流速,在一定时间内没有新的数据到达时才发送数据,通常用于处理快速连续的输入。
示例:在用户停止输入 500 毫秒后执行搜索请求。
searchControl.valueChanges .pipe( debounceTime(500) ) .subscribe(value => console.log('Search:', value));distinctUntilChanged- 去重
distinctUntilChanged 会忽略与上一次相同的数据项,避免重复处理。
示例:用户输入相同内容时,不重复发送请求。
searchControl.valueChanges .pipe( debounceTime(500), distinctUntilChanged() ) .subscribe(value => console.log('Unique search:', value));RxJS 在 Angular 中的应用场景
- HTTP 请求:通过
HttpClient的返回值使用 RxJS 操作符,方便地处理请求数据和错误。 - 路由参数变化:监听路由参数变化,进行依赖请求。
- 表单输入处理:处理用户输入,进行防抖、去重等操作。
- 组件间通信:通过 Subject 实现组件之间的事件或数据传递。
示例:基于 RxJS 构建实时搜索
以下是一个完整示例,展示了如何使用 switchMap、debounceTime 和 distinctUntilChanged 构建一个实时搜索功能:
import { Component } from '@angular/core';import { FormControl } from '@angular/forms';import { HttpClient } from '@angular/common/http';import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@Component({ selector: 'app-search', template: ` <input [formControl]="searchControl" placeholder="Search..."> <ul> <li *ngFor="let result of results">{{ result.name }}</li> </ul> `})export class SearchComponent { searchControl = new FormControl(); results: any[] = [];
constructor(private http: HttpClient) { this.searchControl.valueChanges .pipe( debounceTime(300), distinctUntilChanged(), switchMap(query => this.http.get<any[]>(`https://api.example.com/search?q=${query}`)) ) .subscribe(data => this.results = data); }}在这个示例中:
debounceTime(300)防抖,用户停止输入 300 毫秒后才会发送请求。distinctUntilChanged()避免重复请求相同的搜索词。switchMap每次有新输入时会取消之前的请求,避免频繁网络请求的性能消耗。
状态管理
在复杂的 Angular 应用中,状态管理是一个重要的主题。良好的状态管理能够帮助应用在不同组件和模块之间共享数据、同步状态、简化数据流、提高应用的可维护性。Angular 提供了多种方式进行状态管理,最常见的方式包括通过服务共享数据、使用 @ngrx/store 等库进行集中式管理。
状态管理 是一种在应用中管理和共享状态(数据)的方法,帮助我们更好地控制数据流、更新应用中的 UI。当应用变得复杂,包含多个交互式页面、模块、用户操作等,不同组件可能需要访问和更新相同的数据(例如用户信息、购物车数据等),状态管理可以帮助我们保持数据一致性并减少数据同步的复杂性。
Angular 中的状态管理方式
- 使用服务进行状态共享
Angular 的服务在应用中是单例的,可以通过服务在多个组件之间共享状态,适合不需要复杂状态逻辑的小型应用。
示例:创建一个简单的 UserService,用于管理用户信息。
import { Injectable } from '@angular/core';import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root'})export class UserService { private userSource = new BehaviorSubject<User | null>(null); // 存储用户信息 user$ = this.userSource.asObservable(); // 公开用户信息为 Observable
setUser(user: User) { this.userSource.next(user); // 更新用户信息 }
clearUser() { this.userSource.next(null); // 清除用户信息 }}在组件中,可以通过 UserService 访问和更新用户状态:
@Component({ selector: 'app-profile', template: `<div *ngIf="user$ | async as user">{{ user.name }}</div>`})export class ProfileComponent { user$ = this.userService.user$;
constructor(private userService: UserService) {}}在另一个组件中更新用户信息:
@Component({ selector: 'app-login', template: `<button (click)="login()">Login</button>`})export class LoginComponent { constructor(private userService: UserService) {}
login() { this.userService.setUser(user); // 更新用户信息 }}- 使用
@ngrx/store进行集中式状态管理
对于大型应用,可以使用 @ngrx/store 库进行集中式状态管理。@ngrx/store 实现了 Redux 模式,能够将所有应用状态集中管理,并通过单一数据源来同步和更新状态。
@ngrx/store的核心概念- Store:存储应用的全局状态。所有组件都可以从
Store获取和更新数据。 - Actions:触发状态改变的事件,用于描述要进行的状态更新。
- Reducers:处理
Actions的逻辑,根据不同的Actions更新Store中的状态。 - Selectors:从
Store中获取所需的状态数据。
- Store:存储应用的全局状态。所有组件都可以从
- 安装
@ngrx/store
首先,使用 Angular CLI 安装 @ngrx/store:
ng add @ngrx/store- 创建一个状态管理示例
假设我们要管理一个简单的计数器状态,包括 increment 和 decrement 两个操作。
1. **定义 Action**
在 `counter.actions.ts` 文件中定义计数器的 Action:
```typescript import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter] Increment'); export const decrement = createAction('[Counter] Decrement'); export const reset = createAction('[Counter] Reset'); ```
2. **定义 Reducer**
在 `counter.reducer.ts` 文件中定义计数器的 Reducer:
```typescript import { createReducer, on } from '@ngrx/store'; import { increment, decrement, reset } from './counter.actions';
export const initialState = 0;
const _counterReducer = createReducer( initialState, on(increment, state => state + 1), on(decrement, state => state - 1), on(reset, state => initialState) );
export function counterReducer(state: any, action: any) { return _counterReducer(state, action); } ```
3. **注册 Reducer**
在应用的 `app.module.ts` 中,将 Reducer 注册到 Store:
```typescript import { StoreModule } from '@ngrx/store'; import { counterReducer } from './counter.reducer';
@NgModule({ imports: [ StoreModule.forRoot({ count: counterReducer }) ], bootstrap: [AppComponent] }) export class AppModule {} ```
4. **使用 Store 在组件中管理状态**
在组件中使用 Store 获取和更新计数器状态:
```typescript import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { increment, decrement, reset } from './counter.actions';
@Component({ selector: 'app-counter', template: ` <p>Count: {{ count$ | async }}</p> <button (click)="increment()">Increment</button> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset</button> ` }) export class CounterComponent { count$ = this.store.select('count'); // 获取 count 状态
constructor(private store: Store<{ count: number }>) {}
increment() { this.store.dispatch(increment()); }
decrement() { this.store.dispatch(decrement()); }
reset() { this.store.dispatch(reset()); } } ```
在这个示例中:
- **Actions** 定义了增减和重置计数器的操作。- **Reducer** 根据不同的 `Action` 更新状态。- **Store** 提供了 `count` 状态,组件可以从 Store 订阅状态数据并进行相应的操作。状态管理的最佳实践
- 集中管理应用状态:将共享状态集中存储在服务或 Store 中,避免状态在不同组件中重复存储。
- 避免直接修改状态:通过 Action 和 Reducer 进行状态更新,确保状态变化是可追溯的。
- 分离 UI 和业务逻辑:组件负责 UI 呈现,服务或 Store 负责管理业务逻辑和数据状态。
- 使用 Selectors:使用 Selectors 获取 Store 中的数据,保持数据访问的简单性和一致性。
优化和性能调优
在构建和部署 Angular 应用时,性能优化是确保应用快速加载和响应的关键步骤。Angular 提供了多种优化技术,如懒加载、AOT(提前编译)、Tree Shaking、变化检测策略等。本节将介绍这些优化技巧,帮助你提高应用的性能。
懒加载(Lazy Loading)
懒加载是一种按需加载模块的技术。它允许应用在用户需要时才加载特定模块,避免应用启动时加载所有内容,从而减少首屏加载时间,提升应用的启动速度。
实现懒加载
假设有一个 AdminModule 模块,我们可以在路由中配置懒加载来实现按需加载。
示例:
import { Routes } from '@angular/router';
const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },];在这里,当用户导航到 /admin 路径时,Angular 才会加载 AdminModule 模块。
AOT 编译(Ahead-Of-Time Compilation)
AOT 编译将 Angular 的模板在构建时提前编译为 JavaScript 代码,而不是在浏览器中实时编译。这可以减少浏览器的工作量,减少应用包大小并提高运行速度。
使用 AOT 编译
使用 Angular CLI 构建应用时,AOT 编译是默认开启的,可以通过 ng build --prod 命令构建生产环境版本,启用 AOT 和其他优化。
ng build --prodAOT 编译的好处:
- 提高运行速度:减少浏览器的编译时间。
- 更小的包大小:只打包已编译的模板代码。
- 检测模板错误:在构建时发现模板语法错误,增加代码的安全性。
Tree Shaking
Tree Shaking 是一种在构建过程中移除未使用代码的技术。Angular 使用 Webpack 构建,它会自动移除未使用的模块和代码,减小应用的包体积。
如何优化 Tree Shaking
- 使用 ES6 模块:确保代码使用 ES6 模块语法(
import和export)。 - 移除不必要的依赖:确保只引入和使用必要的库或模块。
- 优化 RxJS 导入:RxJS 库可以按需引入。例如,使用
import { map } from 'rxjs/operators'而不是导入整个rxjs库。
变化检测策略(Change Detection Strategy)
Angular 默认的变化检测机制会检测所有组件的变化,可能导致性能开销。可以使用 OnPush 策略优化变化检测,使组件只在输入数据发生变化或组件内部事件触发时才进行检测。
使用 OnPush 策略
在组件中设置 changeDetection: ChangeDetectionStrategy.OnPush 来启用 OnPush 策略。
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({ selector: 'app-my-component', templateUrl: './my-component.component.html', changeDetection: ChangeDetectionStrategy.OnPush // 启用 OnPush 策略})export class MyComponent {}使用 OnPush 策略的好处:
- 减少不必要的变化检测:只有在输入数据或事件触发时才会重新渲染。
- 提高性能:适合纯展示型组件,不依赖父组件频繁更新的数据。
使用 Service Workers 进行缓存
Angular 支持将应用配置为 渐进式 Web 应用(PWA),利用 Service Worker 缓存资源以提高离线支持和性能。
添加 Service Worker 支持
使用 Angular CLI 添加 Service Worker:
ng add @angular/pwa这会自动生成 Service Worker 配置文件并注册到应用中。在生产环境中,Service Worker 会自动缓存静态资源,加快页面加载速度。
优化图片和静态资源
在 Web 应用中,图片和静态资源通常占据较大体积,可以通过压缩和延迟加载等方式优化资源。
图片压缩和延迟加载
- 使用压缩格式:尽量使用体积较小的图片格式(如 WebP)。
- 懒加载图片:可以通过
loading="lazy"属性实现延迟加载,只有当图片进入视口时才加载。
<img src="image.webp" loading="lazy" alt="Example Image">使用 Angular 的内置优化工具
Angular CLI 提供了许多内置工具,帮助优化应用的包体积和性能。通过以下方式可以进一步优化构建:
ng build --prod 的默认优化
使用 ng build --prod 时,Angular CLI 会自动应用多种优化,包括:
- AOT 编译:提前编译模板。
- Tree Shaking:移除未使用代码。
- Minification:压缩代码。
- Bundle Splitting:分割代码,减少单个包的大小。
提升 Angular 性能的最佳实践
- 按需加载模块:利用懒加载按需加载特定模块,减少主包大小。
- 使用纯展示型组件:在没有状态依赖的组件上启用
OnPush策略,减少变化检测次数。 - 避免在模板中进行复杂计算:在组件类中进行计算,将结果传递到模板中。
- 优化第三方库的引入:使用按需导入的方式引入第三方库(如 RxJS 操作符)。
- 使用 Web Workers:将计算密集型任务交给 Web Worker,避免阻塞主线程。
PWA 和国际化
在本节中,我们将介绍两个重要的进阶主题:渐进式 Web 应用(PWA) 和 国际化(i18n)。PWA 可以让 Angular 应用具备离线支持和原生应用的体验,而国际化则使应用能够支持多语言,方便在全球范围内使用。
渐进式 Web 应用(PWA)
渐进式 Web 应用(PWA) 是一种利用现代 Web 技术构建的应用,使得 Web 应用能够像原生应用一样流畅运行,并具有离线功能。Angular 提供了内置的 PWA 支持,可以帮助开发者轻松构建具有离线支持、推送通知等功能的 Web 应用。
- 将 Angular 应用转换为 PWA
Angular CLI 提供了简便的命令来将现有项目转换为 PWA 应用。
-
添加 PWA 支持
在项目目录中运行以下命令:
ng add @angular/pwa执行命令后,Angular 会自动生成 Service Worker 配置文件(
ngsw-config.json)以及应用的图标(manifest.webmanifest文件)。这些文件可以配置应用的离线缓存和图标等 PWA 设置。 -
配置
ngsw-config.jsonngsw-config.json是 Service Worker 的配置文件,用于定义哪些文件会被缓存。默认情况下,Angular 会缓存应用的主要资源(如 JavaScript 文件、CSS 文件、HTML 文件等)。可以根据需要自定义缓存策略。 -
构建生产版本
使用 PWA 时,应用需要以生产模式运行:
ng build --prod -
部署
将应用部署到服务器后,浏览器会自动检测并注册 Service Worker,使应用具备离线功能。
- 验证 PWA 功能
- 打开应用后,在浏览器的开发者工具中检查是否成功注册了 Service Worker。
- 可以尝试关闭网络,然后刷新页面,应用仍然应该能够加载离线缓存的内容。
国际化
国际化 是将应用中的文本、日期格式、数字格式等内容转换为不同语言和地区格式的过程,使应用可以适应不同的语言和文化背景。Angular 提供了内置的国际化支持,帮助开发者轻松添加多语言。
- 使用 Angular 的 i18n 功能
Angular 的 i18n 功能可以帮助将应用文本进行多语言翻译。以下是配置步骤:
- 标记文本
在模板中使用 i18n 属性标记需要翻译的文本。
<h1 i18n="@@welcome">Welcome to our app!</h1><p i18n="@@intro">This is an example of internationalized content.</p>在这里,i18n="@@key" 用于给文本内容添加一个唯一的翻译键 key,方便后续查找和翻译。
- 提取翻译文件
使用 Angular CLI 提取翻译文件。运行以下命令会在 src/locale 目录下生成一个 messages.xlf 文件:
ng extract-i18n生成的 messages.xlf 是一个 XML 文件,包含应用中所有标记的翻译内容。
- 翻译内容
在 messages.xlf 文件中为每种语言创建翻译文件,翻译内容并保存文件。例如,可以创建 messages.fr.xlf 文件用于法语翻译。
<trans-unit id="welcome" datatype="html"> <source>Welcome to our app!</source> <target>Bienvenue dans notre application!</target></trans-unit>
<trans-unit id="intro" datatype="html"> <source>This is an example of internationalized content.</source> <target>Ceci est un exemple de contenu internationalisé.</target></trans-unit>- 配置多语言编译
在 angular.json 文件中添加多语言配置。例如:
"projects": { "your-app-name": { "i18n": { "sourceLocale": "en", "locales": { "fr": "src/locale/messages.fr.xlf" } } }}- 构建多语言版本
在构建应用时,可以为不同语言生成不同版本:
ng build --prod --localize这样会自动为每个语言生成对应的版本(例如 dist/your-app-name/fr 目录下会生成法语版本的应用)。
- 动态切换语言(可选)
如果希望在运行时动态切换语言,可以使用第三方库(如 ngx-translate)实现更灵活的国际化支持。
Creating a Project
npm install -g @angular/cling new my-angular-projectcd my-angular-projectng serveng serve --port 8081
# access in localhost:4200Angular CLI
Generating files
The Angular CLI provides quick commands to generate various files, keeping the project structure consistent. Common generation commands include:
-
Generate a component:
ng generate component component-name# or shorthandng g c component-nameThis will create a component containing HTML, CSS, TypeScript, and test files.
-
Generate a service:
ng generate service service-name# shorthandng g s service-nameThe generated service file by default includes an injector and can be easily used by components.
-
Generate a module:
ng generate module module-name# shorthandng g m module-nameThe generated module helps you divide code logic by feature modules, facilitating code management and lazy loading.
Build the Project
Use the ng build command to build the project:
ng buildThe built files are stored in the dist directory. To publish to production, you can use the following command:
ng build --prod- The
--prodoption enables production optimizations, such as code minification, obfuscation, and removal of debugging information.
Testing
The Angular CLI supports unit tests and end-to-end tests:
-
Unit tests: Run unit tests with the
ng testcommand, which uses Karma and Jasmine by default.ng testThis will open the test runner in a browser and update results in real time.
-
End-to-end tests: Run end-to-end tests with the
ng e2ecommand, which uses Protractor by default.ng e2eEnd-to-end tests simulate user behavior to ensure the application’s overall functionality.
Generating other structures like services, pipes, directives, etc.
Besides components and modules, the CLI also supports generating other Angular structures:
-
Generate a directive:
ng generate directive directive-name# shorthandng g d directive-name -
Generate a pipe:
ng generate pipe pipe-name# shorthandng g p pipe-name
Configuration and Optimization
- Environment configuration
Angular supports configuration files for different environments, by default including environment.ts (development) and environment.prod.ts (production). You can define more environment configurations in the angular.json file and choose different environments at build time:
ng build --configuration production- Debugging and monitoring
When using ng serve, the Angular CLI will watch for file changes and automatically rebuild the app. You can use the --source-map option to generate debugging information for browser debugging:
ng serve --source-map- Preloading and lazy-loading modules
In large applications, preloading and lazy-loading modules can improve performance. The Angular CLI natively supports lazy loading, helping you load modules on demand and reduce initial load time.
Other Useful CLI Commands
-
Update Angular project or dependencies:
ng updateThis command will check and update Angular and related dependencies.
-
Analyze build bundles: Build the app with the
--stats-jsonoption to generate analysis files for inspecting and optimizing bundle contents.ng build --prod --stats-json -
Add third-party libraries or tools: Using the
ng addcommand, you can quickly add third-party libraries and plugins. For example, add Angular Material:ng add @angular/material
angular.json Configuration File
angular.json is the global configuration file for the Angular project, containing all configuration information. Here you can adjust the build output path, environment configurations, the order in which styles and scripts are included, and more.
Basic Structure of an Angular Project
Project Directory Structure
When you create a new Angular project with ng new, the project structure will look like this:
my-angular-app/├── e2e/ # End-to-end test directory├── node_modules/ # Project dependencies directory├── src/ # Application source code directory│ ├── app/ # Core application directory│ │ ├── app.component.ts # Root component logic file│ │ ├── app.component.html # Root component template│ │ ├── app.component.css # Root component styles│ │ └── app.module.ts # Root module file│ ├── assets/ # Static resources directory│ ├── environments/ # Environment configuration directory│ ├── index.html # Main HTML file│ ├── main.ts # App entry point│ ├── polyfills.ts # Browser compatibility code│ ├── styles.css # Global styles file│ └── test.ts # Unit test entry├── angular.json # Angular project configuration├── package.json # Project dependencies and scripts├── tsconfig.json # TypeScript configuration└── README.md # Project documentationDirectory and File Details
src/Directory
src/ is the main directory that holds the application’s source code; all core code for the Angular app resides here.
-
app/Directory: This is the primary directory of the application, containing the root module and root component. As the project grows, you will create more components, services, modules, etc.app.component.ts: Defines the root component’s logic and acts as the starting point of the application.app.component.html: The root component’s template file for defining the HTML structure of the root component.app.component.css: The root component’s style file.app.module.ts: The root module file loaded when the application starts. Each Angular app has at least one root module.
-
assets/Directory: Stores static resources (like images, fonts, etc.). They are copied to the build directory during the build and can be accessed via relative paths. -
environments/Directory: Contains environment-specific configurations, such as development and production. By default, it includesenvironment.ts(development) andenvironment.prod.ts(production). You can load different configurations based on environment conditions. -
index.html: The app’s main HTML file. It is the entry point for the page, and Angular renders all components into this page. -
main.ts: The app’s main entry file. Angular starts execution from here.main.tsbootstraps the root moduleAppModuleand starts the Angular application. -
polyfills.ts: Used to load compatibility code for different browsers to ensure the app runs consistently across browsers. -
styles.css: Global styles file where you can define styles that apply to the entire application. -
test.ts: Test entry file used to configure and initialize unit tests.
e2e/Directory
The e2e/ directory is used to store end-to-end test code. It defaults to the Protractor framework to run those tests, simulating user behavior and testing the app’s overall functionality.
- Other root-level files
-
angular.json: The Angular project’s configuration file, including configurations for building and the development server. You can adjust the output path, environment configurations, the order of including styles and scripts, and more. -
package.json: Node.js project’s configuration file, including dependencies and scripts. All dependencies and CLI commands are defined and managed here. -
tsconfig.json: TypeScript configuration file, defining rules for compiling TypeScript code. -
README.md: Project documentation, where you can include the project description, installation steps, and usage.
node_modules/Directory
node_modules/ holds the project dependencies installed by npm. All Angular, TypeScript, compiler, and related libraries live here.
Components and Modules
In Angular, components and modules are the core building blocks of an application. Components are responsible for building parts of the UI, while modules help organize and manage these components.
Component
A component is the fundamental building block of an Angular application. A component typically consists of three parts:
- Template: Defines the HTML structure of the component.
- Styles: Defines the CSS styles for the component.
- Class: Defines the component’s behavior and data.
- Creating a component
The Angular CLI provides commands to generate components:
ng generate component component-name# or shorthandng g c component-nameThis command will create a new component directory under the app directory, containing the following files:
component-name.component.ts— the component’s logic file, containing the component class and decorator.component-name.component.html— the component’s template file.component-name.component.css— the component’s style file.component-name.component.spec.ts— the component’s test file.
- Structure of a component
In the component-name.component.ts file, the component is defined using the @Component decorator, with the structure as follows:
import { Component } from '@angular/core';
@Component({ selector: 'app-component-name', // component selector, used to reference the component in templates templateUrl: './component-name.component.html', // component template file styleUrls: ['./component-name.component.css'] // component style file})export class ComponentNameComponent { title = 'Hello, Angular'; // component properties and methods}selector: The component’s selector, used to reference the component in other templates. For example,<app-component-name></app-component-name>.templateUrlandstyleUrls: Paths to the component’s template and style files, respectively.
- Data binding in a component
Angular provides several data binding methods:
- Interpolation: Used to display the value of a component property, e.g.
{{ title }}. - Property binding: Bind a property value to an HTML element attribute using
[], e.g.<img [src]="imageUrl">. - Event binding: Bind events in the view to component methods using
(), e.g.<button (click)="onClick()">Click</button>. - Two-way data binding: Use
[(ngModel)]to synchronize data between the form and the view (requires importingFormsModule).
Module
Modules are used to organize and manage an application’s components, directives, pipes, and services. Every Angular app has at least one root module, defined in AppModule in app.module.ts.
- Module structure
In the app.module.ts file, the module is defined with the @NgModule decorator, as follows:
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { AppComponent } from './app.component';import { ComponentNameComponent } from './component-name/component-name.component';
@NgModule({ declarations: [ AppComponent, ComponentNameComponent // declare components in the module ], imports: [ BrowserModule // import other modules ], providers: [], // declare service providers bootstrap: [AppComponent] // root component to bootstrap})export class AppModule { }declarations: The components, directives, and pipes declared in the module. Only components declared in the module can be used.imports: Import other modules, e.g.,BrowserModuleis the core module for browser apps.providers: Declare the services to be provided for the application.bootstrap: The root component to load when the application starts; typicallyAppComponent.
- Feature modules
In larger applications, you can create multiple feature modules to organize code, making it easier to separate concerns and enable lazy loading.
ng generate module feature-module# or shorthandng g m feature-moduleComponents and Modules Relationships
- Modules organize components: In Angular, modules manage and organize components; a module can contain multiple components.
- Component reusability: A component can be declared and used in multiple modules but must first import the module that contains the component.
- Root module and feature modules: The root module (e.g.,
AppModule) is responsible for bootstrapping the application, while feature modules organize specific features.
Data Binding
Interpolation
Data binding uses the {{ }} syntax to bind data, typically used to display component property values in HTML.
Example: Define a property title in the component class, then display it in the template using interpolation.
// In the component's TypeScript fileexport class MyComponent { title = 'Hello, Angular!';}<!-- In the component's HTML template --><h1>{{ title }}</h1>Here, {{ title }} will be replaced with Hello, Angular!. Interpolation is usually used to display text content.
Property Binding
Property binding uses square brackets [] to bind a component property’s value to an HTML element attribute, such as src, href, disabled, etc.
Example: Suppose we have an image URL and bind it to the src attribute of an img element.
// In the component's TypeScript fileexport class MyComponent { imageUrl = 'https://example.com/image.jpg';}<!-- In the component's HTML template --><img [src]="imageUrl" alt="Example Image">Here, [src]="imageUrl" binds the value of imageUrl to the src attribute of the img element.
Event Binding
Event binding uses parentheses () to bind events in the view (such as click, mouseover) to methods in the component, triggering specific logic.
Example: We can bind a click event to trigger the component’s onClick method on a button.
// In the component's TypeScript fileexport class MyComponent { onClick() { console.log('Button clicked!'); }}<!-- In the component's HTML template --><button (click)="onClick()">Click Me</button>Here, (click)="onClick()" binds the click event, and clicking the button will execute the onClick method and log "Button clicked!".
Two-way Data Binding
Two-way data binding uses the [(ngModel)] syntax to synchronize data in both directions. It allows user input to automatically update the component property, and changes to the component property are automatically reflected in the view. Two-way binding is commonly used with form inputs and user input scenarios.
Example: Use two-way data binding in an input field, binding the name property to the value of the input element. Requires importing FormsModule.
// In the component's TypeScript fileexport class MyComponent { name = '';}<!-- In the component's HTML template --><input [(ngModel)]="name" placeholder="Enter your name"><p>Hello, {{ name }}!</p>Here, [(ngModel)]="name" implements two-way data binding. User input in the field updates the name property, and the value of name is immediately displayed on the page.
Note: Using two-way binding requires importing
FormsModulein the application module, otherwise an error will occur.
import { FormsModule } from '@angular/forms';
@NgModule({ imports: [FormsModule, ... ]})export class AppModule { }Benefits of Data Binding
- Reduces manual DOM updates by binding data
- Real-time synchronization between data and view
- Improves maintainability by separating data from the presentation
Directives
Directives are a very important feature in Angular, allowing us to manipulate DOM elements in templates, control styling, structure, and behavior. Directives make Angular templates more dynamic and flexible.
Types of Directives
There are three main directive types in Angular:
- Component directives: Components are essentially a specialized form of directives with templates and styles.
- Structural Directives: Used to add or remove DOM elements or change the layout. Common ones include
ngIfandngFor. - Attribute Directives: Used to change the appearance or behavior of elements without changing their structure, such as
ngClassandngStyle.
Structural Directives
Structural directives add, remove, or replace DOM elements. They require a leading * when used.
-
ngIfshows or hides a DOM element based on a condition.<div *ngIf="isVisible">This is visible only if isVisible is true.</div> -
ngForiterates over an array to render a collection of DOM elements.<ul><li *ngFor="let item of items">{{ item }}</li></ul> -
ngSwitchcan render different elements based on different conditions, typically for multiple-case logic.<div [ngSwitch]="value"><p *ngSwitchCase="'one'">Value is one</p><p *ngSwitchCase="'two'">Value is two</p><p *ngSwitchDefault>Value is unknown</p></div>ngSwitchCasematches a specific value, andngSwitchDefaultrepresents the default case.
Attribute Directives
Attribute directives change the appearance or behavior of DOM elements without creating or removing elements.
-
ngClassis used to dynamically set CSS classes on an element.<div [ngClass]="{ 'active': isActive, 'highlight': isHighlighted }">Styled div</div>Here, the
activeandhighlightclasses are added dynamically based onisActiveandisHighlighted. -
ngStyleis used to dynamically set inline styles on an element.<div [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px' }">Styled div</div>In this example,
textColorandfontSizedynamically control the element’s color and font size.
Custom Directives
In addition to Angular’s built-in directives, you can create custom directives to implement specific behavior. Typically, custom directives are attribute directives used to extend element behavior.
- Create a custom directive
Using the Angular CLI to create a directive, the command is:
ng generate directive highlight# or shorthandng g d highlightThis command will generate a directive file highlight.directive.ts with initial content like:
import { Directive, ElementRef, Renderer2, HostListener } from '@angular/core';
@Directive({ selector: '[appHighlight]'})export class HighlightDirective { constructor(private el: ElementRef, private renderer: Renderer2) {}
@HostListener('mouseenter') onMouseEnter() { this.highlight('yellow'); }
@HostListener('mouseleave') onMouseLeave() { this.highlight(null); }
private highlight(color: string) { this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', color); }}In this example, the custom HighlightDirective directive will set the background color to yellow when the mouse hovers over the element and remove the color when it leaves.
-
@Directivedecorator: Defines a directive;selector: '[appHighlight]'indicates it is an attribute directive, used by applying theappHighlightattribute to an element. -
ElementRef: Used to access the DOM element the directive is applied to. -
Renderer2: Used to safely manipulate DOM styles, avoiding direct DOM access. -
@HostListener: Listens to the element’s events,mouseenterfor mouse enter andmouseleavefor mouse leave. -
Using the custom directive
In the template, use the appHighlight directive:
<p appHighlight>Hover over this text to see the highlight effect.</p>After adding the appHighlight attribute, the directive will take effect on the element. When the mouse hovers over it, the background becomes yellow; when it leaves, it returns to normal.
Services and Dependency Injection
In Angular, services are used to encapsulate and share logic and data across an application, while the dependency injection (DI) system manages and provides these services. Using services helps separate business logic from components, improving reusability and maintainability. The DI system ensures services can be easily used by components or other services.
Services
A service in Angular is typically a class that encapsulates logic and data that don’t belong to any particular component. For example, fetching data, handling business logic, and managing state. Services can be shared across multiple components.
- Creating a service
The Angular CLI can quickly generate a service:
ng generate service my-service# or shorthandng g s my-serviceThe generated service file my-service.service.ts is roughly as follows:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' // registers the service in the root injector, globally available})export class MyService { constructor() { }
getData() { return 'Hello from MyService!'; }}@Injectabledecorator: Marks a class as injectable into components or other services.providedIn: 'root': Indicates the service is provided in the root injector, making it accessible throughout the app. This avoids manual registration inproviders.
- Using a service in a component
Services are typically used via dependency injection. You can inject the service into a component’s constructor so you can call the service’s methods.
Suppose we have created a service named MyService. Here’s how to use it in a component:
import { Component, OnInit } from '@angular/core';import { MyService } from './my-service.service';
@Component({ selector: 'app-my-component', template: `<p>{{ message }}</p>`})export class MyComponent implements OnInit { message: string;
// Inject the service in the constructor constructor(private myService: MyService) {}
ngOnInit(): void { this.message = this.myService.getData(); }}- Constructor injection: Define a private
myServicevariable in the component’s constructor to inject the service. - Calling service methods: In the
ngOnInitlifecycle hook, callgetData()and assign its return value tomessage.
- Service scope and provision
Angular provides several ways to provide services, and different provisioning methods affect the service’s scope and lifecycle:
- Root-level provisioning
Providing the service in the root injector with providedIn: 'root' makes the service a singleton for the entire application. This is typically used for globally shared data or logic.
@Injectable({ providedIn: 'root'})export class MyService { }- Module-level provisioning
If you want the service to be available only within a specific module, register the service in that module’s providers array. This makes the service’s lifecycle align with the module, suitable for localized shared data or logic.
import { NgModule } from '@angular/core';import { MyService } from './my-service.service';
@NgModule({ providers: [MyService] // provide service at module level})export class MyModule { }- Component-level provisioning
If you want the service instance to be available only for a single component or the component’s children, register the service in the component’s providers array. This gives each component instance its own service instance, suitable for logic used only within a single component.
import { Component } from '@angular/core';import { MyService } from './my-service.service';
@Component({ selector: 'app-my-component', template: `<p>My Component</p>`, providers: [MyService] // provide service at the component level})export class MyComponent { }Dependency Injection
Dependency Injection (DI) is a design pattern where dependencies (such as services) are injected into components or other services, avoiding hard-coded dependencies. Angular’s DI system automatically manages the creation and provision of dependencies, simplifying the app structure.
- How the DI system works
When Angular detects that a class requires a specific dependency (for example, MyService), it looks for an instance of that dependency in the injector. If the instance doesn’t exist, it creates one and returns it to the component or service.
- Custom injectors
Angular supports custom injectors inside components to control how dependencies are provided. This is not commonly used in most apps, but it can be useful when you need to control the scope of a service or implement special dependencies.
Real-world use cases for services
- Data sharing: Store shared data in a service so that multiple components can access and update it.
- HTTP requests: Use the
HttpClientservice to fetch data from backend APIs and encapsulate the logic in services for reuse and testing. - Global state management: Manage app state in a service, such as user authentication information or theme settings.
Example: Creating a simple data service
Suppose we want to create a simple data service DataService to manage a set of user data and provide CRUD operations.
- Create the service
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root'})export class DataService { private users = ['Alice', 'Bob', 'Charlie'];
getUsers() { return this.users; }
addUser(user: string) { this.users.push(user); }}- Use the service in a component
import { Component } from '@angular/core';import { DataService } from './data.service';
@Component({ selector: 'app-user-list', template: ` <ul> <li *ngFor="let user of users">{{ user }}</li> </ul> <input [(ngModel)]="newUser" placeholder="Enter name"> <button (click)="addUser()">Add User</button> `})export class UserListComponent { users: string[]; newUser = '';
constructor(private dataService: DataService) { this.users = this.dataService.getUsers(); }
addUser() { if (this.newUser) { this.dataService.addUser(this.newUser); this.newUser = ''; } }}In this example, DataService provides the logic for managing user data, and the UserListComponent retrieves data from the service to display in the view, while also allowing new users to be added via the service.
Routing and Navigation
Angular’s router enables building single-page applications (SPAs) by controlling different views through URLs. The router allows the app to switch between views without refreshing the page, supports parameter passing, lazy loading, and route guards.
What is Routing?
In a single-page application, there is only one actual page, but users can navigate to different parts of the app via different URLs. Angular’s router lets you define mappings between URL paths and components. When clicking navigation links, the corresponding component is loaded without a full page refresh.
Setting up Angular Routing
- Import
RouterModule
To set up routing in an Angular app, import RouterModule in the app’s root module or relevant feature modules and define the route configuration.
For example, in standalone mode, you can configure routing in main.ts:
import { bootstrapApplication } from '@angular/platform-browser';import { AppComponent } from './app/app.component';import { provideRouter } from '@angular/router';import { HomeComponent } from './app/home/home.component';import { AboutComponent } from './app/about/about.component';
bootstrapApplication(AppComponent, { providers: [ provideRouter([ { path: '', component: HomeComponent }, // default route { path: 'about', component: AboutComponent } // /about route ]) ]});In this example, we defined two routes:
''represents the root path (/), mapped toHomeComponent.'about'represents the/aboutpath, mapped toAboutComponent.
- Create components
If you don’t have the related components yet, you can generate them with the following commands:
ng generate component home --standaloneng generate component about --standalone- Add route links in the template
Angular provides the routerLink directive to create route links. You can add navigation links in the template of AppComponent:
<nav> <a routerLink="/">Home</a> | <a routerLink="/about">About</a></nav><router-outlet></router-outlet>routerLink: Used to specify the path of the navigation link. For example,routerLink="/"points to the root path.<router-outlet></router-outlet>: Router outlet, specifies where in the page the routed component should be displayed. The<router-outlet>is a placeholder for the router to render the matched component.
- Route parameter passing
Angular routing supports passing parameters in the URL and receiving/processing them in components. For example, we can define a route to show user details:
- Define a parameterized route
In the route configuration, use :id as a placeholder to define the parameter:
{ path: 'user/:id', component: UserComponent }- Add links in the template
In the template, you can pass parameters with routerLink:
<a [routerLink]="['/user', 1]">User 1</a><a [routerLink]="['/user', 2]">User 2</a>- Retrieve parameters in the component
In UserComponent, you can use ActivatedRoute to access route parameters:
import { Component, OnInit } from '@angular/core';import { ActivatedRoute } from '@angular/router';
@Component({ selector: 'app-user', standalone: true, template: `<p>User ID: {{ userId }}</p>`})export class UserComponent implements OnInit { userId: string;
constructor(private route: ActivatedRoute) {}
ngOnInit(): void { this.userId = this.route.snapshot.paramMap.get('id')!; }}ActivatedRoute: Angular’s router service for accessing route information.paramMap: A map of route parameters.get('id')retrieves theidparameter from the current route.
Route Guards
Route guards protect routes, ensuring the user has permission to access them. Common route guards include:
CanActivate: Checks before navigating to a route, deciding whether to allow access.CanDeactivate: Checks when leaving a route, deciding whether to allow leaving.
For example, create a simple AuthGuard to ensure a user can access a route only after logging in.
- Generate the guard
Use the CLI to generate a guard:
ng generate guard auth- Implement the
AuthGuardlogic
In the generated auth.guard.ts, write the authentication logic:
import { Injectable } from '@angular/core';import { CanActivate, Router } from '@angular/router';
@Injectable({ providedIn: 'root'})export class AuthGuard implements CanActivate { constructor(private router: Router) {}
canActivate(): boolean { const isAuthenticated = false; // replace with real authentication logic
if (!isAuthenticated) { this.router.navigate(['/login']); return false; } return true; }}- Apply the guard to routes
In the route configuration, apply the guard using the canActivate property:
{ path: 'protected', component: ProtectedComponent, canActivate: [AuthGuard] }Lazy Loading
Lazy loading loads specific modules only when needed, improving the initial load time of the app. You can easily implement lazy loading for components with loadComponent:
{ path: 'lazy', loadComponent: () => import('./lazy/lazy.component').then(m => m.LazyComponent) }Forms Handling
Angular provides powerful form handling capabilities, supporting form creation and validation. Angular forms mainly come in two flavors: Template-driven Forms and Reactive Forms. Each has its advantages and is suitable for different scenarios.
Template-driven Forms
Template-driven forms define the form structure and validation logic primarily through the HTML template. They use Angular’s FormsModule to provide data binding and validation support.
- Import
FormsModule
First, in the module, import FormsModule. If your project is modular, open app.module.ts or the relevant module file, and add FormsModule.
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { FormsModule } from '@angular/forms';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, FormsModule // import FormsModule ], bootstrap: [AppComponent]})export class AppModule {}- Defining a template-driven form
Create a simple form in the template, using the ngModel directive to achieve two-way data binding. Use #name="ngModel" to create a template reference variable to access the input’s validation state.
<form #myForm="ngForm"> <label for="name">Name:</label> <input id="name" name="name" [(ngModel)]="user.name" required> <div *ngIf="!myForm.controls.name?.valid && myForm.controls.name?.touched"> Name is required. </div>
<label for="email">Email:</label> <input id="email" name="email" [(ngModel)]="user.email" required email> <div *ngIf="!myForm.controls.email?.valid && myForm.controls.email?.touched"> Valid email is required. </div>
<button [disabled]="!myForm.valid">Submit</button></form>-
[(ngModel)]: Two-way data binding used to synchronize form fields and component data. -
#myForm="ngForm": Create a template reference variablemyFormto access the form state. -
requiredandemailvalidations: HTML5 validations provided by Angular. -
Define the data model in the component
In app.component.ts, define the user data model, bound to the form.
import { Component } from '@angular/core';
@Component({ selector: 'app-root', templateUrl: './app.component.html'})export class AppComponent { user = { name: '', email: '' };}Reactive Forms
Reactive forms define the form structure and validation logic in the component class, offering more flexibility and suitable for complex dynamic forms. They use the ReactiveFormsModule to provide form controls and validation support.
- Import
ReactiveFormsModule
In the module, import ReactiveFormsModule.
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { ReactiveFormsModule } from '@angular/forms';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, ReactiveFormsModule // import ReactiveFormsModule ], bootstrap: [AppComponent]})export class AppModule {}- Define a reactive form
In the component, use FormBuilder to define the form structure and validation rules.
import { Component } from '@angular/core';import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({ selector: 'app-root', templateUrl: './app.component.html'})export class AppComponent { userForm: FormGroup;
constructor(private fb: FormBuilder) { this.userForm = this.fb.group({ name: ['', Validators.required], email: ['', [Validators.required, Validators.email]] }); }
onSubmit() { if (this.userForm.valid) { console.log(this.userForm.value); } }}-
FormBuilder: Angular-provided service to simplify form construction. -
Validators: Used to set validation rules for form fields. -
Binding a reactive form in the template
Bind the entire form to [formGroup] and each control with formControlName.
<form [formGroup]="userForm" (ngSubmit)="onSubmit()"> <label for="name">Name:</label> <input id="name" formControlName="name"> <div *ngIf="userForm.controls.name.invalid && userForm.controls.name.touched"> Name is required. </div>
<label for="email">Email:</label> <input id="email" formControlName="email"> <div *ngIf="userForm.controls.email.invalid && userForm.controls.email.touched"> Valid email is required. </div>
<button [disabled]="userForm.invalid">Submit</button></form>[formGroup]: Binds the component’suserFormto the template form.formControlName: Binds each form control to the corresponding control inuserForm.
Form Validation
Angular provides a variety of built-in validators, such as Validators.required, Validators.email, and you can also create custom validators.
- Custom validators
You can define a custom validator in the component and apply it to a form control.
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const forbidden = nameRe.test(control.value); return forbidden ? { forbiddenName: { value: control.value } } : null; };}Then apply the custom validator when creating the form:
this.userForm = this.fb.group({ name: ['', [Validators.required, forbiddenNameValidator(/bob/i)]], email: ['', [Validators.required, Validators.email]]});HTTP Client and API Communication
In modern web apps, communicating with backend APIs is essential. Angular provides the HttpClient module to simplify interactions with backend APIs. With HttpClient, you can easily send HTTP requests, handle responses, manage errors, and add interceptors to control requests and responses.
Setting up HttpClient
To use HttpClient in an Angular app, you need to import HttpClientModule in your module.
- Import
HttpClientModule
In the root or feature module, import HttpClientModule:
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { HttpClientModule } from '@angular/common/http';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, HttpClientModule // Import HttpClientModule ], bootstrap: [AppComponent]})export class AppModule {}- Use
HttpClientto send requests
HttpClient provides several methods to send HTTP requests, including get, post, put, delete, and more, suitable for different types of requests.
- Sending a GET request
Suppose we want to fetch a list of users from an API. We can use HttpClient.get to send a GET request:
import { HttpClient } from '@angular/common/http';import { Component, OnInit } from '@angular/core';
@Component({ selector: 'app-user-list', template: ` <ul> <li *ngFor="let user of users">{{ user.name }}</li> </ul> `})export class UserListComponent implements OnInit { users: any[] = [];
constructor(private http: HttpClient) {}
ngOnInit(): void { this.http.get<any[]>('https://jsonplaceholder.typicode.com/users') .subscribe(data => { this.users = data; }); }}In this example:
-
this.http.getsends a GET request. -
subscribehandles the response data, assigning the receiveddatato the component’susersproperty. -
Sending a POST request
If we want to send data to the server, use HttpClient.post:
addUser(newUser: any) { this.http.post('https://jsonplaceholder.typicode.com/users', newUser) .subscribe(response => { console.log('User added:', response); });}Here, the post method sends a POST request, passing the newUser data to the API. The response in the subscribe contains the server’s response.
Error Handling
In real applications, API requests may encounter various errors, like network timeouts or server issues. You can catch and handle errors using the catchError operator.
- Error handling example
Using the catchError operator to handle errors in requests:
import { HttpErrorResponse } from '@angular/common/http';import { catchError } from 'rxjs/operators';import { throwError } from 'rxjs';
this.http.get('https://jsonplaceholder.typicode.com/users') .pipe( catchError(this.handleError) ) .subscribe( data => console.log('Data:', data), error => console.error('Error:', error) );
handleError(error: HttpErrorResponse) { let errorMessage = 'Unknown error!'; if (error.error instanceof ErrorEvent) { // Client-side error errorMessage = `Error: ${error.error.message}`; } else { // Server-side error errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`; } return throwError(errorMessage);}Here:
- The
catchErroroperator catches errors and calls thehandleErrormethod. - The
handleErrormethod generates an error message based on the error type and returns it viathrowError.
HTTP Interceptors
HTTP interceptors allow you to inject logic before a request or response is processed, such as adding an authentication token or logging.
- Creating an interceptor
Use the Angular CLI to create an interceptor:
ng generate interceptor authThe interceptor file might look like:
import { Injectable } from '@angular/core';import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
@Injectable()export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) { // Clone the request and add an authentication token const authReq = req.clone({ setHeaders: { Authorization: `Bearer YOUR_TOKEN_HERE` } }); return next.handle(authReq); }}-
The
interceptmethod of theHttpInterceptorinterface runs before the request is sent. -
req.cloneclones the request object and adds the authentication token. -
Registering the interceptor
Register the interceptor as a multi-provider for HTTP_INTERCEPTORS in a module:
import { HTTP_INTERCEPTORS } from '@angular/common/http';import { AuthInterceptor } from './auth.interceptor';
@NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ]})export class AppModule {}Using RxJS for Asynchronous Handling
HttpClient methods return an Observable, which you subscribe to. You can use RxJS operators (such as map, switchMap) to process asynchronous data streams.
- RxJS operators example
If you want to process data after making a request, you can use the map operator:
import { map } from 'rxjs/operators';
this.http.get<any[]>('https://api.example.com/users') .pipe( map(users => users.map(user => user.name)) ) .subscribe(names => console.log(names));RxJS and Reactive Programming
RxJS is one of Angular’s core libraries for handling asynchronous events and data streams. Its power lies in providing a rich set of operators to efficiently manage streaming data and complex asynchronous operations. In Angular, RxJS is widely used for HTTP requests, forms, routing, and component communication.
RxJS (Reactive Extensions for JavaScript) is a library for handling asynchronous data streams, providing Observables, Observers, and Operators. The core idea of reactive programming is to treat data as a stream and apply a sequence of operators to compose, filter, and transform data, enabling responsive UI to data changes.
Observable
- Observable is the core concept of a data stream. It represents an asynchronous data source, which can be an HTTP request, events, timers, etc.
- Use
Observable.subscribe()to subscribe to the data stream; observers are notified when data arrives.
Common RxJS Operators
RxJS provides many operators to handle data streams. Here are some common operators and their use cases:
map- Data transformation
map transforms each item in the Observable data stream to a new value.
Example: Map the retrieved user array to an array of usernames.
import { map } from 'rxjs/operators';
this.http.get<any[]>('https://api.example.com/users') .pipe( map(users => users.map(user => user.name)) ) .subscribe(names => console.log(names));filter- Data filtering
filter is used to filter items in the data stream that do not meet a condition.
Example: Filter users with isActive set to true.
import { filter } from 'rxjs/operators';
this.http.get<any[]>('https://api.example.com/users') .pipe( map(users => users.filter(user => user.isActive)) ) .subscribe(activeUsers => console.log(activeUsers));switchMap- Cancel previous subscriptions, handle latest data
switchMap cancels the previous unfinished Observable when a new data item arrives, commonly used for nested requests or sequences of events (like form input or route parameter changes).
Example: Automatically search based on user input keywords and cancel the previous request.
import { switchMap, debounceTime } from 'rxjs/operators';import { FormControl } from '@angular/forms';
searchControl = new FormControl();
this.searchControl.valueChanges .pipe( debounceTime(300), // debounce to avoid overly frequent requests switchMap(query => this.http.get(`https://api.example.com/search?q=${query}`)) ) .subscribe(results => console.log(results));mergeMap- Parallel processing
mergeMap maps each data item to a new Observable and processes them in parallel.
Example: Parallel requests for multiple users’ details.
import { mergeMap } from 'rxjs/operators';import { from } from 'rxjs';
const userIds = [1, 2, 3];from(userIds) .pipe( mergeMap(id => this.http.get(`https://api.example.com/users/${id}`)) ) .subscribe(user => console.log(user));catchError- Error handling
catchError is used to catch errors in the data stream and handle accordingly.
Example: Return a default value when a request fails.
import { catchError } from 'rxjs/operators';import { of } from 'rxjs';
this.http.get('https://api.example.com/data') .pipe( catchError(error => { console.error('Error occurred:', error); return of([]); // return an empty array as a default value }) ) .subscribe(data => console.log(data));Flow control in RxJS
RxJS provides some flow-control operators, such as debounceTime and distinctUntilChanged, to help manage user input and other event streams.
debounceTime- Debouncing
debounceTime controls the rate of data emission, emitting data only after a pause for a specified time, typically used for handling rapid input sequences.
Example: Execute a search after the user stops typing for 500ms.
searchControl.valueChanges .pipe( debounceTime(500) ) .subscribe(value => console.log('Search:', value));distinctUntilChanged- Deduplication
distinctUntilChanged ignores the same data item as the previous one to avoid duplicate processing.
Example: Do not send repeated requests when the user types the same content.
searchControl.valueChanges .pipe( debounceTime(500), distinctUntilChanged() ) .subscribe(value => console.log('Unique search:', value));RxJS in Angular Use Cases
- HTTP requests: Use RxJS operators with
HttpClientto process responses and handle errors. - Route parameter changes: Listen to route parameter changes and trigger dependent requests.
- Form input handling: Process user input with debouncing, deduplication, etc.
- Inter-component communication: Use Subjects to enable event or data sharing between components.
Example: Real-time search with RxJS
The following is a complete example showing how to use switchMap, debounceTime, and distinctUntilChanged to build a real-time search:
import { Component } from '@angular/core';import { FormControl } from '@angular/forms';import { HttpClient } from '@angular/common/http';import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@Component({ selector: 'app-search', template: ` <input [formControl]="searchControl" placeholder="Search..."> <ul> <li *ngFor="let result of results">{{ result.name }}</li> </ul> `})export class SearchComponent { searchControl = new FormControl(); results: any[] = [];
constructor(private http: HttpClient) { this.searchControl.valueChanges .pipe( debounceTime(300), distinctUntilChanged(), switchMap(query => this.http.get<any[]>(`https://api.example.com/search?q=${query}`)) ) .subscribe(data => this.results = data); }}In this example:
debounceTime(300)debounces, waiting 300 milliseconds after the user stops typing before sending a request.distinctUntilChanged()prevents duplicate requests for the same search term.switchMapcancels the previous request whenever a new input arrives, avoiding excessive network requests.
State Management
In complex Angular applications, state management is a key topic. Good state management helps share data across components and modules, synchronize state, simplify data flow, and improve maintainability. Angular provides multiple ways to manage state, with common approaches including sharing state via services and using libraries like @ngrx/store for centralized management.
State management is a way of managing and sharing state (data) across an app, helping us better control data flow and update UI. As applications become more complex, with multiple interactive pages, modules, and user actions, different components may need to access and update the same data (e.g., user information, shopping cart). State management helps keep data consistent and reduces the complexity of data synchronization.
State management approaches in Angular
- Using services for state sharing
Angular services are singletons within the app and can share state across components, suitable for small apps that do not require complex state logic.
Example: Create a simple UserService to manage user information.
import { Injectable } from '@angular/core';import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root'})export class UserService { private userSource = new BehaviorSubject<User | null>(null); // stores user information user$ = this.userSource.asObservable(); // public user information as Observable
setUser(user: User) { this.userSource.next(user); // update user information }
clearUser() { this.userSource.next(null); // clear user information }}In a component, you can access and update user state via UserService:
@Component({ selector: 'app-profile', template: `<div *ngIf="user$ | async as user">{{ user.name }}</div>`})export class ProfileComponent { user$ = this.userService.user$;
constructor(private userService: UserService) {}}In another component, update user information:
@Component({ selector: 'app-login', template: `<button (click)="login()">Login</button>`})export class LoginComponent { constructor(private userService: UserService) {}
login() { this.userService.setUser(user); // update user information }}- Using
@ngrx/storefor centralized state management
For large applications, you can use the @ngrx/store library for centralized state management. @ngrx/store implements a Redux-like pattern, enabling all application state to be managed in one place and synchronized via a single data source.
-
Core concepts of
@ngrx/store- Store: Holds the global state of the application. All components can access and update data from the Store.
- Actions: Events that trigger state changes, describing what updates to perform.
- Reducers: Handle the logic for Actions, updating the state in the Store accordingly.
- Selectors: Retrieve required data from the Store.
-
Installing
@ngrx/store
First, install @ngrx/store with the Angular CLI:
ng add @ngrx/store- Creating a state management example
Suppose we want to manage a simple counter state with increment and decrement actions.
1. **Define Action**
In `counter.actions.ts`, define the counter actions:import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');export const decrement = createAction('[Counter] Decrement');export const reset = createAction('[Counter] Reset');2. **Define Reducer**
In `counter.reducer.ts`, define the counter reducer:import { createReducer, on } from '@ngrx/store';import { increment, decrement, reset } from './counter.actions';
export const initialState = 0;
const _counterReducer = createReducer( initialState, on(increment, state => state + 1), on(decrement, state => state - 1), on(reset, state => initialState));
export function counterReducer(state: any, action: any) { return _counterReducer(state, action);}3. **Register Reducer**
In the app's `app.module.ts`, register the Reducer with the Store:import { StoreModule } from '@ngrx/store';import { counterReducer } from './counter.reducer';
@NgModule({ imports: [ StoreModule.forRoot({ count: counterReducer }) ], bootstrap: [AppComponent]})export class AppModule {}4. **Use Store in a component to manage state**
Use the Store in a component to get and update the counter state:import { Component } from '@angular/core';import { Store } from '@ngrx/store';import { increment, decrement, reset } from './counter.actions';
@Component({ selector: 'app-counter', template: ` <p>Count: {{ count$ | async }}</p> <button (click)="increment()">Increment</button> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset</button> `})export class CounterComponent { count$ = this.store.select('count'); // get count state
constructor(private store: Store<{ count: number }>) {}
increment() { this.store.dispatch(increment()); }
decrement() { this.store.dispatch(decrement()); }
reset() { this.store.dispatch(reset()); }}In this example:
- Actions define the increment, decrement, and reset operations.
- Reducer updates the state based on different Actions.
- Store provides the
countstate, and components can subscribe to it and dispatch actions.
Best Practices for State Management
- Centralize application state: Store or service shared state to avoid duplicating state across components.
- Avoid directly mutating state: Use Actions and Reducers to update state, ensuring changes are traceable.
- Separate UI and business logic: Components handle UI rendering, services or Store handle business logic and data state.
- Use Selectors: Retrieve data from the Store using Selectors for simpler and consistent data access.
Optimization and Performance Tuning
When building and deploying Angular apps, performance optimization is key to fast loading and responsive applications. Angular provides various optimization techniques, such as lazy loading, AOT (Ahead-of-Time) compilation, Tree Shaking, and change detection strategies. This section introduces these optimization techniques to help you boost app performance.
Lazy Loading
Lazy loading is a technique for loading modules on demand. It allows the app to load specific modules only when needed, reducing the initial load time and speeding up the startup.
Implementing lazy loading
Suppose there is an AdminModule that we want to lazy-load. Configure lazy loading in the routes:
import { Routes } from '@angular/router';
const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },];Here, Angular will load the AdminModule only when the user navigates to the /admin path.
AOT Compilation
AOT compilation compiles Angular templates into JavaScript code at build time rather than in the browser, reducing the browser’s work, shrinking bundle size, and speeding up runtime.
Using AOT compilation
When building the app with the Angular CLI, AOT compilation is enabled by default. You can build a production-ready version with ng build --prod, which enables AOT and other optimizations.
ng build --prodBenefits of AOT compilation:
- Faster runtime performance: reduces the browser’s compilation time
- Smaller bundle sizes: only compiled template code is included
- Early template error detection: catches template syntax errors during build for better safety
Tree Shaking
Tree Shaking is a technique to remove unused code during the build process. Angular uses Webpack to build and automatically removes unused modules and code, reducing the application bundle size.
How to optimize Tree Shaking
- Use ES6 modules: Ensure code uses ES6 module syntax (
importandexport). - Remove unnecessary dependencies: Make sure you only import and use necessary libraries or modules.
- Optimize RxJS imports: RxJS can be imported in a tree-shakable way. For example, use
import { map } from 'rxjs/operators'instead of importing the entirerxjslibrary.
Change Detection Strategy
Angular’s default change detection checks all components, which can incur performance overhead. You can optimize change detection by using the OnPush strategy, which triggers checks only when input data changes or component events occur.
Using OnPush
In a component, set changeDetection: ChangeDetectionStrategy.OnPush to enable the OnPush strategy.
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({ selector: 'app-my-component', templateUrl: './my-component.component.html', changeDetection: ChangeDetectionStrategy.OnPush // enable OnPush strategy})export class MyComponent {}Benefits of OnPush:
- Reduces unnecessary change detection: re-runs only when inputs change or events occur
- Improves performance: suitable for purely presentational components that do not rely on frequent updates from parent components
Using Service Workers for caching
Angular supports configuring the app as a Progressive Web App (PWA) to leverage Service Worker caching for offline support and performance.
- Adding Service Worker support
Use the Angular CLI to add Service Worker support:
ng add @angular/pwaThis will automatically generate the Service Worker configuration file (ngsw-config.json) and register it with the app. In production, the Service Worker will cache static resources automatically, speeding up page loads.
Optimizing Images and Static Resources
Images and static assets often contribute significantly to the payload. You can optimize resources with compression and lazy loading.
Image compression and lazy loading
- Use compressed formats (e.g., WebP) when possible
- Lazy-load images using the
loading="lazy"attribute so they are only loaded when they enter the viewport
<img src="image.webp" loading="lazy" alt="Example Image">Using Angular’s Built-in Optimization Tools
Angular CLI offers many built-in tools to help optimize bundle size and performance. You can further optimize builds as follows:
Default optimization of ng build --prod
When building with ng build --prod, Angular CLI automatically applies multiple optimizations, including:
- AOT compilation
- Tree Shaking
- Minification
- Bundle Splitting
Best Practices for Improving Angular Performance
- Lazy-load modules to reduce the main bundle size
- Use pure presentational components with OnPush to reduce change detection cycles
- Avoid heavy computations in templates; compute in the component class
- Optimize third-party library usage; prefer on-demand imports (e.g., RxJS operators)
- Use Web Workers for compute-intensive tasks to avoid blocking the main thread
PWA and Internationalization
In this section, we introduce two important advanced topics: Progressive Web Apps (PWA) and Internationalization (i18n). PWA can give Angular apps offline support and a native-like experience, while internationalization enables multi-language support for global usage.
Progressive Web Apps (PWA)
Progressive Web Apps (PWA) are apps built with modern web technologies that enable web apps to function like native apps with offline support. Angular provides built-in PWA support to help developers easily build web apps with offline support, push notifications, and more.
- Turning an Angular app into a PWA
The Angular CLI provides an easy command to convert an existing project into a PWA:
- Add PWA support
In your project directory, run:
ng add @angular/pwaAfter running the command, Angular will automatically generate the Service Worker configuration (ngsw-config.json) and the app’s icons (manifest.webmanifest). These files configure the app’s offline caching and icons for the PWA.
- Configure
ngsw-config.json
ngsw-config.json is the Service Worker configuration file, used to define which files should be cached. By default, Angular caches the app’s main resources (JavaScript files, CSS files, HTML files, etc.). You can customize the caching strategy as needed.
- Build production version
When using a PWA, the app should run in production mode:
ng build --prod- Deployment
After deploying the app to a server, the browser will automatically detect and register the Service Worker, enabling offline functionality.
- Verifying PWA features
- After opening the app, check the browser’s developer tools to verify that the Service Worker has been registered successfully.
- You can try turning off the network and refreshing the page; the app should still load from the offline cache.
Internationalization
Internationalization involves converting texts, date formats, number formats, and other content in the app to different languages and regional formats, enabling the app to adapt to different languages and cultural contexts. Angular provides built-in i18n support to help developers easily add multilingual content.
- Using Angular’s i18n features
Angular’s i18n features help translate the app’s text into multiple languages. Here are the configuration steps:
- Marking texts
In templates, mark text to be translated using the i18n attribute.
<h1 i18n="@@welcome">Welcome to our app!</h1><p i18n="@@intro">This is an example of internationalized content.</p>Here, i18n="@@key" adds a unique translation key key to the text content, making it easier to locate and translate later.
- Extract translation files
Use the Angular CLI to extract translation files. Running the following command will generate a messages.xlf file in the src/locale directory:
ng extract-i18nThe generated messages.xlf is an XML file containing all marked translations from the app.
- Translating content
Create translation files for each language in the messages.xlf file, translate the contents, and save the files. For example, you can create a messages.fr.xlf file for French translations.
<trans-unit id="welcome" datatype="html"> <source>Welcome to our app!</source> <target>Bienvenue dans notre application!</target></trans-unit>
<trans-unit id="intro" datatype="html"> <source>This is an example of internationalized content.</source> <target>Ceci est un exemple de contenu internationalisé.</target></trans-unit>- Configuring multi-language compilation
In the angular.json file, add multi-language configuration, for example:
"projects": { "your-app-name": { "i18n": { "sourceLocale": "en", "locales": { "fr": "src/locale/messages.fr.xlf" } } }}- Building multi-language versions
When building the app, you can generate different versions for different languages:
ng build --prod --localizeThis will automatically generate versions for each language (for example, the French version will be under dist/your-app-name/fr).
- Dynamic language switching (optional)
If you want to switch languages at runtime, you can use a third-party library (such as ngx-translate) to implement more flexible internationalization support.
プロジェクトの作成
npm install -g @angular/cling new my-angular-projectcd my-angular-projectng serveng serve --port 8081
# access in localhost:4200Angular CLI
ファイル生成
Angular CLI はさまざまなファイルを生成するためのショートカットコマンドを提供し、プロジェクト構造の一貫性を保ちます。よく使われる生成コマンドには次のものが含まれます:
-
コンポーネントを生成:
ng generate component component-name# 或者简写ng g c component-nameこれは、HTML、CSS、TypeScript、テストファイルを含むコンポーネントを作成します。
-
サービスを生成:
ng generate service service-name# 简写ng g s service-name生成されるサービスファイルにはデフォルトでインジェクターが含まれ、コンポーネントから簡単に使用できます。
-
モジュールを生成:
ng generate module module-name# 简写ng g m module-name生成されるモジュールは、機能モジュールごとにコードのロジックを分割し、コードの管理と遅延ロードを容易にします。
ビルドプロジェクト
ビルドには ng build コマンドを使用します:
ng buildビルド後のファイルは dist ディレクトリに格納されます。本番環境へ公開するには、以下のコマンドを使用します:
ng build --prod-prodオプションは、本番環境の最適化(コード圧縮、難読化、デバッグ情報の削除など)を有効にします。
テスト
Angular CLI はユニットテストとE2Eテストをサポートします:
-
ユニットテスト:
ng testコマンドを使用してユニットテストを実行します。デフォルトは Karma と Jasmine を使用します。ng testこれによりブラウザでテストUIが開き、テスト結果がリアルタイムで更新されます。
-
エンドツーエンド テスト:
ng e2eコマンドを使用してエンドツーエンドテストを実行します。デフォルトは Protractor。ng e2eエンドツーエンドテストはユーザーの動作を模倣し、アプリ全体の機能が正常に動作することを確認します。
生成サービス、パイプ、ディレクティブなど他の構造を生成
コンポーネントとモジュール以外にも、CLI は他の Angular 構造の生成をサポートしています:
-
ディレクティブを生成:
ng generate directive directive-name# 简写ng g d directive-name -
パイプを生成:
ng generate pipe pipe-name# 简写ng g p pipe-name
設定と最適化
- 環境設定
Angular は環境ごとの設定ファイルをサポートしており、デフォルトでは environment.ts(開発環境)と environment.prod.ts(本番環境)が含まれます。angular.json ファイルでさらに環境設定を定義し、ビルド時に異なる環境を選択できます:
ng build --configuration production- デバッグと監視
ng serve を使用すると、Angular CLI がファイルの変更をリアルタイムで監視し、アプリケーションを自動的に再ビルドします。--source-map オプションを使用してデバッグ情報を生成し、ブラウザでコードをデバッグできます:
ng serve --source-map- プリロードと遅延ロード(Lazy Loading)モジュール
大規模なアプリケーションでは、プリロードと遅延ロードモジュールを使用するとパフォーマンスが向上します。Angular CLI は自動的に遅延ロードをサポートし、必要に応じてモジュールを読み込むのを手伝い、初回ロード時間を短縮します。
その他の有用な CLI コマンド
-
Angular プロジェクトや依存関係の更新:
ng updateこのコマンドは Angular および関連依存関係をチェックして更新します。
-
ビルドパッケージの分析:
-stats-jsonオプションを使ってアプリをビルドすると、分析ファイルが生成され、パッケージ内容の確認と最適化に役立ちます。ng build --prod --stats-json -
サードパーティライブラリやツールの追加:
ng addコマンドを使用してサードパーティライブラリやプラグインを迅速に追加できます。例として、Angular Material を追加:ng add @angular/material
angular.json 配置ファイル
angular.json は Angular プロジェクトのグローバル設定ファイルで、プロジェクトの全ての設定情報を含みます。ここでビルド出力パス、環境設定、スタイルやスクリプトの読み込み順序などを調整できます。
Angular プロジェクトの基礎構造
プロジェクトのディレクトリ構造
ng new で新しい Angular プロジェクトを作成すると、プロジェクト構造は次のようになります:
my-angular-app/├── e2e/ # エンドツーエンド テストディレクトリ├── node_modules/ # プロジェクト依存パッケージディレクトリ├── src/ # アプリケーションのソースコードディレクトリ│ ├── app/ # コアアプリディレクトリ│ │ ├── app.component.ts # ルートコンポーネントのロジックファイル│ │ ├── app.component.html # ルートコンポーネントのテンプレートファイル│ │ ├── app.component.css # ルートコンポーネントのスタイルファイル│ │ └── app.module.ts # ルートモジュールファイル│ ├── assets/ # 静的リソースディレクトリ│ ├── environments/ # 環境設定ディレクトリ│ ├── index.html # 主 HTML ファイル│ ├── main.ts # アプリの主エントリファイル│ ├── polyfills.ts # ブラウザ互換コード│ ├── styles.css # グローバルスタイルファイル│ └── test.ts # ユニットテスト用エントリファイル├── angular.json # Angular プロジェクト設定ファイル├── package.json # プロジェクト依存とスクリプト├── tsconfig.json # TypeScript の設定ファイル└── README.md # プロジェクトの説明ファイル目录とファイルの詳細
-
src/ディレクトリsrc/はアプリケーションのソースコードを格納する主要ディレクトリで、Angular アプリのすべてのコアコードがここにあります。app/ディレクトリ:これはアプリケーションの主要ディレクトリで、ルートモジュールとルートコンポーネントを含みます。プロジェクトの開発が進むにつれて、ここにさらにコンポーネント、サービス、モジュールなどを作成します。app.component.ts:コンポーネントのロジックファイルで、コンポーネントクラスとデコレーターを含みます。app.component.html:ルートコンポーネントのテンプレートファイル。app.component.css:ルートコンポーネントのスタイルファイル。app.module.ts:ルートモジュールファイル、アプリ起動時にロードされるモジュール。各 Angular アプリには少なくとも1つのルートモジュールがあります。
assets/ディレクトリ:静的リソース(画像、フォントなど)を格納します。ビルド時にビルドディレクトリに直接コピーされ、相対パスでアクセスできます。environments/ディレクトリ:異なる環境の設定ファイルを含みます。例として開発環境と本番環境があります。デフォルトではenvironment.ts(開発環境の設定)とenvironment.prod.ts(本番環境の設定)が含まれます。環境条件に応じて異なる設定を読み込むことができます。index.html:アプリのメインHTMLファイル。ページの入口で、Angular はすべてのコンポーネントをこのページ内にレンダリングします。main.ts:アプリのメインエントリーファイル。Angular アプリはここから実行を開始します。main.tsはルートモジュールAppModuleをブーツストラップし、Angular アプリを起動します。polyfills.ts:異なるブラウザの互換性コードを読み込み、すべてのブラウザでアプリが一貫して動作するようにします。styles.css:グローバルスタイルファイルで、アプリ全体の共通スタイルをここで定義できます。test.ts:テストのエントリーファイルで、ユニットテストの設定と初期化に使用されます。
-
e2e/ディレクトリe2e/ディレクトリにはエンドツーエンドテストのコードを格納します。デフォルトでは Protractor フレームワークを使用してこれらのテストを実行し、ユーザーの動作を模倣してアプリ全体の機能をテストします。 -
根ディレクトリの他のファイル
angular.json:Angular プロジェクトの設定ファイルで、ビルドと開発サーバーに関する設定を含みます。ここでビルド出力パス、環境設定、スタイルやスクリプトの読み込み順序などを調整できます。package.json:Node.js プロジェクトの設定ファイルで、依存関係と実行スクリプトを含みます。すべての依存ライブラリと CLI コマンドはここで定義・管理されます。tsconfig.json:TypeScript の設定ファイルで、TypeScript コードのコンパイル規則を定義します。README.md:プロジェクトの説明ファイル。プロジェクトの概要、インストール手順、使用方法などを記載できます。
-
node_modules/ディレクトリnode_modules/ディレクトリには、npmによってインストールされたプロジェクトの依存パッケージが格納されます。Angular、TypeScript、コンパイラなどのライブラリのコードはすべてここにあります。
コンポーネントとモジュール
Angular では、コンポーネントとモジュールがアプリケーションのコアとなる構造です。コンポーネントはページの各部分を構築し、モジュールはこれらのコンポーネントを整理・管理するのに役立ちます。
コンポーネント(Component)
コンポーネントは Angular アプリの基本的な構成要素です。1つのコンポーネントは通常、3つの部分から成り立っています:
- テンプレート(Template):コンポーネントの HTML 構造を定義します。
- スタイル(Styles):コンポーネントの CSS スタイルを定義します。
- ロジック(Class):コンポーネントの挙動とデータを定義します。
-
コンポーネントの作成
Angular CLI はコンポーネントを生成するコマンドを提供します:
ng generate component component-name# 或者简写ng g c component-nameこのコマンドは
appディレクトリの下に新しいコンポーネントディレクトリを作成し、以下のファイルを含みます。component-name.component.ts:コンポーネントのロジックファイル、コンポーネントクラスとデコレーターを含みます。component-name.component.html:コンポーネントのテンプレートファイル。component-name.component.css:コンポーネントのスタイルファイル。component-name.component.spec.ts:コンポーネントのテストファイル。
-
コンポーネントの構造
component-name.component.tsファイル内で、コンポーネントは@Componentデコレーターを使用して定義され、構造は以下のとおりです:import { Component } from '@angular/core';@Component({selector: 'app-component-name', // コンポーネントのセレクター。テンプレートでの参照に使用templateUrl: './component-name.component.html', // コンポーネントのテンプレートファイルstyleUrls: ['./component-name.component.css'] // コンポーネントのスタイルファイル})export class ComponentNameComponent {title = 'Hello, Angular'; // コンポーネントの属性とメソッド}selector:他のテンプレートからこのコンポーネントを参照するためのセレクター。例えば、<app-component-name></app-component-name>。templateUrlとstyleUrls:それぞれコンポーネントのテンプレートとスタイルファイルのパスを定義します。
-
コンポーネントのデータバインディング
Angular は複数のデータバインディングの方法を提供しています:
- 挿入表現(インターポレーション):コンポーネントの属性値を表示するために使用します。例:
{{ title }}。 - 属性バインディング:
[]を使って属性に値をバインドします。例:<img [src]="imageUrl">。 - イベントバインディング:
()を使ってイベントをバインドします。例:<button (click)="onClick()">Click</button>。 - 双方向データバインディング:
[(ngModel)]を使ってデータとビューを双方向に結び付けます(FormsModuleのインポートが必要です)。
- 挿入表現(インターポレーション):コンポーネントの属性値を表示するために使用します。例:
モジュール(Module)
モジュールは、アプリケーションのコンポーネント、ディレクティブ、パイプ、サービスを整理・管理するために使用します。各 Angular アプリには少なくとも1つのルートモジュール、AppModule があり、app.module.ts ファイルで定義します。
-
モジュールの構造
app.module.tsファイルで、モジュールは@NgModuleデコレーターを使って定義され、その構造は以下のとおりです:import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { AppComponent } from './app.component';import { ComponentNameComponent } from './component-name/component-name.component';@NgModule({declarations: [AppComponent,ComponentNameComponent // 声明モジュール内のコンポーネント],imports: [BrowserModule // 他のモジュールをインポート],providers: [], // アプリケーションで使用するサービスの提供者を宣言bootstrap: [AppComponent] // アプリの起動時に読み込まれるルートコンポーネントを定義})export class AppModule { }declarations:モジュール内で宣言されたコンポーネント、ディレクティブ、パイプ。モジュールに宣言されているコンポーネントのみが使用できます。imports:他のモジュールをインポートします。例えばBrowserModuleはブラウザアプリのコアモジュールです。providers:アプリで使用されるサービスの提供者を宣言します。bootstrap:アプリ起動時にロードされるルートコンポーネントを定義します。通常はAppComponent。
-
特性モジュール
大規模なアプリケーションでは、コードを整理するために複数の機能モジュール(Feature Modules)を作成し、機能ごとに分離や遅延ロードを実現します。
ng generate module feature-module# 或者简写ng g m feature-module
コンポーネントとモジュールの関係
- モジュールはコンポーネントを整理するために使用します:Angular では、モジュールはコンポーネントを管理・整理します。各モジュールは複数のコンポーネントを含むことができます。
- コンポーネントの再利用性:1つのコンポーネントは複数のモジュールで宣言・使用できますが、まずそのコンポーネントを宣言しているモジュールをインポートする必要があります。
- ルートモジュールと機能モジュール:ルートモジュール(例:
AppModule)はアプリの起動を担当し、機能モジュールはアプリの具体的な機能を整理します。
データバインディング
インターポレーション(Interpolation)
インターポレーションは {{ }} 構文を使用してデータをバインドします。HTML 内でコンポーネントの属性値を表示するのに通常使用します。
例: コンポーネントクラスに属性 title を定義し、それをテンプレートで表示します。
// コンポーネントの TypeScript ファイル内export class MyComponent { title = 'Hello, Angular!';}<!-- コンポーネントの HTML テンプレート内 --><h1>{{ title }}</h1>ここで {{ title }} は Hello, Angular! に置換されます。インターポレーションは通常、テキスト内容の表示に使用されます。
属性バインディング(Property Binding)
属性バインディングは方括弧 [] 構文を使い、コンポーネントの属性値を HTML 要素の属性にバインドします。例: src、href、disabled など。
例: 画像リンクを持っていると仮定し、それを img 要素の src 属性にバインドします。
// コンポーネントの TypeScript ファイル内export class MyComponent { imageUrl = 'https://example.com/image.jpg';}<!-- コンポーネントの HTML テンプレート内 --><img [src]="imageUrl" alt="Example Image">ここの [src]="imageUrl" は、imageUrl の値を img の src 属性にバインドしていることを意味します。
イベントバインディング(Event Binding)
イベントバインディングは丸括弧 () 構文を使用し、ビューのイベント(例:click、mouseover)をコンポーネントのメソッドにバインドして、特定のロジックを発火させます。
例: ボタンに click イベントをバインドして、コンポーネントの onClick メソッドを呼び出します。
// コンポーネントの TypeScript ファイル内export class MyComponent { onClick() { console.log('Button clicked!'); }}<!-- コンポーネントの HTML テンプレート内 --><button (click)="onClick()">Click Me</button>ここで (click)="onClick()" は click イベントをバインドし、ボタンをクリックすると onClick メソッドが実行され、"Button clicked!" が出力されます。
双方向データバインディング(Two-way Binding)
双方向データバインディングは [(ngModel)] 構文を使用し、データとビューを双方向に同期します。ユーザーが入力したデータは自動的にコンポーネントの属性を更新し、属性の変更もビューに自動的に反映されます。双方向バインディングは通常、フォームの入力やユーザー入力のシーンで使用されます。
例: 入力フィールドで双方向データバインディングを使用し、name 属性を input フィールドの値と同期します。FormsModule のインポートが必要です。
// コンポーネントの TypeScript ファイル内export class MyComponent { name = '';}<!-- コンポーネントの HTML テンプレート内 --><input [(ngModel)]="name" placeholder="Enter your name"><p>Hello, {{ name }}!</p>ここでは [(ngModel)]="name" が双方向データバインディングを実現し、入力欄の入力は即座に name を更新し、name の値はページに即座に表示されます。
注意:双方向バインディングを使用するには、アプリのモジュールで
FormsModuleをインポートする必要があります。そうしないとエラーになります。
import { FormsModule } from '@angular/forms';
@NgModule({ imports: [FormsModule], ...})export class AppModule { }データバインディングの利点
- コードの簡素化:バインディングを使うことで、DOM を手動で更新するコードを減らせます。
- リアルタイムの同期:データとビューを同期させ、アプリをより動的にします。
- 保守性の向上:データとビューを分離することで、アプリの保守・拡張が容易になります。
指令(Directives)
ディレクティブは Angular の非常に重要な特性で、テンプレート内で DOM 要素を操作したり、スタイル・構造・振る舞いを制御したりします。ディレクティブは Angular のテンプレートをより動的で柔軟にします。
ディレクティブのタイプ
Angular には主に3つのディレクティブタイプがあります:
- コンポーネントディレクティブ:コンポーネント自体も一種のディレクティブで、テンプレート・スタイル・ロジックを持ち、ディレクティブの拡張形式です。
- 構造ディレクティブ(Structural Directives):DOM 要素の追加・削除やページ構造の制御に使用します。よく使われるのは
ngIfとngFor。 - 属性ディレクティブ(Attribute Directives):要素の外観や振る舞いを変更するために使用します。よく使われるのは
ngClassとngStyle。
構造ディレクティブ
構造ディレクティブは DOM 要素の追加、削除、置換を行います。使用時にはディレクティブの前に * を付けます。
-
ngIfは条件に応じて DOM 要素を表示または非表示にします。<div *ngIf="isVisible">This is visible only if isVisible is true.</div>上の例では、
*ngIf="isVisible"がdivの表示を制御します。isVisibleがtrueの場合のみこの要素がレンダリングされます。 -
ngForは配列を反復処理して、一連の DOM 要素を生成します。<ul><li *ngFor="let item of items">{{ item }}</li></ul>上の例では、
*ngForがitems配列を走査し、各要素に対して<li>を生成します。 -
ngSwitchは異なる条件に応じて異なる要素をレンダリングします。通常は複数条件の判定に使用されます。<div [ngSwitch]="value"><p *ngSwitchCase="'one'">Value is one</p><p *ngSwitchCase="'two'">Value is two</p><p *ngSwitchDefault>Value is unknown</p></div>ngSwitchCaseは特定の値にマッチさせるために使用します。ngSwitchDefaultはデフォルトのケースを表します。
属性ディレクティブ
属性ディレクティブは DOM 要素の外観または振る舞いを変更します。要素を作成または削除するのではなく、既存の要素の属性を変更します。
-
ngClassは要素の CSS クラスを動的に設定します。<div [ngClass]="{ 'active': isActive, 'highlight': isHighlighted }">Styled div</div>上の例では、
isActiveとisHighlightedの値に基づいてactiveとhighlightクラスを動的に追加します。 -
ngStyleは要素のインラインスタイルを動的に設定します。<div [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px' }">Styled div</div>上の例では、
textColorとfontSizeによってdiv要素の色とフォントサイズを動的に制御します。
自定义ディレクティブ
Angular が提供する組み込みディレクティブに加え、特定の振る舞いを実装するカスタムディレクティブを作成することもできます。通常、カスタムディレクティブは属性型ディレクティブで、要素の機能を拡張します。
- カスタムディレクティブを作成する
Angular CLI を使用してディレクティブを作成するコマンドは次のとおりです:
ng generate directive highlight# 或简写ng g d highlightこのコマンドは highlight.directive.ts というディレクティブファイルを生成します。初期内容は次のとおりです:
import { Directive, ElementRef, Renderer2, HostListener } from '@angular/core';
@Directive({ selector: '[appHighlight]'})export class HighlightDirective { constructor(private el: ElementRef, private renderer: Renderer2) {}
@HostListener('mouseenter') onMouseEnter() { this.highlight('yellow'); }
@HostListener('mouseleave') onMouseLeave() { this.highlight(null); }
private highlight(color: string) { this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', color); }}この例では、カスタムの HighlightDirective ディレクティブは、マウスを要素上に置くと背景色を黄色に設定し、離れると色を元に戻します。
-
@Directiveデコレーター:ディレクティブを定義します。selector: '[appHighlight]'は属性型ディレクティブを意味し、使用時にはappHighlight属性を要素に追加します。 -
ElementRef:ディレクティブが適用される DOM 要素へアクセスします。 -
Renderer2:DOM スタイルを安全に操作するため、直接 DOM を変更しないようにします。 -
@HostListener:要素のイベントをリスンします。mouseenterはマウスが入るとき、mouseleaveはマウスが出るときです。 -
自定義ディレクティブの使用
テンプレートで appHighlight ディレクティブを使用します:
<p appHighlight>Hover over this text to see the highlight effect.</p>appHighlight 属性を追加すると、ディレクティブが要素に適用されます。マウスをホバーすると背景色が黄色になり、離れると元に戻ります。
サービス(Services)と依存性注入(Dependency Injection)
Angular では、サービス(Service)はアプリケーション内のロジックとデータをカプセル化して共有するために使用し、依存性注入(DI)システムはこれらのサービスの管理と提供を担当します。サービスを使用することで、アプリのビジネスロジックをコンポーネントから分離し、コードの再利用性と保守性を向上させます。DI システムは、サービスをコンポーネントや他のサービスから容易に活用できるようにします。
サービス
サービスは通常、Angular でクラスとして実装され、特定のコンポーネントに属さないロジックとデータをカプセル化します。データの取得、ビジネスロジックの処理、状態の管理などを含み、サービスは複数のコンポーネント間で共有できます。
- サービスの作成
Angular CLI を使って高速にサービスを生成します:
ng generate service my-service# 或者简写ng g s my-service生成されるサービスファイルはおおむね以下のとおりです:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' // 将服务注册在根注入器中,全局可用})export class MyService { constructor() { }
getData() { return 'Hello from MyService!'; }}@Injectableデコレーター:別のコンポーネントやサービスに依存として注入できることをマークします。providedIn: 'root':このサービスがルートインジェクターで提供され、アプリ全体で利用できます。これにより、providersに手動登録する必要がなくなります。
- コンポーネントでのサービスの使用
サービスは通常、依存性注入を介して使用します。コンポーネントのコンストラクターにサービスを注入して、コンポーネント内でサービスのメソッドを呼び出します。
以下は、MyService というサービスを作成済みとして、コンポーネントでこのサービスを使用する例です:
import { Component, OnInit } from '@angular/core';import { MyService } from './my-service.service';
@Component({ selector: 'app-my-component', template: `<p>{{ message }}</p>`})export class MyComponent implements OnInit { message: string;
// コンストラクターでサービスを注入 constructor(private myService: MyService) {}
ngOnInit(): void { this.message = this.myService.getData(); }}- コンストラクターインジェクション:コンポーネントのコンストラクターで
private myService: MyServiceを定義してサービスを注入します。 - サービスメソッドの呼び出し:
ngOnInitライフサイクルフックでgetData()を呼び出し、戻り値をmessageに代入します。
- サービスのスコープと提供方法
Angular のサービス提供方法には複数あり、提供方法の違いはサービスのスコープとライフサイクルに影響します:
- ルートレベルの提供方法
providedIn: 'root' を用いてルートインジェクターでサービスを提供します。サービスのインスタンスはアプリ全体のライフサイクルでシングルトンになります。この方法は、グローバルに共有されるデータやロジックに通常使用されます。
@Injectable({ providedIn: 'root'})export class MyService { }- モジュールレベルの提供方法
サービスを特定のモジュールだけで使用可能にしたい場合、そのモジュールの providers 配列にサービスを登録します。これにより、サービスのライフサイクルはモジュールと同じになり、ローカルに共有されるデータやロジックに適しています。
import { NgModule } from '@angular/core';import { MyService } from './my-service.service';
@NgModule({ providers: [MyService] // モジュールレベルで提供する})export class MyModule { }- コンポーネントレベルの提供方法
サービスのインスタンスを単一のコンポーネントやその子コンポーネントのみで使用したい場合、コンポーネントの providers 配列にサービスを登録します。これにより、各コンポーネントのインスタンスが独自のサービスインスタンスを持ち、特定のコンポーネント内でのみ使用されるロジックに適しています。
import { Component } from '@angular/core';import { MyService } from './my-service.service';
@Component({ selector: 'app-my-component', template: `<p>My Component</p>`, providers: [MyService] // コンポーネントレベルでサービスを提供})export class MyComponent { }依存性注入(Dependency Injection)
依存性注入は、依存関係(例:サービス)をコンポーネントや他のサービスに注入することで、ハードコードされた依存を避ける設計パターンです。Angular の DI システムは依存関係の生成と提供を自動で管理し、アプリの構造を簡素化します。
- DI システムの動作原理
Angular が特定のクラスが特定の依存を必要とすると検出すると、インジェクターでその依存のインスタンスを探します。インスタンスが存在しない場合、インスタンスを作成してコンポーネントやサービスに返します。
- カスタムインジェクター
Angular はコンポーネント内でカスタムの注入器をサポートします。これにより、依存の提供方法を制御できます。これは大多数のアプリでは一般的ではありませんが、サービスのスコープを制御したり特殊な依存関係を実装する場合に便利です。
サービスの実際の適用シーン
- データ共有:共有データをサービスに格納して、複数のコンポーネントがデータにアクセス・更新できるようにします。
- HTTP リクエスト:
HttpClientサービスを通じてバックエンド API からデータを取得し、ロジックをサービスにカプセル化して再利用とテストを容易にします。 - グローバルな状態管理:サービスでアプリの状態を管理します(例:ユーザー認証情報やテーマ設定)。
例:シンプルなデータサービスの作成
以下は、ユーザーデータのセットを管理し、追加・削除・更新・検索機能を提供するシンプルなデータサービス DataService を作成する例です。
- サービスの作成
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root'})export class DataService { private users = ['Alice', 'Bob', 'Charlie'];
getUsers() { return this.users; }
addUser(user: string) { this.users.push(user); }}- コンポーネントでのサービスの使用
import { Component } from '@angular/core';import { DataService } from './data.service';
@Component({ selector: 'app-user-list', template: ` <ul> <li *ngFor="let user of users">{{ user }}</li> </ul> <input [(ngModel)]="newUser" placeholder="Enter name"> <button (click)="addUser()">Add User</button> `})export class UserListComponent { users: string[]; newUser = '';
constructor(private dataService: DataService) { this.users = this.dataService.getUsers(); }
addUser() { if (this.newUser) { this.dataService.addUser(this.newUser); this.newUser = ''; } }}この例では、DataService はユーザデータの管理ロジックを提供し、UserListComponent はサービスからデータを取得してビューに表示し、同時にサービスを通じて新しいユーザーを追加できます。
ルーティング(Routing)とナビゲーション
Angular のルーティングシステムは、URL を通じてページの異なるビューを構築することを可能にします。ルーティングは、ユーザーのナビゲーションに応じてページ間を切り替え、パラメータの伝送、遅延読み込み、ルートガードなどの高度な機能をサポートします。
ルーティングとは?
シングルページアプリケーションでは、実際には1つのページしかありませんが、異なる URL を介してアプリの異なる部分にナビゲートできます。Angular のルーティングは URL パスとコンポーネントの対応付けを定義でき、ナビゲーションリンクをクリックすると対応するコンポーネントがロードされ、ページ全体をリフレッシュすることはありません。
Angular のルーティング設定
RouterModuleのインポート
Angular アプリでルーティングを設定するには、アプリのルートモジュールまたは関連機能モジュールで RouterModule をインポートし、ルーティング設定を定義します。
例えば、 standalone モードでは main.ts でルーティングを構成します:
import { bootstrapApplication } from '@angular/platform-browser';import { AppComponent } from './app/app.component';import { provideRouter } from '@angular/router';import { HomeComponent } from './app/home/home.component';import { AboutComponent } from './app/about/about.component';
bootstrapApplication(AppComponent, { providers: [ provideRouter([ { path: '', component: HomeComponent }, // default route { path: 'about', component: AboutComponent } // /about route ]) ]});この例では、2つのルートを定義しています:
''はルートパス(/)で、HomeComponentに対応します。'about'は/aboutパスで、AboutComponentに対応します。
- コンポーネントの作成
関連するコンポーネントがまだない場合は、次のコマンドで生成します:
ng generate component home --standaloneng generate component about --standalone- テンプレートにルーティングリンクを追加
Angular はルーティングリンクを作成するための routerLink ディレクティブを提供します。AppComponent のテンプレートにナビゲーションリンクを追加できます:
<nav> <a routerLink="/">Home</a> | <a routerLink="/about">About</a></nav><router-outlet></router-outlet>routerLink:ナビゲーションリンクのパスを指定します。例:routerLink="/"はルートパスを指します。<router-outlet></router-outlet>:ルートアウトレット。ルーティングされたコンポーネントが表示される場所を指定します。<router-outlet>はルーティングシステムのプレースホルダーで、現在の URL に基づいて対応するコンポーネントをここに表示します。
- ルーティングパラメータの伝達
Angular ルーティングは URL にパラメータを伝え、コンポーネント側で受け取り処理します。例として、ユーザー詳細を表示するルートを定義します:
- パラメータ付きルートの定義
ルート設定で :id をプレースホルダーとしてパラメータを定義します:
{ path: 'user/:id', component: UserComponent }- テンプレートにリンクを追加
テンプレートで routerLink を使ってパラメータを渡すことができます:
<a [routerLink]="['/user', 1]">User 1</a><a [routerLink]="['/user', 2]">User 2</a>- コンポーネントでパラメータを取得
UserComponent では ActivatedRoute を使用してルートパラメータを取得できます:
import { Component, OnInit } from '@angular/core';import { ActivatedRoute } from '@angular/router';
@Component({ selector: 'app-user', standalone: true, template: `<p>User ID: {{ userId }}</p>`})export class UserComponent implements OnInit { userId: string;
constructor(private route: ActivatedRoute) {}
ngOnInit(): void { this.userId = this.route.snapshot.paramMap.get('id')!; }}ActivatedRoute:Angular のルーティングサービスで、ルート情報を取得します。paramMap:ルートパラメータのキー/値の集合です。get('id')で現在のルートのidパラメータを取得します。
路由守衛(Route Guards)
ルートガードはルートを保護し、ユーザーがアクセス権を持っていることを保証します。よく使われるルートガードには次のものがあります:
CanActivate:特定のルートへナビゲートする前にチェックを行い、アクセスを許可するかを決定します。CanDeactivate:ルートを離れるときにチェックを行い、離れることを許可するかを決定します。
例えば、ユーザーがログインしている場合のみ特定のルートにアクセスできるようにするシンプルな AuthGuard を作成します。
- ガードの生成
CLI を使ってガードを生成します:
ng generate guard authAuthGuardのロジックを実装
生成された auth.guard.ts ファイルに認証ロジックを実装します:
import { Injectable } from '@angular/core';import { CanActivate, Router } from '@angular/router';
@Injectable({ providedIn: 'root'})export class AuthGuard implements CanActivate { constructor(private router: Router) {}
canActivate(): boolean { const isAuthenticated = false; // 実際の認証ロジックに置き換える
if (!isAuthenticated) { this.router.navigate(['/login']); return false; } return true; }}- 守衛をルートに適用
ルーティング設定で canActivate プロパティを用いて守衛を適用します:
{ path: 'protected', component: ProtectedComponent, canActivate: [AuthGuard] }懒加载(Lazy Loading)
レイジーロードは、特定のモジュールを必要なときだけ読み込むことで、アプリの初期ロード速度を最適化します。loadComponent を使ってコンポーネントのレイジーロードを簡単に実現できます:
{ path: 'lazy', loadComponent: () => import('./lazy/lazy.component').then(m => m.LazyComponent) }表单(Forms)处理
Angular は強力なフォーム処理機能を提供し、フォームの作成と検証をサポートします。Angular のフォームには主に2つの方式があります:テンプレート駆動フォームと反応的フォーム。この2つの方式にはそれぞれ長所があり、さまざまな場面に適しています。
テンプレート駆動フォーム(Template-driven Forms)
テンプレート駆動フォームは主に HTML テンプレートを通じてフォームの構造と検証ロジックを定義します。簡単なフォームに適しています。データバインディングと検証サポートは Angular の FormsModule を使用して提供されます。
FormsModuleのインポート
まず、モジュールで FormsModule をインポートします。プロジェクトがモジュール化されている場合は、app.module.ts または対応するモジュールファイルを開いて FormsModule を追加します。
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { FormsModule } from '@angular/forms';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, FormsModule // Import FormsModule ], bootstrap: [AppComponent]})export class AppModule {}- テンプレート駆動フォームの定義
テンプレート内でシンプルなフォームを作成し、ngModel ディレクティブを使って双方向データバインディングを実現します。#name="ngModel" のようにテンプレート変数を作成して、入力の検証状態にアクセスします。
<form #myForm="ngForm"> <label for="name">Name:</label> <input id="name" name="name" [(ngModel)]="user.name" required> <div *ngIf="!myForm.controls.name?.valid && myForm.controls.name?.touched"> Name is required. </div>
<label for="email">Email:</label> <input id="email" name="email" [(ngModel)]="user.email" required email> <div *ngIf="!myForm.controls.email?.valid && myForm.controls.email?.touched"> Valid email is required. </div>
<button [disabled]="!myForm.valid">Submit</button></form>[(ngModel)]:双方向データバインディング。フォームフィールドとコンポーネントのデータを同期します。#myForm="ngForm":テンプレート参照変数myFormを作成し、フォームの状態にアクセスできます。- **
requiredおよびemail検証:Angular が HTML5 の検証ルールを自動的に提供します。 - コンポーネントでデータモデルを定義
app.component.ts に user データモデルを定義し、フォームとバインドします。
import { Component } from '@angular/core';
@Component({ selector: 'app-root', templateUrl: './app.component.html'})export class AppComponent { user = { name: '', email: '' };}反応的フォーム(Reactive Forms)
反応的フォームは、コンポーネントクラス内でフォーム構造と検証ロジックを定義します。より柔軟で、動的な複雑なフォームに適しています。ReactiveFormsModule を使ってフォームコントロールと検証を提供します。
ReactiveFormsModuleのインポート
モジュールで ReactiveFormsModule をインポートします。
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { ReactiveFormsModule } from '@angular/forms';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, ReactiveFormsModule // Import ReactiveFormsModule ], bootstrap: [AppComponent]})export class AppModule {}- 反応的フォームの定義
FormBuilder を用いてフォーム構造と検証ルールを定義します。
import { Component } from '@angular/core';import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({ selector: 'app-root', templateUrl: './app.component.html'})export class AppComponent { userForm: FormGroup;
constructor(private fb: FormBuilder) { this.userForm = this.fb.group({ name: ['', Validators.required], email: ['', [Validators.required, Validators.email]] }); }
onSubmit() { if (this.userForm.valid) { console.log(this.userForm.value); } }}FormBuilder:Angular が提供するサービスで、フォーム構造の作成を簡略化します。Validators:フォームフィールドの検証ルールを設定します。- 反応的フォームをテンプレートにバインド
テンプレートでは [formGroup] で全体のフォームをバインドし、formControlName で各フォームコントロールをバインドします。
<form [formGroup]="userForm" (ngSubmit)="onSubmit()"> <label for="name">Name:</label> <input id="name" formControlName="name"> <div *ngIf="userForm.controls.name.invalid && userForm.controls.name.touched"> Name is required. </div>
<label for="email">Email:</label> <input id="email" formControlName="email"> <div *ngIf="userForm.controls.email.invalid && userForm.controls.email.touched"> Valid email is required. </div>
<button [disabled]="userForm.invalid">Submit</button></form>[formGroup]:コンポーネントのuserFormをテンプレートのフォームにバインドします。formControlName:フォームコントロールをuserFormの対応するコントロールにバインドします。
フォーム検証
Angular は多数の組み込みバリデータを提供します。例えば、Validators.required、Validators.email などがあり、カスタム検証器を作成することもできます。
- カスタム検証器
コンポーネント内でカスタム検証器を定義し、フォームコントロールに適用します。
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const forbidden = nameRe.test(control.value); return forbidden ? { forbiddenName: { value: control.value } } : null; };}そのうえで、フォーム作成時にカスタム検証器を適用します:
this.userForm = this.fb.group({ name: ['', [Validators.required, forbiddenNameValidator(/bob/i)]], email: ['', [Validators.required, Validators.email]]});HTTP クライアントと API 通信
モダンな Web アプリケーションでは、バックエンド API との通信は不可欠です。Angular は HttpClient モジュールを提供し、バックエンド API との対話を簡素化します。HttpClient を使用すると、HTTP リクエストの送信、レスポンスデータの処理、エラーの管理、リクエストとレスポンスの挙動を制御するためのインターセプターの追加が容易になります。
HttpClient の設定
Angular アプリで HttpClient を使用するには、モジュールで HttpClientModule をインポートする必要があります。
HttpClientModuleのインポート
ルートモジュールまたは機能モジュールで HttpClientModule をインポートします:
import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { HttpClientModule } from '@angular/common/http';import { AppComponent } from './app.component';
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, HttpClientModule // Import HttpClientModule ], bootstrap: [AppComponent]})export class AppModule {}HttpClientでリクエストを送る
HttpClient は get、post、put、delete など、さまざまな HTTP リクエストを送信するメソッドを提供します。
- GET リクエストの送信
API からユーザー一覧を取得する例では、HttpClient.get を使って GET リクエストを送ります:
import { HttpClient } from '@angular/common/http';import { Component, OnInit } from '@angular/core';
@Component({ selector: 'app-user-list', template: ` <ul> <li *ngFor="let user of users">{{ user.name }}</li> </ul> `})export class UserListComponent implements OnInit { users: any[] = [];
constructor(private http: HttpClient) {}
ngOnInit(): void { this.http.get<any[]>('<https://jsonplaceholder.typicode.com/users>') .subscribe(data => { this.users = data; }); }}この例では:
-
this.http.getが GET リクエストを送信します。 -
subscribeはレスポンスデータを処理します。受け取るdataはユーザーリストで、コンポーネントのusersプロパティに代入されます。 -
POST リクエストの送信
サーバーへデータを送信する場合は、HttpClient.post を使用して POST リクエストを送ります。
addUser(newUser: any) { this.http.post('<https://jsonplaceholder.typicode.com/users>', newUser) .subscribe(response => { console.log('User added:', response); });}ここでの post メソッドは POST リクエストを送信し、newUser データを API に渡します。subscribe の response にはサーバーが返した結果が含まれます。
エラーハンドリング
実際のアプリケーションでは、API リクエストでネットワークタイムアウトやサーバーエラーなどのさまざまなエラーが発生する可能性があります。catchError 演算子を使ってエラーを捕捉・処理できます。
- エラーハンドリングの例
リクエストのエラーを catchError 演算子で処理し、handleError メソッドを呼び出します:
import { HttpErrorResponse } from '@angular/common/http';import { catchError } from 'rxjs/operators';import { throwError } from 'rxjs';
this.http.get('<https://jsonplaceholder.typicode.com/users>') .pipe( catchError(this.handleError) ) .subscribe( data => console.log('Data:', data), error => console.error('Error:', error) );
handleError(error: HttpErrorResponse) { let errorMessage = 'Unknown error!'; if (error.error instanceof ErrorEvent) { // Client-side error errorMessage = `Error: ${error.error.message}`; } else { // Server-side error errorMessage = `Error Code: ${error.status}\\nMessage: ${error.message}`; } return throwError(errorMessage);}ここで:
catchError演算子はエラーを捕捉してhandleErrorメソッドを呼び出します。handleErrorメソッドはエラーのタイプに基づいてエラーメッセージを生成し、throwErrorで返します。
HTTP インターセプター(Interceptors)
HTTP インターセプターは、リクエストやレスポンスを処理する前にロジックを挿入することを可能にします。例えば認証トークンを追加したり、ログを記録したりします。
- インターセプターの作成
Angular CLI を使ってインターセプターを作成します:
ng generate interceptor authインターセプターのファイルは次のようになることが多いです:
import { Injectable } from '@angular/core';import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
@Injectable()export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) { // 克隆请求并添加认证 token const authReq = req.clone({ setHeaders: { Authorization: `Bearer YOUR_TOKEN_HERE` } }); return next.handle(authReq); }}-
HttpInterceptorインターフェースのinterceptメソッドはリクエストを送信する前に実行されます。 -
req.cloneはリクエストオブジェクトをクローンして認証トークンを追加します。 -
守衛の登録
モジュールでインターセプターを HTTP_INTERCEPTORS のマルチプロバイダとして登録します:
import { HTTP_INTERCEPTORS } from '@angular/common/http';import { AuthInterceptor } from './auth.interceptor';
@NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ]})export class AppModule {}RxJS を用いた非同期処理
HttpClient のメソッドは Observable を返します。データを購読するには subscribe を使用します。非同期データフローを処理するには、RxJS の演算子(例:map、switchMap)を使用できます。
- RxJS 演算子の例
リクエスト送信後にデータを処理したい場合、map 演算子を使用します:
import { map } from 'rxjs/operators';
this.http.get<any[]>('<https://api.example.com/users>') .pipe( map(users => users.map(user => user.name)) ) .subscribe(names => console.log(names));RxJS とリアクティブプログラミング
RxJS は Angular の核心ライブラリのひとつで、非同期イベントとデータフローを処理します。
RxJS の強力さは、豊富な演算子を提供し、データをストリームとして扱い、データの変換・フィルタリング・組み合わせを行って、データの変化に対して反応することです。Angular では、HTTP リクエスト、フォーム処理、ルーティング、コンポーネント間通信などの場面で広く使用されています。
RxJS(Reactive Extensions for JavaScript) は、非同期データフローを扱うライブラリで、Observable、Observer、Operators などの機能を提供します。リアクティブプログラミングの核心は、データをストリームとして扱い、一連の演算子を適用してデータを組み合わせ、フィルタリング、変換して、データの変化に反応することです。
Observable(可観測オブジェクト)
- Observable はデータフローの核心概念です。非同期データソースを表し、HTTP リクエスト、イベント、タイマーなどになることがあります。
- データフローを購読するには
Observable.subscribe()を使用します。データが到着すると、オブザーバーに通知されます。
よく使う RxJS 演算子
RxJS にはデータフローを処理する多くの演算子が用意されています。以下はよく使われる演算子とその適用例です:
map- データ変換
map 演算子は、Observable データフローの各データ項目を新しい値に変換します。
例:取得したユーザー配列の各ユーザーオブジェクトをユーザー名にマッピングします。
import { map } from 'rxjs/operators';
this.http.get<any[]>('<https://api.example.com/users>') .pipe( map(users => users.map(user => user.name)) ) .subscribe(names => console.log(names));filter- データのフィルタリング
filter はデータフローの条件に合わないデータ項目をフィルタリングします。
例:isActive が true のユーザーをフィルタリングします。
import { filter } from 'rxjs/operators';
this.http.get<any[]>('<https://api.example.com/users>') .pipe( map(users => users.filter(user => user.isActive)) ) .subscribe(activeUsers => console.log(activeUsers));switchMap- 古い購読をキャンセルして最新データを処理
switchMap は新しいデータを受け取るたびに、前の未完了の Observable をキャンセルします。ネストしたリクエストや連続イベント(例:フォーム入力、ルートパラメータの変化)を処理する際に便利です。
例:ユーザーの入力キーワードに合わせて自動的に検索し、前回のリクエストをキャンセルします。
import { switchMap, debounceTime } from 'rxjs/operators';import { FormControl } from '@angular/forms';
searchControl = new FormControl();
this.searchControl.valueChanges .pipe( debounceTime(300), // 防抖。過度なリクエストを避ける switchMap(query => this.http.get(`https://api.example.com/search?q=${query}`)) ) .subscribe(results => console.log(results));mergeMap- 並列処理
mergeMap は各データ項目を新しい Observable にマッピングし、各 Observable を並列に処理します。
例:複数のユーザーの詳細情報を並列で取得します。
import { mergeMap } from 'rxjs/operators';
const userIds = [1, 2, 3];from(userIds) .pipe( mergeMap(id => this.http.get(`https://api.example.com/users/${id}`)) ) .subscribe(user => console.log(user));catchError- エラーハンドリング
catchError はデータフローのエラーを捕捉し、適切に処理します。
例:リクエストが失敗した場合、デフォルト値を返します。
import { catchError } from 'rxjs/operators';import { of } from 'rxjs';
this.http.get('<https://api.example.com/data>') .pipe( catchError(error => { console.error('Error occurred:', error); return of([]); // デフォルト値として空配列を返す }) ) .subscribe(data => console.log(data));RxJS のフロー制御
RxJS には debounceTime や distinctUntilChanged などのフロー制御演算子が用意されており、ユーザー入力やクリックなどのイベントフローを処理するのに役立ちます。
debounceTime- デバウンス
debounceTime はデータの流量を制御します。一定時間内に新しいデータが到着しない場合にのみデータを送信します。通常、連続した高速な入力を処理する際に使用します。
例:ユーザーが入力を停止してから 500 ミリ秒後に検索リクエストを実行します。
searchControl.valueChanges .pipe( debounceTime(500) ) .subscribe(value => console.log('Search:', value));distinctUntilChanged- 重複排除
distinctUntilChanged は前回と同じデータ項目を無視し、重複処理を防ぎます。
例:ユーザーが同じ内容を入力した場合、リクエストを再送しません。
searchControl.valueChanges .pipe( debounceTime(500), distinctUntilChanged() ) .subscribe(value => console.log('Unique search:', value));RxJS を Angular で活用するシーン
- HTTP リクエスト:
HttpClientの戻り値に RxJS 演算子を適用して、データ取得とエラー処理を容易にします。 - ルートパラメータの変化:ルートパラメータの変化を監視し、依存するリクエストを実行します。
- フォーム入力の処理:入力を処理し、デバウンス・重複排除などを行います。
- コンポーネント間通信:Subject を介してコンポーネント間のイベントやデータ伝達を実現します。
例:RxJS ベースのリアルタイム検索
以下は、switchMap、debounceTime、distinctUntilChanged を使用してリアルタイム検索機能を構築する完全な例です:
import { Component } from '@angular/core';import { FormControl } from '@angular/forms';import { HttpClient } from '@angular/common/http';import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@Component({ selector: 'app-search', template: ` <input [formControl]="searchControl" placeholder="Search..."> <ul> <li *ngFor="let result of results">{{ result.name }}</li> </ul> `})export class SearchComponent { searchControl = new FormControl(); results: any[] = [];
constructor(private http: HttpClient) { this.searchControl.valueChanges .pipe( debounceTime(300), distinctUntilChanged(), switchMap(query => this.http.get<any[]>(`https://api.example.com/search?q=${query}`)) ) .subscribe(data => this.results = data); }}この例では:
debounceTime(300)はデバウンスで、ユーザーが入力を停止してから 300 ミリ秒後にリクエストを送信します。distinctUntilChanged()は重複するリクエストを避けます。switchMapは新しい入力があるたびに以前のリクエストをキャンセルし、頻繁なネットワークリクエストのパフォーマンスコストを回避します。
状態管理
複雑な Angular アプリケーションでは、状態管理は重要なトピックです。良好な状態管理は、異なるコンポーネントやモジュール間でデータを共有し、状態を同期し、データフローを簡素化し、アプリの保守性を向上させます。Angular は状態管理のさまざまな方法を提供しており、最も一般的なのはサービスを介してデータを共有する方法と、@ngrx/store などのライブラリを用いた集中管理です。
状態管理 は、アプリケーション内で状態(データ)を管理・共有する手法で、データフローの制御や UI の更新をより良くします。アプリが複雑になり、複数のインタラクティブなページ、モジュール、ユーザー操作を含む場合、異なるコンポーネントが同じデータ(例:ユーザー情報、ショッピングカートのデータなど)へのアクセス・更新を必要とすることがあり、状態管理はデータの一貫性を保ち、データ同期の複雑さを減らすのに役立ちます。
Angular での状態管理の方法
- サービスを用いた状態共有
Angular のサービスはアプリケーション全体でシングルトンとして動作します。サービスを通じて複数のコンポーネント間で状態を共有でき、複雑な状態ロジックを必要としない小規模なアプリに適しています。
- 例:シンプルな
UserServiceを作成してユーザー情報を管理する
import { Injectable } from '@angular/core';import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root'})export class UserService { private userSource = new BehaviorSubject<User | null>(null); // ユーザー情報を格納 user$ = this.userSource.asObservable(); // 公開しているユーザー情報を Observable として提供
setUser(user: User) { this.userSource.next(user); // ユーザー情報を更新 }
clearUser() { this.userSource.next(null); // ユーザー情報をクリア }}コンポーネント側では、UserService を介してユーザーの状態へアクセス・更新します:
@Component({ selector: 'app-profile', template: `<div *ngIf="user$ | async as user">{{ user.name }}</div>`})export class ProfileComponent { user$ = this.userService.user$;
constructor(private userService: UserService) {}}別のコンポーネントでユーザー情報を更新する例:
@Component({ selector: 'app-login', template: `<button (click)="login()">Login</button>`})export class LoginComponent { constructor(private userService: UserService) {}
login() { this.userService.setUser(user); // ユーザー情報を更新 }}@ngrx/storeを用いた集中管理
大規模なアプリケーションでは、@ngrx/store ライブラリを用いて集中管理を実現します。@ngrx/store は Redux スタイルを実装しており、アプリの全状態を1つのデータソースで集中管理・同期・更新します。
@ngrx/storeのコア概念- Store:アプリのグローバル状態を格納します。すべてのコンポーネントは Store からデータを取得・更新できます。
- Actions:状態変更をトリガーするイベントで、どのような状態更新を行うかを記述します。
- Reducers:
Actionsを処理するロジックで、異なるActionsに基づいて Store の状態を更新します。 - Selectors:Store から必要な状態データを取得します。
@ngrx/storeのインストール
まず、Angular CLI を使って @ngrx/store をインストールします:
ng add @ngrx/store- 状態管理のサンプル作成
カウンター状態を管理するサンプルを作成します。increment と decrement の操作を含みます。
- Action の定義
counter.actions.ts でカウンターの Action を定義します:
import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');export const decrement = createAction('[Counter] Decrement');export const reset = createAction('[Counter] Reset');- Reducer の定義
counter.reducer.ts でカウンターの Reducer を定義します:
import { createReducer, on } from '@ngrx/store';import { increment, decrement, reset } from './counter.actions';
export const initialState = 0;
const _counterReducer = createReducer( initialState, on(increment, state => state + 1), on(decrement, state => state - 1), on(reset, state => initialState));
export function counterReducer(state: any, action: any) { return _counterReducer(state, action);}- Reducer の登録
アプリの app.module.ts で Reducer を Store に登録します:
import { StoreModule } from '@ngrx/store';import { counterReducer } from './counter.reducer';
@NgModule({ imports: [ StoreModule.forRoot({ count: counterReducer }) ], bootstrap: [AppComponent]})export class AppModule {}- コンポーネントで Store を使って状態を管理
import { Component } from '@angular/core';import { Store } from '@ngrx/store';import { increment, decrement, reset } from './counter.actions';
@Component({ selector: 'app-counter', template: ` <p>Count: {{ count$ | async }}</p> <button (click)="increment()">Increment</button> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset</button> `})export class CounterComponent { count$ = this.store.select('count'); // count 状態を取得
constructor(private store: Store<{ count: number }>) {}
increment() { this.store.dispatch(increment()); }
decrement() { this.store.dispatch(decrement()); }
reset() { this.store.dispatch(reset()); }}この例では:
- Actions は増減とリセットの操作を定義します。
- Reducer は異なる
Actionに基づいて状態を更新します。 - Store は
count状態を提供します。コンポーネントは Store から状態データを購読し、対応する操作を実行できます。
状態管理のベストプラクティス
- アプリケーションの状態を集中管理します。共有状態をサービスまたは Store に格納し、異なるコンポーネント間での状態の重複保存を避けます。
- 直接状態を変更しないでください。Action と Reducer を介して状態を更新し、状態変化が追跡可能であることを保証します。
- UI とビジネスロジックを分離します。コンポーネントは UI の表示を担当し、サービスまたは Store はビジネスロジックとデータ状態を管理します。
- Selectors を使用します。Store からデータを取得する際のアクセスをシンプルかつ一貫性のあるものにします。
最適化とパフォーマンス調整
Angular アプリの構築とデプロイ時には、パフォーマンスの最適化が、アプリの高速な読み込みとレスポンスを保証する鍵です。Angular は遅延ロード、AOT(Ahead-Of-Time コンパイル)、Tree Shaking、変更検出戦略など、さまざまな最適化技術を提供します。本節ではこれらの最適化テクニックを紹介し、アプリのパフォーマンス向上を支援します。
レイジーロード(Lazy Loading)
レイジーロードは、必要に応じてモジュールを読み込む技術です。ユーザーが必要とする時のみ特定のモジュールをロードすることで、アプリの初期ロード時間を短縮し、起動速度を向上させます。
実装例
AdminModule があると仮定して、ルーティングでレイジーロードを設定して必要に応じて読み込むようにします。
例:
import { Routes } from '@angular/router';
const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },];ここでは、ユーザーが /admin にナビゲートした場合にのみ AdminModule が読み込まれます。
AOT コンパイル(Ahead-Of-Time Compilation)
AOT コンパイルは、Angular のテンプレートをビルド時に事前に JavaScript にコンパイルします。これにより、ブラウザ側の作業量を削減し、パッケージサイズを小さくし、実行速度を向上させます。
AOT コンパイルの利用
Angular CLI でビルドする際、AOT はデフォルトで有効になっており、ng build --prod で本番環境バージョンをビルドすると、AOT やその他の最適化が有効になります。
ng build --prodAOT コンパイルの利点:
- 実行速度の向上:ブラウザ側のコンパイル時間を短縮します。
- より小さなパッケージサイズ:コンパイル済みのテンプレートコードのみをパッケージします。
- テンプレートエラーの検出:ビルド時にテンプレート構文エラーを検出し、コードの安全性を高めます。
Tree Shaking
Tree Shaking は、ビルド時に未使用コードを削除する技術です。Angular は Webpack を利用してビルドを行い、未使用のモジュールやコードを自動的に削除して、アプリのパッケージサイズを小さくします。
Tree Shaking の最適化方法
- ES6 モジュールの使用:コードが ES6 のモジュール構文(
importとexport)を使用していることを確認します。 - 不要な依存関係の削除:必要なライブラリ・モジュールのみをインポート・使用します。
- RxJS のインポートの最適化:RxJS は必要な部分だけをインポートします。例えば、
import { map } from 'rxjs/operators'のように、rxjs全体をインポートするのではなく、必要な部分だけをインポートします。
変更検出戦略(Change Detection Strategy)
Angular のデフォルトの変更検出機構はすべてのコンポーネントを検出します。これがパフォーマンスのオーバーヘッドになることがあります。OnPush 戦略を使用して変更検出を最適化し、入力データが変わるか、コンポーネント内のイベントが発生したときにのみ検出を行うようにします。
OnPush 戦略を使用
コンポーネントに changeDetection: ChangeDetectionStrategy.OnPush を設定して OnPush 戦略を有効にします。
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({ selector: 'app-my-component', templateUrl: './my-component.component.html', changeDetection: ChangeDetectionStrategy.OnPush // OnPush 戦略を有効化})export class MyComponent {}OnPush 戦略の利点:
- 不要な変更検出の削減:入力データやイベントが発生したときのみ再レンダリングされます。
- パフォーマンスの向上:純粋な表示専用コンポーネントに適しています。
Service Worker を使ったキャッシュ
Angular はアプリを Progressive Web App(PWA) に設定することをサポートしており、Service Worker を利用してリソースをキャッシュし、オフライン対応とパフォーマンスを向上させます。
Service Worker のサポートを追加
Angular CLI を使って Service Worker を追加します:
ng add @angular/pwaこれにより Service Worker の設定ファイルが自動生成され、アプリに登録されます。本番環境では、Service Worker が静的リソースを自動的にキャッシュし、ページの読み込みを高速化します。
- PWA 機能の検証
- アプリを開いた状態で、ブラウザの開発者ツールを開き、Service Worker が正しく登録されているかを確認します。
- ネットワークをオフにしてページをリフレッシュすると、オフラインキャッシュからコンテンツが読み込まれることを確認します。
画像と静的リソースの最適化
Web アプリでは、画像や静的リソースが大きな割合を占めることが多いため、圧縮や遅延読み込みなどで最適化します。
-
画像の圧縮と遅延読み込み
-
圧縮フォーマットの使用:WebP など、ファイルサイズの小さい画像フォーマットをなるべく使用します。
-
画像の遅延読み込み:
loading="lazy"属性を使って遅延読み込みを実装できます。画像がビューPortに入るタイミングで読み込まれます。
<img src="image.webp" loading="lazy" alt="Example Image">Angular の内蔵最適化ツールの活用
Angular CLI は、アプリのパッケージサイズとパフォーマンスを向上させる多くの内蔵ツールを提供します。以下の方法でビルドをさらに最適化できます。
ng build --prod のデフォルト最適化
ng build --prod を実行すると、Angular CLI は複数の最適化を自動的に適用します。これには以下が含まれます:
- AOT コンパイル:テンプレートを事前にコンパイル
- Tree Shaking:未使用コードを削除
- Minification:コードを圧縮
- Bundle Splitting:コードを分割して、単一のパッケージサイズを削減
Angular パフォーマンス向上のベストプラクティス
- モジュールを必要に応じてロードする:レイジーロードを活用して、特定のモジュールを遅延ロードします。これにより、メインパッケージのサイズを削減します。
- 純粋な表示型コンポーネントを使用する:
OnPush戦略を適用して、変更検出の発生回数を削減します。表示専用のコンポーネントに適しています。 - テンプレートでの複雑な計算を避ける:コンポーネントクラスで計算を行い、結果をテンプレートに渡します。
- 3rd パーティのライブラリ導入を最適化:RxJS 演算子など、必要な分だけをインポートします。
- Web Worker の活用:計算集約タスクを Web Worker に任せ、メインスレッドのブロックを回避します。
PWA と国際化
この節では、高度な2つのテーマ、PWA(Progressive Web App)と i18n(国際化)について紹介します。PWA は Angular アプリにオフライン対応とネイティブアプリの体験を提供し、国際化は多言語対応を可能にします。
渐进式 Web 应用(PWA)
渐进式 Web 应用(PWA) は、現代の Web 技術を利用して Web アプリをネイティブアプリのようにスムーズに実行させ、オフライン機能を提供するアプリケーションです。Angular は組み込みの PWA サポートを提供しており、オフライン対応、プッシュ通知などの機能を持つ Web アプリの構築を容易にします。
- Angular アプリを PWA に変換
Angular CLI は、既存のプロジェクトを PWA アプリへ変換する簡単なコマンドを提供します。
- PWA サポートを追加
プロジェクトディレクトリで以下のコマンドを実行します:
ng add @angular/pwaこのコマンドを実行すると、Service Worker の設定ファイル(ngsw-config.json)とアプリのアイコン(manifest.webmanifest ファイル)が自動生成されます。これらのファイルはアプリのオフラインキャッシュやアイコンなどの PWA 設定を構成します。
ngsw-config.jsonの設定
ngsw-config.json は Service Worker の設定ファイルで、どのファイルをキャッシュするかを定義します。デフォルトでは、Angular はアプリの主要リソース(JavaScript、CSS、HTML など)をキャッシュします。必要に応じてキャッシュ戦略をカスタマイズできます。
- 本番版のビルド
PWA を利用する場合、アプリは本番モードで動作する必要があります:
ng build --prod- デプロイ
アプリをサーバーにデプロイすると、ブラウザが Service Worker を自動検出・登録し、オフライン機能を有効化します。
- PWA 機能の検証
- アプリを開いたときに、ブラウザの開発者ツールで Service Worker が正常に登録されているかを確認します。
- ネットワークをオフにしてページをリフレッシュしてみて、オフラインキャッシュから内容がロードされることを確認します。
国際化
国際化(i18n) は、アプリ内のテキスト、日付フォーマット、数字フォーマットなどを異なる言語・地域の形式に変換するプロセスで、アプリが異なる言語と文化背景に対応できるようにします。Angular は組み込みの i18n サポートを提供しており、多言語対応を簡単に追加できます。
- Angular の i18n 機能を使う
Angular の i18n 機能は、アプリのテキストを複数言語に翻訳するのに役立ちます。以下は設定手順です。
- テキストのマーク付け
テンプレート内で翻訳するテキストに i18n 属性を付与します。
<h1 i18n="@@welcome">Welcome to our app!</h1><p i18n="@@intro">This is an example of internationalized content.</p>ここで、i18n="@@key" はテキスト内容に一意の翻訳キーを付与し、後で検索・翻訳するのに便利です。
- 翻訳ファイルの抽出
Angular CLI を使って翻訳ファイルを抽出します。以下のコマンドを実行すると、src/locale ディレクトリに messages.xlf ファイルが生成されます:
ng extract-i18n生成された messages.xlf は XML ファイルで、アプリ内のすべてのマーク済みテキストの翻訳内容を含みます。
- 翻訳内容
messages.xlf ファイルには、各言語の翻訳ファイルを作成します。翻訳内容を充填し、ファイルを保存します。例えば、フランス語の翻訳ファイルとして messages.fr.xlf を作成します。
<trans-unit id="welcome" datatype="html"> <source>Welcome to our app!</source> <target>Bienvenue dans notre application!</target></trans-unit>
<trans-unit id="intro" datatype="html"> <source>This is an example of internationalized content.</source> <target>Ceci est un exemple de contenu internationalisé.</target></trans-unit>- 多言語コンパイルの設定
angular.json ファイルに多言語設定を追加します。例:
"projects": { "your-app-name": { "i18n": { "sourceLocale": "en", "locales": { "fr": "src/locale/messages.fr.xlf" } } }}- 多言語バージョンのビルド
アプリをビルドする際、言語ごとに別々のバージョンを生成します:
ng build --prod --localizeこれにより、各言語に対応するバージョンが自動的に生成されます(例:dist/your-app-name/fr ディレクトリにフランス語版のアプリが生成されます)。
- 動的な言語切替(任意)
実行時に言語を動的に切り替えたい場合は、第三者ライブラリ(例:ngx-translate)を使用して、より柔軟な国際化サポートを実現できます。
部分信息可能已经过时









