Angular核心概念进阶
1. 深入理解Angular的模块系统
在Angular中,模块是一种组织代码的方式,它把相关的组件、指令、管道和服务组合在一起。
1.1 NgModule基础
NgModule是Angular的模块定义机制,通过装饰器@NgModule
来定义。一个简单的NgModule示例如下:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
这里imports
数组指定了该模块依赖的其他模块,declarations
数组包含了该模块中定义的组件、指令和管道,bootstrap
数组指定了应用的根组件。
1.2 模块类型
- 根模块:通常是
AppModule
,它是应用启动时首先加载的模块。根模块负责引导应用,它的bootstrap
属性指定了应用的根组件。 - 特性模块:用于将应用功能划分成不同的特性区域。比如一个电商应用,可能有产品模块、订单模块等。以产品模块为例:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductListComponent } from './product - list.component';
import { ProductDetailComponent } from './product - detail.component';
@NgModule({
imports: [CommonModule],
declarations: [ProductListComponent, ProductDetailComponent],
exports: [ProductListComponent, ProductDetailComponent]
})
export class ProductModule {}
这里exports
数组将模块内的组件暴露出去,以便其他模块使用。
- 共享模块:用于存放多个模块共享的组件、指令和管道。例如,我们创建一个
SharedModule
:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoadingSpinnerComponent } from './loading - spinner.component';
import { AlertComponent } from './alert.component';
@NgModule({
imports: [CommonModule],
declarations: [LoadingSpinnerComponent, AlertComponent],
exports: [LoadingSpinnerComponent, AlertComponent]
})
export class SharedModule {}
其他模块只需导入SharedModule
,就可以使用其中的组件。
1.3 模块的懒加载
懒加载是一种延迟加载模块的技术,能提高应用的初始加载性能。通过在路由配置中使用loadChildren
属性实现。
const routes: Routes = [
{
path: 'products',
loadChildren: () => import('./product.module').then(m => m.ProductModule)
}
];
这样,当用户访问/products
路径时,ProductModule
才会被加载。
2. 组件交互的高级技巧
组件是Angular应用的基本构建块,组件之间的交互至关重要。
2.1 父子组件交互
- 父传子:通过
@Input()
装饰器实现。父组件模板中传递数据:
<app - child - component [inputValue]="parentData"></app - child - component>
子组件定义@Input()
属性接收数据:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app - child - component',
templateUrl: './child - component.html'
})
export class ChildComponent {
@Input() inputValue: string;
}
- 子传父:通过
@Output()
装饰器和EventEmitter
实现。子组件触发事件:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app - child - component',
templateUrl: './child - component.html'
})
export class ChildComponent {
@Output() childEvent = new EventEmitter();
onButtonClick() {
this.childEvent.emit('Data from child');
}
}
父组件在模板中监听事件:
<app - child - component (childEvent)="handleChildEvent($event)"></app - child - component>
export class ParentComponent {
handleChildEvent(data: string) {
console.log(data);
}
}
2.2 非父子组件交互
- 通过服务:创建一个共享服务,不同组件通过注入该服务来共享数据。例如,创建
DataService
:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DataService {
private data: string;
setData(value: string) {
this.data = value;
}
getData() {
return this.data;
}
}
组件A设置数据:
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app - component - a',
templateUrl: './component - a.html'
})
export class ComponentA {
constructor(private dataService: DataService) {}
onSetData() {
this.dataService.setData('Data from ComponentA');
}
}
组件B获取数据:
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app - component - b',
templateUrl: './component - b.html'
})
export class ComponentB {
data: string;
constructor(private dataService: DataService) {
this.data = this.dataService.getData();
}
}
- 通过
Subject
和BehaviorSubject
:Subject
是一种可以多播给多个观察者的Observable
。BehaviorSubject
会保存“当前”值,并将其发送给新的订阅者。以BehaviorSubject
为例:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SharedSubjectService {
private subject = new BehaviorSubject<string>('Initial value');
public observable$ = this.subject.asObservable();
updateValue(value: string) {
this.subject.next(value);
}
}
组件订阅:
import { Component } from '@angular/core';
import { SharedSubjectService } from './shared - subject.service';
@Component({
selector: 'app - subscribing - component',
templateUrl: './subscribing - component.html'
})
export class SubscribingComponent {
data: string;
constructor(private sharedSubjectService: SharedSubjectService) {
this.sharedSubjectService.observable$.subscribe((value) => {
this.data = value;
});
}
}
组件更新:
import { Component } from '@angular/core';
import { SharedSubjectService } from './shared - subject.service';
@Component({
selector: 'app - updating - component',
templateUrl: './updating - component.html'
})
export class UpdatingComponent {
constructor(private sharedSubjectService: SharedSubjectService) {}
onUpdate() {
this.sharedSubjectService.updateValue('New value');
}
}
3. 深入RxJS在Angular中的应用
RxJS(Reactive Extensions for JavaScript)是一个用于处理异步操作和事件流的库,在Angular中被广泛应用。
3.1 基本概念
- Observable:表示一个可观察的序列,它可以发出零个或多个值,并且可以在完成时或发生错误时通知观察者。例如:
import { Observable } from 'rxjs';
const observable = new Observable((observer) => {
observer.next(1);
observer.next(2);
observer.complete();
});
observable.subscribe((value) => {
console.log(value);
});
这里subscribe
方法用于注册观察者,next
方法用于发出值,complete
方法表示序列结束。
- Observer:是一个对象,它有
next
、error
和complete
三个回调函数,用于处理Observable
发出的值、错误和完成通知。 - Subscription:当我们订阅一个
Observable
时,会返回一个Subscription
对象。它有一个unsubscribe
方法,用于取消订阅,防止内存泄漏。例如:
import { Observable } from 'rxjs';
const observable = new Observable((observer) => {
let count = 0;
const intervalId = setInterval(() => {
observer.next(count++);
if (count === 5) {
observer.complete();
clearInterval(intervalId);
}
}, 1000);
});
const subscription = observable.subscribe((value) => {
console.log(value);
});
// 取消订阅
setTimeout(() => {
subscription.unsubscribe();
}, 3000);
3.2 RxJS操作符
- map:用于将
Observable
发出的每个值映射为一个新的值。例如,将数组中的每个数字加倍:
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
const numbers = [1, 2, 3];
const observable = new Observable((observer) => {
numbers.forEach((number) => {
observer.next(number);
});
observer.complete();
});
observable.pipe(
map((number) => number * 2)
).subscribe((value) => {
console.log(value);
});
- filter:用于过滤
Observable
发出的值,只允许满足条件的值通过。例如,只输出偶数:
import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
const numbers = [1, 2, 3, 4, 5];
const observable = new Observable((observer) => {
numbers.forEach((number) => {
observer.next(number);
});
observer.complete();
});
observable.pipe(
filter((number) => number % 2 === 0)
).subscribe((value) => {
console.log(value);
});
- switchMap:用于将
Observable
发出的值映射为另一个Observable
,并取消之前的Observable
订阅。常用于处理HTTP请求等异步操作。例如,根据用户输入搜索数据:
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app - search - component',
templateUrl: './search - component.html'
})
export class SearchComponent {
searchTerm: string;
results: Observable<any>;
constructor(private http: HttpClient) {}
onSearch() {
this.results = this.http.get(`https://api.example.com/search?q=${this.searchTerm}`).pipe(
switchMap((response) => {
return this.http.get(`https://api.example.com/detail?id=${response.id}`);
})
);
}
}
3.3 RxJS在Angular服务中的应用
在Angular服务中,常使用RxJS来处理异步数据。例如,创建一个获取用户数据的服务:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient) {}
getUserData(): Observable<any> {
return this.http.get('https://api.example.com/user');
}
}
组件中使用该服务:
import { Component } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app - user - component',
templateUrl: './user - component.html'
})
export class UserComponent {
userData;
constructor(private userService: UserService) {
this.userService.getUserData().subscribe((data) => {
this.userData = data;
});
}
}
4. Angular的依赖注入
依赖注入(Dependency Injection,简称DI)是Angular的核心特性之一,它有助于实现松耦合的代码结构。
4.1 基本概念
- 依赖:一个组件或服务所需要的其他对象。例如,一个
UserComponent
可能依赖于UserService
来获取用户数据。 - 注入器:负责创建和管理依赖对象的实例,并将它们提供给需要的组件或服务。Angular有一个分层的注入器系统,包括根注入器和组件级注入器。
4.2 创建和提供服务
通过@Injectable
装饰器创建服务,并使用providedIn
属性指定服务的提供范围。例如:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoggerService {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
这里providedIn: 'root'
表示该服务由根注入器提供,应用中的任何组件都可以使用。
4.3 注入服务
组件通过构造函数注入服务。例如:
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
selector: 'app - example - component',
templateUrl: './example - component.html'
})
export class ExampleComponent {
constructor(private logger: LoggerService) {
this.logger.log('Component initialized');
}
}
4.4 自定义注入器
在某些情况下,我们可能需要自定义注入器。可以通过在组件的providers
数组中提供服务来创建组件级注入器。例如:
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
selector: 'app - custom - injector - component',
templateUrl: './custom - injector - component.html',
providers: [LoggerService]
})
export class CustomInjectorComponent {
constructor(private logger: LoggerService) {
this.logger.log('Component in custom injector');
}
}
这里CustomInjectorComponent
有自己的组件级注入器,它创建的LoggerService
实例与其他组件使用的根注入器提供的实例不同。
4.5 依赖注入的优势
- 松耦合:组件不需要自己创建依赖对象,只需要声明依赖,这使得组件更易于测试和维护。例如,在测试
ExampleComponent
时,可以很容易地替换LoggerService
为模拟服务。 - 可复用性:服务可以被多个组件复用,提高了代码的复用性。例如,
LoggerService
可以被多个组件用于记录日志。
5. 指令的高级应用
指令是Angular中用于修改DOM元素行为的代码块,分为属性指令和结构指令。
5.1 自定义属性指令
自定义属性指令用于改变元素的外观或行为。例如,创建一个HighlightDirective
,当鼠标悬停时高亮元素:
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
在模板中使用:
<p appHighlight>Hover over me to highlight</p>
5.2 自定义结构指令
自定义结构指令用于改变DOM的结构。例如,创建一个UnlessDirective
,与*ngIf
相反的逻辑:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appUnless]'
})
export class UnlessDirective {
private hasView = false;
constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {}
@Input() set appUnless(condition: boolean) {
if (!condition &&!this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
在模板中使用:
<div *appUnless="isLoggedIn">
<p>Please log in</p>
</div>
5.3 指令的生命周期
指令和组件一样有生命周期钩子。例如,ngOnInit
用于在指令初始化后执行逻辑,ngOnDestroy
用于在指令销毁时清理资源。以HighlightDirective
为例,添加ngOnInit
钩子:
import { Directive, ElementRef, HostListener, OnInit } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective implements OnInit {
constructor(private el: ElementRef) {}
ngOnInit() {
this.highlight('lightgray');
}
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
这样,指令初始化时元素会有一个初始的背景色。
6. Angular中的表单处理
表单是Web应用中常见的交互元素,Angular提供了强大的表单处理功能,包括模板驱动表单和响应式表单。
6.1 模板驱动表单
模板驱动表单通过在模板中使用指令来创建和管理表单。例如,创建一个简单的登录表单:
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm.value)">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" ngModel required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" ngModel required>
</div>
<button type="submit">Submit</button>
</form>
组件中处理表单提交:
import { Component } from '@angular/core';
@Component({
selector: 'app - login - form',
templateUrl: './login - form.html'
})
export class LoginFormComponent {
onSubmit(formData) {
console.log(formData);
}
}
这里ngModel
指令用于双向数据绑定,#loginForm="ngForm"
创建了一个NgForm
实例,(ngSubmit)
绑定表单提交事件。
6.2 响应式表单
响应式表单通过在组件类中创建和管理表单模型。例如,创建一个注册表单:
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app - register - form',
templateUrl: './register - form.html'
})
export class RegisterFormComponent {
registerForm: FormGroup;
constructor() {
this.registerForm = new FormGroup({
'username': new FormControl('', Validators.required),
'email': new FormControl('', [Validators.required, Validators.email]),
'password': new FormControl('', Validators.minLength(6))
});
}
onSubmit() {
if (this.registerForm.valid) {
console.log(this.registerForm.value);
}
}
}
模板中使用:
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<div>
<label for="username">Username:</label>
<input type="text" id="username" formControlName="username">
<div *ngIf="registerForm.get('username').hasError('required') && (registerForm.get('username').touched || registerForm.get('username').dirty)">
Username is required
</div>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" formControlName="email">
<div *ngIf="registerForm.get('email').hasError('required') && (registerForm.get('email').touched || registerForm.get('email').dirty)">
Email is required
</div>
<div *ngIf="registerForm.get('email').hasError('email') && (registerForm.get('email').touched || registerForm.get('email').dirty)">
Please enter a valid email
</div>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" formControlName="password">
<div *ngIf="registerForm.get('password').hasError('minlength') && (registerForm.get('password').touched || registerForm.get('password').dirty)">
Password must be at least 6 characters
</div>
</div>
<button type="submit">Submit</button>
</form>
这里FormGroup
和FormControl
用于创建表单模型,formControlName
将模板中的输入元素与表单模型关联。
6.3 表单验证
- 内置验证器:Angular提供了许多内置验证器,如
required
、email
、minLength
等。在上面的响应式表单示例中已经使用到。 - 自定义验证器:可以创建自定义验证器。例如,创建一个验证用户名是否唯一的自定义验证器:
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function uniqueUsernameValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
// 模拟异步验证
const isUnique = true; // 实际应通过服务检查用户名是否唯一
return isUnique? null : { 'usernameNotUnique': true };
};
}
在响应式表单中使用:
this.registerForm = new FormGroup({
'username': new FormControl('', [Validators.required, uniqueUsernameValidator()]),
'email': new FormControl('', [Validators.required, Validators.email]),
'password': new FormControl('', Validators.minLength(6))
});
7. Angular的路由与导航
路由是Angular应用中实现页面导航和视图切换的关键机制。
7.1 基本路由配置
通过@angular/router
模块进行路由配置。例如,创建一个简单的路由配置:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
在模板中使用router - outlet
显示路由组件:
<router - outlet></router - outlet>
使用routerLink
指令创建导航链接:
<ul>
<li><a routerLink="">Home</a></li>
<li><a routerLink="about">About</a></li>
</ul>
7.2 路由参数
路由参数用于传递数据。例如,创建一个产品详情路由,通过参数传递产品ID:
const routes: Routes = [
{ path: 'products/:id', component: ProductDetailComponent }
];
在ProductDetailComponent
中获取参数:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app - product - detail',
templateUrl: './product - detail.html'
})
export class ProductDetailComponent implements OnInit {
productId: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.productId = this.route.snapshot.paramMap.get('id');
}
}
导航到产品详情页:
<a [routerLink]="['/products', product.id]">{{ product.name }}</a>
7.3 嵌套路由
嵌套路由用于创建具有层次结构的路由。例如,一个产品模块可能有产品列表和产品详情,产品详情又有评论子路由:
const productRoutes: Routes = [
{
path: 'products', component: ProductListComponent,
children: [
{ path: ':id', component: ProductDetailComponent,
children: [
{ path: 'comments', component: ProductCommentsComponent }
]
}
]
}
];
在ProductDetailComponent
的模板中添加router - outlet
用于显示子路由组件:
<router - outlet></router - outlet>
7.4 路由守卫
路由守卫用于保护路由,控制是否允许访问。例如,创建一个AuthGuard
来确保只有登录用户可以访问某些路由:
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.authService.isLoggedIn()) {
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
}
在路由配置中使用:
const routes: Routes = [
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
];