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

Angular核心概念

2023-02-037.1k 阅读

组件(Components)

组件的定义与作用

在Angular中,组件是应用的基本构建块。它是一个带有 @Component 装饰器的类,负责管理应用的一部分视图及其相关逻辑。每个组件都有自己的模板(template),用于定义组件呈现的HTML结构;有自己的样式(styles),用于设置组件的外观;还有一个类,用于处理与组件相关的业务逻辑。

例如,一个简单的Angular应用可能包含一个导航栏组件、一个内容展示组件等。每个组件独立封装,使得代码易于维护和复用。

组件的创建

使用Angular CLI可以快速创建组件。在项目根目录下执行以下命令:

ng generate component component - name

上述命令会在src/app目录下创建一个名为component - name的组件。生成的组件目录结构通常包含以下文件:

  • component - name.component.ts:组件的逻辑代码文件,包含@Component装饰器和组件类。
  • component - name.component.html:组件的模板文件,定义组件的HTML结构。
  • component - name.component.css:组件的样式文件,用于设置组件的样式。
  • component - name.component.spec.ts:组件的测试文件,用于编写测试用例。

组件类

组件类是包含业务逻辑的地方。以下是一个简单的组件类示例:

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

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.component.html',
  styleUrls: ['./my - component.component.css']
})
export class MyComponent {
  message: string = 'Hello, Angular!';

  constructor() { }

  changeMessage() {
    this.message = 'Message has been changed!';
  }
}

在上述代码中,@Component装饰器定义了组件的元数据,包括选择器(selector),用于在HTML中引用该组件;模板文件路径(templateUrl)和样式文件路径(styleUrls)。组件类MyComponent中定义了一个属性message和一个方法changeMessage,用于改变message的值。

组件模板

组件模板是组件呈现给用户的HTML结构。以下是与上述组件类对应的模板文件my - component.component.html示例:

<div>
  <p>{{message}}</p>
  <button (click)="changeMessage()">Change Message</button>
</div>

在模板中,使用双花括号{{message}}来插值组件类中的message属性,将其值显示在页面上。按钮的(click)事件绑定到组件类中的changeMessage方法,当按钮被点击时,会调用该方法改变message的值,从而更新页面显示。

组件样式

组件样式可以通过stylesstyleUrls属性在@Component装饰器中定义。如果使用styles,可以直接在组件类中定义CSS样式:

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.component.html',
  styles: [
    'p { color: blue; }',
    'button { background - color: green; color: white; }'
  ]
})
export class MyComponent {
  // ...
}

如果使用styleUrls,则需要指定外部CSS文件路径:

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.component.html',
  styleUrls: ['./my - component.component.css']
})
export class MyComponent {
  // ...
}

my - component.component.css文件中编写CSS样式:

p {
  color: blue;
}
button {
  background - color: green;
  color: white;
}

组件样式默认是封装的,即组件的样式不会影响其他组件,其他组件的样式也不会影响该组件。这有助于避免样式冲突。

模块(Modules)

模块的概念

Angular模块是一个组织相关代码的容器。它使用@NgModule装饰器来定义,将一组组件、指令、管道以及服务组合在一起,形成一个功能单元。模块可以包含导入(imports),用于引入其他模块的功能;导出(exports),用于将模块内的某些功能暴露给其他模块使用。

根模块(Root Module)

每个Angular应用都有一个根模块,通常命名为AppModule。根模块引导应用的启动过程。以下是AppModule的基本结构示例:

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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • declarations:声明模块内的组件、指令和管道。
  • imports:导入其他模块,这里导入BrowserModule,它提供了在浏览器中运行应用所需的基础功能。
  • providers:注册应用所需的服务。
  • bootstrap:指定应用的根组件,这里是AppComponent

特性模块(Feature Modules)

除了根模块,还可以创建特性模块来组织特定功能的代码。例如,一个电商应用可能有用户模块(UserModule)用于处理用户相关的功能,产品模块(ProductModule)用于处理产品相关的功能等。

以下是一个简单的UserModule示例:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserComponent } from './user.component';
import { UserService } from './user.service';

@NgModule({
  declarations: [
    UserComponent
  ],
  imports: [
    CommonModule
  ],
  providers: [
    UserService
  ],
  exports: [
    UserComponent
  ]
})
export class UserModule { }

在上述代码中,UserModule声明了UserComponent,导入了CommonModule(提供一些常用的指令和管道),注册了UserService,并将UserComponent导出,以便其他模块可以使用。

共享模块(Shared Modules)

共享模块用于存放多个模块都可能用到的组件、指令和管道等。例如,一个应用中有一些通用的按钮组件、图标组件等,可以放在共享模块中。

以下是一个简单的SharedModule示例:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button.component';
import { IconComponent } from './icon.component';

@NgModule({
  declarations: [
    ButtonComponent,
    IconComponent
  ],
  imports: [
    CommonModule
  ],
  exports: [
    ButtonComponent,
    IconComponent
  ]
})
export class SharedModule { }

其他模块只需导入SharedModule,就可以使用其中导出的组件。

服务(Services)

服务的定义与用途

服务是一个独立的类,用于提供特定的功能或数据。它通常用于处理与组件无关的业务逻辑,如数据获取、存储管理等。服务可以在多个组件之间共享,有助于实现代码的复用和分离关注点。

例如,一个服务可以用于从后端API获取数据,另一个服务可以用于处理用户认证逻辑。

服务的创建

使用Angular CLI创建服务:

ng generate service service - name

这会在src/app目录下创建一个名为service - name的服务文件,通常包含两个文件:service - name.service.ts(服务的逻辑代码)和service - name.service.spec.ts(服务的测试文件)。

服务示例:数据获取服务

以下是一个简单的数据获取服务示例,使用HttpClient从后端API获取数据:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  constructor(private http: HttpClient) { }

  getData(): Observable<any> {
    return this.http.get('https://example.com/api/data');
  }
}

在上述代码中,@Injectable装饰器标记该类为可注入的服务。providedIn: 'root'表示该服务在根模块中提供,应用的任何组件都可以使用它。getData方法使用HttpClientget方法从指定的API地址获取数据,并返回一个Observable对象。

在组件中使用服务

在组件中使用服务,需要先在组件的构造函数中注入服务。以下是一个使用DataService的组件示例:

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.component.html'
})
export class MyComponent {
  data: any;

  constructor(private dataService: DataService) { }

  ngOnInit() {
    this.dataService.getData().subscribe(response => {
      this.data = response;
    });
  }
}

在上述组件中,通过构造函数注入DataService,并在ngOnInit生命周期钩子中调用getData方法获取数据。subscribe方法用于订阅Observable对象,当数据获取成功时,将数据赋值给组件的data属性。

依赖注入(Dependency Injection)

依赖注入的概念

依赖注入是一种设计模式,它允许将依赖(如服务)传递给一个类,而不是让类自己创建依赖。在Angular中,依赖注入是一个核心机制,使得组件和服务之间的依赖关系更加清晰和易于管理。

依赖注入的原理

Angular使用注入器(Injector)来创建和管理依赖的实例。注入器维护一个依赖的注册表,当一个类需要某个依赖时,注入器会查找注册表并创建或返回已创建的依赖实例。

例如,当一个组件需要DataService时,注入器会查找DataService的注册表,如果没有找到实例,则创建一个新的实例并返回给组件。

依赖注入的使用

在组件或服务的构造函数中声明依赖,Angular会自动注入相应的实例。例如:

import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.component.html'
})
export class MyComponent {
  constructor(private dataService: DataService) { }
}

在上述代码中,MyComponent的构造函数声明了DataService依赖,Angular会自动将DataService的实例注入到MyComponent中。

自定义注入器

除了根注入器,还可以创建自定义注入器。例如,在一个组件树中,可以在某个组件级别创建一个注入器,该注入器可以覆盖或提供特定于该组件及其子组件的依赖。

以下是一个简单的示例,在组件中创建自定义注入器:

import { Component, Injector } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.component.html',
  providers: [
    {
      provide: DataService,
      useClass: CustomDataService
    }
  ]
})
export class MyComponent {
  constructor(private injector: Injector) {
    const dataService = injector.get(DataService);
  }
}

在上述代码中,通过providers数组在组件级别提供了一个自定义的DataService实现(CustomDataService)。组件通过Injector获取DataService实例,此时获取到的是自定义的CustomDataService实例。

指令(Directives)

指令的分类与概念

Angular指令是用于扩展HTML的行为。它分为三类:

  • 组件指令:实际上就是组件,带有模板的指令。
  • 结构型指令:用于改变DOM结构,如*ngIf*ngFor等。
  • 属性型指令:用于改变元素的外观或行为,如ngModelngStyle等。

结构型指令

*ngIf

*ngIf指令用于根据条件决定是否渲染一个元素或一组元素。例如:

<div *ngIf="isLoggedIn">
  <p>Welcome, user!</p>
</div>

在上述代码中,如果组件类中的isLoggedIn属性为true,则<div>及其内部内容会被渲染到页面上;否则,<div>及其内部内容不会出现在DOM中。

*ngFor

*ngFor指令用于迭代一个数组或可迭代对象,并为每个元素创建一个模板实例。例如:

<ul>
  <li *ngFor="let item of items">{{item.name}}</li>
</ul>

假设组件类中有一个items数组,每个数组元素有name属性,上述代码会为items数组中的每个元素创建一个<li>元素,并将元素的name属性值显示出来。

属性型指令

ngModel

ngModel指令用于在表单元素和组件的属性之间建立双向数据绑定。例如,在一个输入框中:

<input type="text" [(ngModel)]="userName">
<p>Your name is: {{userName}}</p>

在上述代码中,[(ngModel)]语法实现了双向数据绑定。当用户在输入框中输入内容时,userName属性会更新;当userName属性通过组件逻辑改变时,输入框中的内容也会更新。

ngStyle

ngStyle指令用于根据表达式动态设置元素的样式。例如:

<div [ngStyle]="{ 'color': isActive? 'green' :'red' }">
  This div's color changes based on isActive.
</div>

在上述代码中,如果组件类中的isActive属性为true,则<div>的颜色为绿色;否则为红色。

管道(Pipes)

管道的定义与用途

管道用于对数据进行转换和格式化。例如,将日期格式化为特定的字符串格式,将数字进行货币格式化等。Angular提供了一些内置管道,同时也允许开发者创建自定义管道。

内置管道

DatePipe

DatePipe用于格式化日期。例如:

<p>{{today | date: 'yyyy - MM - dd' }}</p>

假设组件类中有一个today属性表示当前日期,上述代码会将today格式化为yyyy - MM - dd的字符串格式。

CurrencyPipe

CurrencyPipe用于将数字格式化为货币格式。例如:

<p>{{price | currency: 'USD' }}</p>

假设组件类中有一个price属性表示价格,上述代码会将price格式化为美元货币格式。

自定义管道

创建自定义管道,首先使用Angular CLI:

ng generate pipe pipe - name

这会生成一个管道类文件,如pipe - name.pipe.ts。以下是一个简单的自定义管道示例,用于将字符串转换为大写:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'toUpperCase'
})
export class ToUpperCasePipe implements PipeTransform {
  transform(value: string): string {
    return value.toUpperCase();
  }
}

在模板中使用自定义管道:

<p>{{message | toUpperCase }}</p>

假设组件类中有一个message属性,上述代码会将message的值转换为大写并显示。

路由(Routing)

路由的概念与作用

路由允许根据不同的URL导航到不同的组件视图。它使得单页应用(SPA)能够模拟多页应用的导航效果,而无需重新加载整个页面。通过路由,用户可以在应用的不同功能模块之间进行切换。

配置路由

首先在项目中创建路由模块。使用Angular CLI:

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

上述命令会创建一个名为app - routing.module.ts的路由模块,并将其导入到AppModule中。

以下是一个简单的路由配置示例:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } 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 { }

在上述代码中,Routes数组定义了路由规则。path表示URL路径,component表示该路径对应的组件。RouterModule.forRoot(routes)用于配置根路由。

路由导航

在模板中,可以使用<a>标签或RouterLink指令进行路由导航。例如:

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

<router - outlet>是一个占位符,用于显示当前路由对应的组件。当用户点击HomeAbout链接时,routerLink指令会根据配置的路由规则导航到相应的组件,并在<router - outlet>处显示组件内容。

路由参数

路由可以传递参数。例如,在路由配置中定义带参数的路由:

const routes: Routes = [
  { path: 'user/:id', component: UserComponent }
];

在模板中导航到带参数的路由:

<a [routerLink]="['/user', userId]">View User</a>

在组件中获取路由参数:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app - user',
  templateUrl: './user.component.html'
})
export class UserComponent implements OnInit {
  userId: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.params.subscribe(params => {
      this.userId = params['id'];
    });
  }
}

在上述代码中,ActivatedRoute服务用于获取当前路由的参数。通过subscribe方法监听参数变化,当路由参数改变时,userId会更新。

响应式编程(Reactive Programming)

响应式编程概念

响应式编程是一种基于异步数据流和变化传播的编程范式。在Angular中,通过RxJS(Reactive Extensions for JavaScript)库来实现响应式编程。它使得处理异步操作(如HTTP请求、事件处理等)更加简洁和可维护。

ObservableObserver

Observable是一个可观察对象,它可以发出多个值,如HTTP请求的响应、用户事件等。Observer是一个对象,用于监听Observable发出的值。

以下是一个简单的Observable示例:

import { Observable } from 'rxjs';

const observable = new Observable(observer => {
  observer.next('Value 1');
  observer.next('Value 2');
  observer.complete();
});

const observer = {
  next: value => console.log('Received: ', value),
  error: error => console.error('Error: ', error),
  complete: () => console.log('Completed')
};

observable.subscribe(observer);

在上述代码中,Observable通过next方法发出值,complete方法表示不再发出值。Observer通过subscribe方法订阅Observable,并处理接收到的值、错误和完成事件。

操作符(Operators)

RxJS提供了丰富的操作符,用于对Observable进行转换、过滤等操作。例如,map操作符用于将Observable发出的值进行转换:

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

const observable = new Observable(observer => {
  observer.next(1);
  observer.next(2);
  observer.complete();
});

observable.pipe(
  map(value => value * 2)
).subscribe(result => console.log(result));

在上述代码中,map操作符将Observable发出的每个值乘以2,然后订阅处理转换后的值。

在Angular中的应用

在Angular中,HttpClient返回的是Observable对象,因此可以方便地使用RxJS操作符进行处理。例如:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app - my - component',
  templateUrl: './my - component.component.html'
})
export class MyComponent {
  data: any;

  constructor(private http: HttpClient) { }

  ngOnInit() {
    this.http.get('https://example.com/api/data')
    .pipe(
      map(response => response.filter(item => item.active))
    )
    .subscribe(result => {
      this.data = result;
    });
  }
}

在上述代码中,通过map操作符对HTTP请求返回的数据进行过滤,只保留activetrue的项,然后订阅处理过滤后的数据。

总结

Angular的核心概念包括组件、模块、服务、依赖注入、指令、管道、路由和响应式编程等。这些概念相互协作,使得开发者能够构建出高效、可维护且功能强大的前端应用。通过深入理解和熟练运用这些核心概念,开发者可以充分发挥Angular的优势,打造出优秀的用户体验。在实际开发中,需要根据项目的需求和规模,合理地运用这些概念,以实现最佳的代码结构和性能。同时,不断学习和关注Angular的更新和发展,能够更好地适应新的开发需求和技术趋势。