ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Angular入门实战:从核心概念到Todo应用完整开发指南

Angular入门实战:从核心概念到Todo应用完整开发指南 相信很多前端开发者在做业务项目时都有过类似的经历初期用原生 JavaScript 或早期 jQuery 方案写起来确实很快可一旦页面变多、状态变复杂代码就开始失控。DOM 更新散落在各种事件回调里数据同步全靠团队约定调试一个问题往往要翻遍整个文件。Angular 给我的直观感受是它把组件化、依赖注入、路由、表单、HTTP 客户端这些能力作为框架的一部分完整提供给开发者并用 TypeScript 约束代码结构让团队在写代码阶段就能避开很多低级错误。本文会从 Angular 的定位讲起带大家完成 Angular 安装及环境配置逐步拆解组件、模板、服务、路由等核心知识点最后用一个完整的待办事项应用把整个流程串起来并补充常见问题、工程化建议和面试自查要点。无论你是刚接触 Angular 的新手还是在 Angular 与 Vue 之间做技术选型的开发者都可以参考这篇文章。1. Angular 是什么它解决什么问题1.1 前端开发中的 AngularAngular 是一个基于 TypeScript 的开源前端框架由 Google 开发和维护。它最早源于 2010 年前后的 AngularJS而当前大家说的 Angular 通常指从 2.0 版本开始完全重写后的新一代框架与 AngularJS 并不是同一个技术体系。Angular 的核心设计理念是“框架即平台”。它不只是解决页面渲染问题而是把一套完整的前端工程方案内置进来组件化开发把 UI 拆成独立、可复用的组件。模板语法在 HTML 中直接书写数据绑定和指令。依赖注入让服务、组件之间的依赖关系由框架统一管理。路由内置路由模块支持懒加载、路由守卫等能力。表单提供模板驱动表单和响应式表单两套方案。HTTP 客户端对异步请求做了较好的封装默认基于 RxJS。这些能力不需要开发者自己组装第三方库Angular 官方会统一迭代和升级。对于企业级中后台项目、大型多人协作项目来说这种“统一约束”能明显降低维护成本。1.2 Angular 与 Vue、React 的定位对比Angular 经常被拿来和 Vue、React 放在一起比较。三者都能做组件化前端开发但设计思路差异比较大。对比维度AngularVueReact语言默认 TypeScriptJavaScript / TypeScript 均可JavaScript / TypeScript 均可组件结构组件类 模板 样式单文件组件 SFCJSX 组件数据绑定单向绑定 双向绑定单向绑定 v-model单向数据流依赖注入框架内置能力完整需要额外方案需要额外方案路由官方内置官方 vue-routerReact Router表单两套方案功能强轻量自带社区方案居多学习曲线较陡峭平缓依赖生态积累简单来说Vue 的上手成本更低React 的社区生态更丰富Angular 则在大型项目规范性和“开箱即用”方面更有优势。如果你所在团队做的是多人在线协作的中后台系统希望框架能约束开发规范、减少技术选型争论Angular 是很适合的选择。1.3 学习 Angular 前需要具备的基础学习 Angular 之前建议先掌握HTML / CSS 基础JavaScript 核心语法尤其是 ES6 的类、模块、箭头函数、解构赋值TypeScript 基础比如类型、接口、装饰器npm / Node.js 基础的包管理操作如果你已经有 Vue 或 React 经验理解 Angular 的组件、模板、路由会非常快重点要适应的是依赖注入和响应式编程风格。2. Angular 安装及环境配置2.1 工具链与版本建议Angular 的版本迭代速度比较快不同版本之间会有一些 API 差异。本文示例以当前主流版本的默认结构为准重点演示配置思路实际操作时请以你自己的环境为准。环境准备清单Node.js建议安装 LTS 版本。Angular CLI 对 Node.js 版本有要求过旧版本可能无法运行最新 CLI。安装完成后可以用node -v和npm -v确认。Angular CLI全局安装后通过ng命令创建项目、生成代码。IDEVS Code 是 Angular 开发中比较常用的编辑器配合 Angular Language Service 插件可以获得更好的代码提示。浏览器Chrome 或 Edge 均可主要用于调试和查看运行效果。2.2 安装 CLI 并创建项目Angular CLI 的安装命令npm install -g angular/cli安装完成后查看版本验证是否安装成功ng version如果看到类似下面的信息说明 CLI 已经就绪Angular CLI: 18.x.x Node: 20.x.x Package Manager: npm 10.x.x创建项目的命令ng new angular-todo-demo执行之后CLI 会询问是否添加 Angular routing这里选择 Yes样式格式可以根据习惯选择 CSS、SCSS、Sass 或 Less本文示例选择 CSS。等待依赖安装完成后进入项目目录启动开发服务器cd angular-todo-demo ng serve --open默认情况下开发服务器运行在http://localhost:4200。启动成功后浏览器会自动打开 Angular 的初始欢迎页面。2.3 项目目录结构与启动流程用最新版 Angular CLI 创建的项目默认是 standalone 组件结构不会生成传统的app.module.ts。核心目录如下angular-todo-demo/ ├── src/ │ ├── app/ │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ └── app.routes.ts │ ├── main.ts │ ├── index.html │ └── styles.css ├── angular.json ├── package.json └── tsconfig.json几个关键文件的作用main.ts应用入口使用bootstrapApplication启动根组件。app.component.ts根组件。app.routes.ts路由配置。app.config.ts应用级配置比如路由、HTTP 客户端等 provider。angular.jsonAngular 项目的构建和工程配置。如果新项目仍然生成了app.module.ts说明你使用的是较旧的 Angular 版本或选择了传统 NgModule 模式。两种模式核心思想一致只是组织方式不同后面的实战示例会以新结构为主并在常见问题中说明差异。3. Angular 核心概念拆解3.1 组件UI 的基本单位Angular 应用中页面是由组件树组成的。每个组件负责自己这块视图的展示和交互逻辑。看一个最简单的组件// 文件路径src/app/demo/demo.component.ts import { Component } from angular/core; Component({ selector: app-demo, standalone: true, template: phello angular/p, }) export class DemoComponent {}selector组件在 HTML 中的标签名比如app-demo/app-demo。standalone: true表示这是一个独立组件可以直接被其他组件引用不需要挂在 NgModule 中。template组件视图模板也可以改用templateUrl指向外部 HTML 文件。类中的属性和方法可以在模板中直接使用。在另一个组件中使用这个组件import { Component } from angular/core; import { DemoComponent } from ../demo/demo.component; Component({ selector: app-home, standalone: true, imports: [DemoComponent], template: app-demo/app-demo, }) export class HomeComponent {}这里需要把DemoComponent加入imports数组模板中才能识别对应的标签。3.2 模板与数据绑定Angular 模板支持多种数据绑定方式理解这些绑定规则是掌握组件开发的关键。插值表达式p{{ title }}/p属性绑定把组件属性绑定到 HTML 属性或组件属性img [src]imgUrl / input [disabled]isDisabled /事件绑定监听 DOM 事件并调用组件方法button (click)handleClick()点击/button双向绑定通过ngModel实现表单输入与组件属性的双向同步input [(ngModel)]name / p你输入的是{{ name }}/p使用ngModel时需要先在组件中引入FormsModuleimport { Component } from angular/core; import { FormsModule } from angular/forms; Component({ selector: app-input-demo, standalone: true, imports: [FormsModule], template: input [(ngModel)]name / p你输入的是{{ name }}/p , }) export class InputDemoComponent { name ; }这里需要特别注意[(ngModel)]并不是“双向绑定”这个概念的魔术它是“属性绑定[ngModel] 事件绑定(ngModelChange)”的语法糖。理解这一点有助于你排查表单绑定不更新的问题。3.3 指令系统指令用来操作 DOM。Angular 中最常用的是结构型指令和属性型指令。结构型指令会改变 DOM 结构常见的有*ngIf和*ngForp *ngIfisVisible条件渲染/p ul li *ngForlet item of items{{ item }}/li /ul属性型指令用来修改元素的外观或行为常见的有ngClass和ngStylediv [ngClass]{ active: isActive, disabled: isDisabled }样式绑定/div div [ngStyle]{ color: textColor }内联样式/div在 standalone 组件中使用这些内置指令时需要从angular/common引入对应的指令类import { Component } from angular/core; import { NgFor, NgIf } from angular/common; Component({ selector: app-list-demo, standalone: true, imports: [NgFor, NgIf], template: ul li *ngForlet item of items{{ item }}/li /ul , }) export class ListDemoComponent { items [Angular, TypeScript, RxJS]; }除了内置指令你也可以通过Directive装饰器创建自定义指令用于封装一些通用的 DOM 操作逻辑。自定义指令在实际项目中经常用来做权限控制、埋点上报、拖拽等能力。3.4 服务与依赖注入服务是用来封装业务逻辑和数据访问的类。Angular 通过依赖注入Dependency Injection简称 DI把服务实例提供给需要的组件开发者不需要手动管理实例的创建和销毁。先定义一个服务// 文件路径src/app/services/user.service.ts import { Injectable } from angular/core; Injectable({ providedIn: root }) export class UserService { private users [张三, 李四, 王五]; getUsers(): string[] { return this.users; } addUser(name: string): void { this.users.push(name); } }在组件中注入这个服务import { Component, OnInit } from angular/core; import { UserService } from ../services/user.service; Component({ selector: app-user-list, standalone: true, template: ul li *ngForlet user of users{{ user }}/li /ul , }) export class UserListComponent implements OnInit { users: string[] []; constructor(private userService: UserService) {} ngOnInit(): void { this.users this.userService.getUsers(); } }这里有一点值得注意providedIn: root表示该服务在根注入器中提供整个应用共享同一个实例。构造函数中的private userService: UserService是 Angular 依赖注入的标准写法框架会根据类型自动找到并注入对应的服务实例。3.5 路由与 HTTP 请求Angular 路由负责 URL 与组件之间的映射。在 standalone 结构下路由配置写在app.routes.ts中// 文件路径src/app/app.routes.ts import { Routes } from angular/router; import { HomeComponent } from ./home/home.component; import { UserListComponent } from ./user-list/user-list.component; export const routes: Routes [ { path: , component: HomeComponent }, { path: users, component: UserListComponent }, ];然后在组件模板中使用路由出口和路由链接nav a routerLink/首页/a a routerLink/users用户列表/a /nav router-outlet/router-outletHTTP 请求通常由服务层负责。Angular 通过HttpClient发起请求返回的是 RxJS 的Observable。import { Injectable } from angular/core; import { HttpClient } from angular/common/http; import { Observable } from rxjs; Injectable({ providedIn: root }) export class ApiService { private apiUrl /api/users; constructor(private http: HttpClient) {} getUsers(): Observablestring[] { return this.http.getstring[](this.apiUrl); } }使用 HttpClient 前需要在app.config.ts的 providers 中注册provideHttpClient()import { ApplicationConfig } from angular/core; import { provideRouter } from angular/router; import { provideHttpClient } from angular/common/http; import { routes } from ./app.routes; export const appConfig: ApplicationConfig { providers: [ provideRouter(routes), provideHttpClient(), ], };这样服务中才能正常注入HttpClient。4. 完整实战从零搭建一个待办事项应用4.1 需求分析与模块划分下面用 Angular 实现一个常见的 Todo 应用功能包括展示待办事项列表添加新待办事项标记待办事项为已完成删除待办事项功能不大但能覆盖组件、模板、数据绑定、服务、路由、依赖注入这几个核心知识点。项目结构规划src/app/ ├── app.component.ts ├── app.component.html ├── app.config.ts ├── app.routes.ts ├── models/ │ └── todo.model.ts ├── services/ │ └── todo.service.ts └── components/ └── todo-list/ ├── todo-list.component.ts ├── todo-list.component.html └── todo-list.component.css4.2 创建数据模型与服务先定义一个 Todo 数据接口。// 文件路径src/app/models/todo.model.ts export interface Todo { id: number; title: string; completed: boolean; createdAt: Date; }然后创建 TodoService用数组模拟数据存储。后续如果需要接入后端接口只需要在服务中把内存操作替换成 HttpClient 请求即可。// 文件路径src/app/services/todo.service.ts import { Injectable } from angular/core; import { Todo } from ../models/todo.model; Injectable({ providedIn: root }) export class TodoService { private todos: Todo[] [ { id: 1, title: 学习 Angular 组件, completed: true, createdAt: new Date() }, { id: 2, title: 完成路由配置, completed: false, createdAt: new Date() }, ]; private nextId 3; getTodos(): Todo[] { return [...this.todos]; } addTodo(title: string): void { this.todos.push({ id: this.nextId, title, completed: false, createdAt: new Date(), }); } toggleTodo(id: number): void { const todo this.todos.find((item) item.id id); if (todo) { todo.completed !todo.completed; } } removeTodo(id: number): void { this.todos this.todos.filter((todo) todo.id ! id); } }getTodos()返回一个新数组避免组件直接修改服务内部状态。这是一个很小的细节但在真实项目中能减少很多状态同步问题。4.3 编写 Todo 列表组件生成组件ng generate component components/todo-list组件的 TypeScript 逻辑如下// 文件路径src/app/components/todo-list/todo-list.component.ts import { Component, OnInit } from angular/core; import { NgClass, NgFor, NgIf } from angular/common; import { FormsModule } from angular/forms; import { TodoService } from ../../services/todo.service; import { Todo } from ../../models/todo.model; Component({ selector: app-todo-list, standalone: true, imports: [NgClass, NgFor, NgIf, FormsModule], templateUrl: ./todo-list.component.html, styleUrl: ./todo-list.component.css, }) export class TodoListComponent implements OnInit { todos: Todo[] []; newTitle ; constructor(private todoService: TodoService) {} ngOnInit(): void { this.todos this.todoService.getTodos(); } addTodo(): void { const title this.newTitle.trim(); if (!title) { return; } this.todoService.addTodo(title); this.todos this.todoService.getTodos(); this.newTitle ; } toggleTodo(id: number): void { this.todoService.toggleTodo(id); this.todos this.todoService.getTodos(); } removeTodo(id: number): void { this.todoService.removeTodo(id); this.todos this.todoService.getTodos(); } }模板如下!-- 文件路径src/app/components/todo-list/todo-list.component.html -- div classtodo-container h2我的待办/h2 div classtodo-input input typetext [(ngModel)]newTitle placeholder输入新待办事项 (keyup.enter)addTodo() / button (click)addTodo()添加/button /div ul classtodo-list li *ngForlet todo of todos [class.completed]todo.completed input typecheckbox [checked]todo.completed (change)toggleTodo(todo.id) / span{{ todo.title }}/span button (click)removeTodo(todo.id)删除/button /li /ul p *ngIftodos.length 0暂无待办事项/p /div简单样式/* 文件路径src/app/components/todo-list/todo-list.component.css */ .todo-container { max-width: 480px; margin: 40px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; } .todo-input { display: flex; gap: 8px; margin-bottom: 16px; } .todo-input input { flex: 1; padding: 8px; font-size: 14px; } .todo-list { list-style: none; padding: 0; } .todo-list li { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid #eee; } .todo-list li.completed span { text-decoration: line-through; color: #999; }这里用到了两个典型绑定[class.completed]todo.completed根据todo.completed的值动态添加或移除completed类。[(ngModel)]newTitle实现输入框与组件属性的双向同步。4.4 配置路由并运行项目接下来把 Todo 组件挂到路由上替换默认首页。// 文件路径src/app/app.routes.ts import { Routes } from angular/router; import { TodoListComponent } from ./components/todo-list/todo-list.component; export const routes: Routes [ { path: , redirectTo: /todos, pathMatch: full }, { path: todos, title: 待办事项, component: TodoListComponent }, { path: **, redirectTo: /todos }, ];根组件模板修改为只保留路由出口!-- 文件路径src/app/app.component.html -- main router-outlet/router-outlet /main默认生成的app.config.ts已经包含路由 provider不需要额外改动// 文件路径src/app/app.config.ts import { ApplicationConfig, provideZoneChangeDetection } from angular/core; import { provideRouter } from angular/router; import { routes } from ./app.routes; export const appConfig: ApplicationConfig { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), ], };启动项目ng serve --open浏览器访问http://localhost:4200/todos就可以看到待办列表。输入新内容后点击“添加”或按回车待办会立即出现在列表中点击复选框可以切换完成状态点击“删除”可以移除对应条目。5. 常见问题与排查思路5.1ng命令找不到问题现象常见原因解决思路执行ng version提示ng: command not foundAngular CLI 未安装或 npm 全局目录不在 PATH 中重新执行npm install -g angular/cli并检查 npm 全局路径如果不想全局安装可以直接使用npx ng调用当前项目中的 CLI。5.2 端口被占用导致启动失败问题现象常见原因解决思路启动时提示Port 4200 is already in use或EADDRINUSE4200 端口已被其他进程占用使用ng serve --port 4201更换端口或关闭占用端口的进程5.3ngModel无法识别问题现象常见原因解决思路控制台报错Cant bind to ngModel since it isnt a known property of input组件没有引入FormsModule在 standalone 组件的imports中加入FormsModule在 NgModule 模式项目中在对应模块中导入FormsModule5.4 HttpClient 无法注入问题现象常见原因解决思路报错NullInjectorError: No provider for HttpClient没有注册 HttpClient 提供者在app.config.ts的providers中加入provideHttpClient()旧版本项目中导入HttpClientModule5.5 路由跳转后页面空白问题现象常见原因解决思路点击路由链接后 URL 变了但页面空白根组件模板中缺少router-outlet或路由路径配置错误检查根组件模板是否包含路由出口检查app.routes.ts中的 path 定义5.6 standalone 与 NgModule 结构差异新版本 Angular CLI 默认生成 standalone 组件没有app.module.ts。如果你在网上搜索资料时看到老项目结构比如app.module.ts中的declarations、imports、providers不要直接照搬。核心思路是对应的但写法不同老结构imports: [CommonModule, FormsModule]放在NgModule中。新结构组件级 standaloneimports直接写在Component中。遇到版本差异时优先参考你本地ng version对应的官方文档。6. Angular 工程化最佳实践6.1 目录与命名规范项目目录建议按业务模块划分而不是按类型堆在一起。比如src/app/ ├── core/ # 核心服务、单例服务 ├── shared/ # 通用组件、管道、指令 ├── features/ # 业务模块 │ ├── todo/ # 待办业务 │ ├── user/ # 用户业务 │ └── ...组件文件的命名使用xxx.component.ts服务使用xxx.service.ts接口或模型使用xxx.model.ts。组件类名使用大驼峰变量和函数使用小驼峰常量使用全大写加下划线。命名规范看起来是小问题但在团队协作中能显著提高代码的可读性。6.2 组件通信方式怎么选Angular 中组件通信的常见方式有四种Input/Output父组件向子组件传数据、子组件向父组件发事件适合相邻层级的组件通信。服务多个组件共享同一个服务实例适合非相邻组件、跨模块通信。路由参数页面跳转时传递参数。状态管理库如 NgRx适合大型项目的复杂状态管理但引入成本较高。选择原则是“能就近通信就不要引入全局状态”。一上来就使用 NgRx对于中小项目反而会增加理解成本。6.3 变更检测与性能优化Angular 默认使用 Zone.js 做变更检测组件数据变化后会自动更新视图。在数据量较大的列表中有几点优化经验值得关注*ngFor尽量使用trackBy避免整个列表重新渲染。li *ngForlet todo of todos; trackBy: trackByIdtrackById(index: number, todo: Todo): number { return todo.id; }对于不频繁变化的大组件可以把变更检测策略改为ChangeDetectionStrategy.OnPush。使用async管道订阅Observable避免在组件中手动管理订阅生命周期。6.4 工程安全、异常处理与面试自查Angular 默认会处理模板中的 XSS 问题但动态拼接 HTML 时仍需谨慎。尽量避免使用innerHTML直接插入不可信内容如果确实需要要经过服务端转义和白名单校验。在异常处理方面HTTP 请求要区分成功回调和错误回调this.apiService.getUsers().subscribe({ next: (users) console.log(users), error: (err) console.error(请求失败, err), });在组件销毁时应该取消订阅避免内存泄漏。使用async管道可以自动处理大部分场景手动订阅的场景则需要在ngOnDestroy中清理。如果你正在准备 Angular 面试可以按下面这些问题自查Angular 数据绑定有几种方式组件间通信有哪些方式什么是依赖注入providedIn: root的作用是什么Angular 生命周期钩子有哪些执行顺序是什么*ngIf和[hidden]的区别是什么纯管道和不纯管道的区别是什么路由守卫有哪几种分别用在什么场景Angular 与 Vue、React 的核心区别是什么准备面试时建议不要只背概念而是亲手用 Angular 写一个包含组件、路由、服务、表单的小项目。真正写过一遍之后对这些问题的理解会比单纯背资料牢固很多。7. 总结与学习路线通过这篇文章你完成了 Angular 的基础入门并在本地搭建并运行了一个完整的待办事项应用。核心收获包括理解 Angular 为什么适合大型项目开发。掌握 Angular 安装及环境配置的基本流程。熟悉组件、模板、数据绑定、指令、服务、依赖注入、路由、HTTP 客户端这些核心概念。动手实现了包含新增、删除、状态切换、路由跳转的 Todo 应用。了解常见报错和排查思路。下一步可以继续学习这些方向RxJS理解Observable、Subject、操作符Angular 的很多能力都建立在 RxJS 之上。响应式表单掌握表单校验、动态表单、自定义验证器。Angular 路由进阶了解懒加载、路由守卫、预加载策略。NgRx学习大型应用中的状态管理方案。组件库熟悉 Angular Material 或 PrimeNG提升中后台开发效率。在实际项目中优先关注的地方是依赖版本一致性、组件职责边界、状态变更的可追踪性以及异步任务的资源释放。框架本身提供了完整的基础设施真正决定项目质量的是你对组件化和数据流的理解程度。建议你从最简单的组件开始逐步添加路由、服务、表单和 HTTP 请求把一个静态页面改造成真正可交互的应用。遇到问题先看控制台报错再结合类型判断做排查大多数坑都能自己找到原因。
返回列表