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

了解Angular开启前端新旅程

2021-12-181.7k 阅读

什么是Angular

Angular 是一款由 Google 维护的开源 JavaScript 框架,用于构建高效、复杂且功能丰富的单页应用程序(SPA)。它基于 TypeScript 构建,结合了诸多现代前端开发理念,为开发者提供了一套全面的解决方案,涵盖组件化开发、模板驱动、依赖注入、路由管理等前端开发的各个关键方面。

组件化架构

Angular 的核心架构是基于组件的。组件是 Angular 应用程序的基本构建块,每个组件都包含一个视图(HTML 模板)、一个逻辑类(TypeScript 代码)以及相关的样式。这种清晰的分离使得代码易于维护和理解。

例如,我们创建一个简单的 Angular 组件 app - hello - world

  1. 创建组件
    ng generate component hello - world
    
  2. 组件逻辑类(hello - world.component.ts
    import { Component } from '@angular/core';
    
    @Component({
        selector: 'app - hello - world',
        templateUrl: './hello - world.component.html',
        styleUrls: ['./hello - world.component.css']
    })
    export class HelloWorldComponent {
        message: string = 'Hello, Angular!';
    }
    
  3. 组件视图(hello - world.component.html
    <div>
        <p>{{message}}</p>
    </div>
    
  4. 组件样式(hello - world.component.css
    div {
        background - color: lightblue;
        padding: 20px;
    }
    
  5. 在父组件中使用: 在 app.component.html 中添加 <app - hello - world></app - hello - world>,就可以在应用的主视图中展示这个组件。

模板驱动

Angular 的模板是一种强大的机制,它允许开发者以声明式的方式描述应用的视图。模板不仅可以绑定组件的数据,还能处理用户输入、事件绑定等操作。

  1. 数据绑定

    • 插值:如上述 hello - world.component.html 中的 {{message}},这是最基本的数据绑定方式,用于将组件中的属性值插入到模板中。
    • 属性绑定:例如,我们有一个图片组件,想要动态设置图片的 src 属性:
      <img [src]="imageUrl" alt="示例图片">
      
      在组件逻辑类中定义 imageUrl: string = 'https://example.com/image.jpg';
    • 双向绑定:常用于表单元素,如 input 框。假设我们有一个用于输入用户名的 input
      <input [(ngModel)]="username" type="text">
      <p>你输入的用户名是: {{username}}</p>
      
      在组件逻辑类中定义 username: string = '';,这里的 ngModel 指令来自 @angular/forms 模块,实现了输入框的值与组件属性的双向同步。
  2. 事件绑定: 假设我们有一个按钮,点击按钮时要触发一个方法来改变组件的状态。

    <button (click)="onButtonClick()">点击我</button>
    

    在组件逻辑类中定义:

    onButtonClick() {
        this.message = '按钮被点击了!';
    }
    

Angular 模块

Angular 模块(NgModule)是对相关的组件、指令、管道以及服务进行分组的机制。它有助于组织和管理大型应用程序的代码结构。

根模块

每个 Angular 应用都有一个根模块,通常命名为 AppModule。它是应用程序的启动点,负责引导应用并定义应用的顶级组件。

以下是 AppModule 的基本结构:

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

@NgModule({
    declarations: [
        AppComponent,
        HelloWorldComponent
    ],
    imports: [
        BrowserModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule {}
  • declarations:声明模块中包含的组件、指令和管道。
  • imports:导入该模块所需的其他模块,如 BrowserModule 提供了在浏览器环境中运行所需的基础功能。
  • providers:注册应用程序中使用的服务,这些服务可以在整个应用中共享。
  • bootstrap:指定应用的根组件,即应用启动时首先加载的组件。

特性模块

随着应用程序的增长,我们可以创建特性模块来将相关功能进行分离。例如,我们有一个电子商务应用,可能会有用户模块、产品模块、订单模块等。

以用户模块为例:

  1. 创建用户模块
    ng generate module users
    
  2. 用户模块(users.module.ts
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { UserListComponent } from './user - list.component';
    import { UserDetailComponent } from './user - detail.component';
    
    @NgModule({
        declarations: [
            UserListComponent,
            UserDetailComponent
        ],
        imports: [
            CommonModule
        ],
        exports: [
            UserListComponent,
            UserDetailComponent
        ]
    })
    export class UsersModule {}
    

这里的 CommonModule 提供了一些常用的指令和管道,如 ngIfngFor 等。exports 数组用于指定哪些组件、指令或管道可以被其他模块使用。

  1. 在根模块中导入用户模块: 在 AppModuleimports 数组中添加 UsersModule
import { UsersModule } from './users.module';

@NgModule({
    imports: [
        BrowserModule,
        UsersModule
    ]
})
export class AppModule {}

依赖注入

依赖注入(Dependency Injection,简称 DI)是 Angular 中的一项重要设计模式,它使得组件之间的依赖关系更加清晰和易于管理。通过依赖注入,组件不需要自己创建所依赖的对象,而是由外部容器提供。

服务与依赖注入

假设我们有一个服务 LoggerService,用于记录日志。

  1. 创建服务

    ng generate service logger
    
  2. 服务实现(logger.service.ts

    import { Injectable } from '@angular/core';
    
    @Injectable({
        providedIn: 'root'
    })
    export class LoggerService {
        log(message: string) {
            console.log(`[${new Date().toISOString()}] ${message}`);
        }
    }
    

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

  3. 在组件中使用服务: 在 HelloWorldComponent 中注入 LoggerService

import { Component } from '@angular/core';
import { LoggerService } from './logger.service';

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

    constructor(private logger: LoggerService) {}

    onButtonClick() {
        this.message = '按钮被点击了!';
        this.logger.log('按钮点击事件触发');
    }
}

在组件的构造函数中,通过 private logger: LoggerService 声明了对 LoggerService 的依赖,Angular 会自动创建 LoggerService 的实例并注入到组件中。

依赖注入的优势

  1. 解耦组件:组件不需要知道依赖对象的具体创建过程,只关注使用,使得组件更加独立和可测试。例如,我们可以轻松地替换 LoggerService 的实现,而不影响使用它的组件。
  2. 提高代码的可维护性:依赖关系集中管理,便于理解和修改。如果需要在整个应用中更改日志记录的方式,只需要修改 LoggerService 即可。

Angular 路由

路由是单页应用程序中实现页面导航和视图切换的关键机制。Angular 提供了强大的路由模块 @angular/router,可以方便地管理应用的路由配置。

基本路由配置

假设我们有一个简单的应用,包含首页和关于页面。

  1. 创建组件
    ng generate component home
    ng generate component about
    
  2. 配置路由(app - routing.module.ts
    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 {}
    

这里定义了两条路由,path: '' 表示应用的根路径,对应 HomeComponentpath: 'about' 对应 AboutComponentRouterModule.forRoot(routes) 用于配置应用的根路由。

  1. 在模板中添加导航链接: 在 app.component.html 中添加:
<ul>
    <li><a routerLink="/">首页</a></li>
    <li><a routerLink="/about">关于</a></li>
</ul>
<router - outlet></router - outlet>

routerLink 指令用于创建路由链接,router - outlet 是路由视图的占位符,匹配的组件将在此处渲染。

路由参数

有时我们需要在路由中传递参数,比如在用户详情页面,根据用户 ID 显示不同用户的信息。

  1. 配置带参数的路由: 在 app - routing.module.ts 中添加:
import { UserDetailComponent } from './user - detail.component';

const routes: Routes = [
    // 其他路由...
    { path: 'user/:id', component: UserDetailComponent }
];

这里的 :id 是参数占位符。

  1. 在组件中获取参数: 在 UserDetailComponent 中:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
    selector: 'app - user - detail',
    templateUrl: './user - detail.component.html',
    styleUrls: ['./user - detail.component.css']
})
export class UserDetailComponent implements OnInit {
    userId: string;

    constructor(private route: ActivatedRoute) {}

    ngOnInit() {
        this.userId = this.route.snapshot.paramMap.get('id');
    }
}

通过 ActivatedRoutesnapshot.paramMap.get('id') 可以获取路由参数。

Angular 表单

在前端开发中,表单是收集用户输入的重要方式。Angular 提供了两种处理表单的方式:模板驱动表单和响应式表单。

模板驱动表单

模板驱动表单基于指令和数据绑定,适用于简单的表单场景。

  1. 创建表单: 在组件的模板中添加:
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
    <div>
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username" [(ngModel)]="username" required>
    </div>
    <div>
        <label for="email">邮箱:</label>
        <input type="email" id="email" name="email" [(ngModel)]="email" required>
    </div>
    <button type="submit">提交</button>
</form>

这里使用了 ngModel 指令实现双向绑定,required 是 HTML5 的验证属性。#userForm="ngForm" 创建了一个模板引用变量 userForm,代表整个表单。

  1. 在组件逻辑类中处理表单提交
import { Component } from '@angular/core';

@Component({
    selector: 'app - user - form',
    templateUrl: './user - form.component.html',
    styleUrls: ['./user - form.component.css']
})
export class UserFormComponent {
    username: string = '';
    email: string = '';

    onSubmit(form) {
        if (form.valid) {
            console.log('表单提交成功', { username: this.username, email: this.email });
        } else {
            console.log('表单验证失败');
        }
    }
}

响应式表单

响应式表单提供了更灵活和强大的方式来处理表单,尤其适用于复杂的表单场景。

  1. 导入响应式表单模块: 在 app.module.ts 中导入 ReactiveFormsModule
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
    imports: [
        // 其他模块...
        ReactiveFormsModule
    ]
})
export class AppModule {}
  1. 创建响应式表单: 在组件逻辑类中:
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
    selector: 'app - reactive - user - form',
    templateUrl: './reactive - user - form.component.html',
    styleUrls: ['./reactive - user - form.component.css']
})
export class ReactiveUserFormComponent {
    userForm: FormGroup;

    constructor() {
        this.userForm = new FormGroup({
            username: new FormControl('', Validators.required),
            email: new FormControl('', [Validators.required, Validators.email])
        });
    }

    onSubmit() {
        if (this.userForm.valid) {
            console.log('表单提交成功', this.userForm.value);
        } else {
            console.log('表单验证失败');
        }
    }
}
  1. 在模板中使用响应式表单
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
    <div>
        <label for="username">用户名:</label>
        <input type="text" id="username" formControlName="username">
        <div *ngIf="userForm.get('username').hasError('required') && (userForm.get('username').touched || userForm.get('username').dirty)">用户名是必填项</div>
    </div>
    <div>
        <label for="email">邮箱:</label>
        <input type="email" id="email" formControlName="email">
        <div *ngIf="userForm.get('email').hasError('required') && (userForm.get('email').touched || userForm.get('email').dirty)">邮箱是必填项</div>
        <div *ngIf="userForm.get('email').hasError('email') && (userForm.get('email').touched || userForm.get('email').dirty)">邮箱格式不正确</div>
    </div>
    <button type="submit">提交</button>
</form>

响应式表单通过 FormGroupFormControl 来管理表单状态和验证,并且可以更细粒度地控制表单的行为。

与后端交互

在实际应用中,前端需要与后端服务器进行数据交互。Angular 提供了 HttpClient 模块来处理 HTTP 请求。

使用 HttpClient

  1. 导入 HttpClient 模块: 在 app.module.ts 中导入 HttpClientModule
import { HttpClientModule } from '@angular/common/http';

@NgModule({
    imports: [
        // 其他模块...
        HttpClientModule
    ]
})
export class AppModule {}
  1. 发送 GET 请求: 假设我们有一个后端 API 用于获取用户列表,在服务中:
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) {}

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

这里 http.get 方法返回一个 Observable,我们可以在组件中订阅它来获取数据:

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

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

    constructor(private userService: UserService) {}

    ngOnInit() {
        this.userService.getUsers().subscribe(data => {
            this.users = data;
        });
    }
}
  1. 发送 POST 请求: 如果要创建新用户,在服务中:
createUser(user: any): Observable<any> {
    return this.http.post('https://example.com/api/users', user);
}

在组件中:

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

@Component({
    selector: 'app - create - user',
    templateUrl: './create - user.component.html',
    styleUrls: ['./create - user.component.css']
})
export class CreateUserComponent {
    newUser = { name: '', email: '' };

    constructor(private userService: UserService) {}

    onSubmit() {
        this.userService.createUser(this.newUser).subscribe(response => {
            console.log('用户创建成功', response);
        });
    }
}

错误处理

在处理 HTTP 请求时,错误处理是必不可少的。我们可以通过 catchError 操作符来处理请求过程中的错误。

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

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

    getUsers(): Observable<any> {
        return this.http.get('https://example.com/api/users').pipe(
            catchError(error => {
                console.error('获取用户列表失败', error);
                return Observable.throw(error);
            })
        );
    }
}

这样,当请求出现错误时,会在控制台打印错误信息,并将错误继续抛出给订阅者处理。

性能优化

随着应用程序的规模和复杂度增加,性能优化变得至关重要。Angular 提供了多种机制来提升应用的性能。

变更检测

Angular 使用变更检测机制来检查数据变化并更新视图。默认情况下,Angular 使用 Default 策略,即每当事件发生(如用户交互、HTTP 请求完成等)时,会检查整个组件树。

  1. OnPush 策略: 对于一些组件,如果其输入属性或可观察对象没有变化,我们可以使用 OnPush 变更检测策略来提高性能。 在组件装饰器中设置:
import { Component } from '@angular/core';

@Component({
    selector: 'app - my - component',
    templateUrl: './my - component.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {}

当使用 OnPush 策略时,只有当输入属性引用发生变化、组件接收到事件(如点击事件)或可观察对象发出新值时,才会触发变更检测,从而减少不必要的检查。

懒加载

懒加载是一种延迟加载模块的技术,只有在需要时才加载相应的模块。这对于大型应用来说,可以显著提高初始加载速度。

  1. 配置懒加载路由: 在 app - routing.module.ts 中:
const routes: Routes = [
    {
        path: 'products',
        loadChildren: () => import('./products.module').then(m => m.ProductsModule)
    }
];

这里使用 loadChildren 来指定延迟加载 ProductsModule。当用户访问 /products 路由时,才会加载 products.module.ts 文件及其相关的组件和代码。

代码压缩与摇树优化

在构建 Angular 应用时,Angular CLI 会自动进行代码压缩和摇树优化。摇树优化(Tree - shaking)会去除未使用的代码,从而减小打包文件的大小。

ng build --prod

--prod 标志会启用生产环境的优化配置,包括压缩代码、摇树优化等,生成的文件适用于部署到生产环境。

通过深入了解和运用上述 Angular 的各个方面,开发者能够构建出高效、可维护且功能丰富的前端应用程序,开启精彩的前端开发新旅程。无论是小型项目还是大型企业级应用,Angular 都提供了全面且强大的工具和技术,满足不同场景的需求。在实际开发过程中,不断实践和优化,结合项目的具体要求,充分发挥 Angular 的优势,将为开发者带来良好的开发体验和优秀的应用成果。