Angular 学生列表组件
目标
创建典型的 Angular 组件:展示学生列表,支持搜索过滤和添加学生——涵盖 Component / Service / 双向绑定 / *ngFor / 管道。
完整代码
1. 创建项目并生成文件
ng new student-demo --routing --style scss
cd student-demo
ng g s services/student
2. src/app/services/student.service.ts
import { Injectable } from '@angular/core';
export interface Student {
id: number;
name: string;
major: string;
grade: number;
}
@Injectable({ providedIn: 'root' })
export class StudentService {
private students: Student[] = [
{ id: 1, name: '张三', major: '计算机科学', grade: 89 },
{ id: 2, name: '李四', major: '软件工程', grade: 92 },
{ id: 3, name: '王五', major: '人工智能', grade: 78 },
];
private nextId = 4;
getStudents(): Student[] {
return [...this.students];
}
addStudent(student: Omit<Student, 'id'>): void {
this.students.push({ ...student, id: this.nextId++ });
}
deleteStudent(id: number): void {
this.students = this.students.filter(s => s.id !== id);
}
}
3. src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { StudentService, Student } from './services/student.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
students: Student[] = [];
searchTerm = '';
newStudent = { name: '', major: '', grade: null as number | null };
constructor(private studentService: StudentService) {}
ngOnInit() {
this.students = this.studentService.getStudents();
}
get filteredStudents(): Student[] {
return this.students.filter(s =>
s.name.includes(this.searchTerm) ||
s.major.includes(this.searchTerm)
);
}
addStudent() {
if (!this.newStudent.name || !this.newStudent.major || !this.newStudent.grade) return;
this.studentService.addStudent({ ...this.newStudent, grade: this.newStudent.grade! });
this.newStudent = { name: '', major: '', grade: null };
this.students = this.studentService.getStudents();
}
deleteStudent(id: number) {
this.studentService.deleteStudent(id);
this.students = this.studentService.getStudents();
}
}
4. src/app/app.component.html
<div class="container">
<h1>📋 学生管理系统</h1>
<!-- 搜索 -->
<input
type="text"
[(ngModel)]="searchTerm"
placeholder="搜索姓名或专业..."
class="search-input"
/>
<!-- 添加表单 -->
<div class="add-form">
<input [(ngModel)]="newStudent.name" placeholder="姓名" />
<input [(ngModel)]="newStudent.major" placeholder="专业" />
<input [(ngModel)]="newStudent.grade" placeholder="成绩" type="number" />
<button (click)="addStudent()">➕ 添加</button>
</div>
<!-- 学生列表 -->
<table>
<thead>
<tr>
<th>ID</th><th>姓名</th><th>专业</th><th>成绩</th><th>评级</th><th>操作</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let student of filteredStudents">
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ student.major }}</td>
<td>{{ student.grade }}</td>
<td>{{ student.grade >= 90 ? '优秀' : student.grade >= 80 ? '良好' : '及格' }}</td>
<td><button (click)="deleteStudent(student.id)">🗑️</button></td>
</tr>
</tbody>
</table>
</div>
5. src/app/app.module.ts
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],
bootstrap: [AppComponent]
})
export class AppModule { }
6. src/styles.scss
body { font-family: system-ui; background: #f5f5f5; margin: 0; padding: 20px; }
.container { max-width: 800px; margin: 0 auto; }
.search-input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 6px; }
.add-form { display: flex; gap: 10px; margin: 10px 0;
input { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
button { padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer; }
}
table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden;
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
th { background: #f8fafc; }
}
运行步骤
ng serve --open
预期输出
- 首页显示预置的 3 名学生
- 搜索框输入"计算机"过滤出张三
- 添加新学生后自动刷新列表
- 点击删除按钮移除学生
Angular Hello World — 组件与双向绑定
目标
创建 Angular 应用,展示组件、双向绑定、服务注入、路由和 HTTP 请求。
完整代码
项目创建
ng new angular-demo --routing --style=scss
cd angular-demo
ng generate component counter
ng generate component todo
ng generate service todo
ng serve
根组件 src/app/app.component.ts
import { Component } from "@angular/core";
@Component({
selector: "app-root",
template: `
<main class="app">
<h1>🅰️ Angular Demo</h1>
<p class="subtitle">企业级前端框架的 Hello World</p>
<nav>
<a routerLink="/counter">计数器</a>
<a routerLink="/todo">待办事项</a>
</nav>
<router-outlet></router-outlet>
</main>
`,
styles: [`
.app {
max-width: 560px; margin: 40px auto; padding: 0 16px;
font-family: -apple-system, 'Microsoft YaHei', sans-serif;
}
h1 { text-align: center; }
.subtitle { text-align: center; color: #666; }
nav { display: flex; gap: 16px; justify-content: center; margin: 20px 0; }
nav a {
padding: 8px 20px; border-radius: 8px;
text-decoration: none; background: #f1f5f9; color: #333;
transition: 0.2s;
}
nav a:hover, nav a.active { background: #3b82f6; color: white; }
`],
})
export class AppComponent {
title = "angular-demo";
}
路由 src/app/app.routes.ts
import { Routes } from "@angular/router";
import { CounterComponent } from "./counter/counter.component";
import { TodoComponent } from "./todo/todo.component";
export const routes: Routes = [
{ path: "", redirectTo: "/counter", pathMatch: "full" },
{ path: "counter", component: CounterComponent },
{ path: "todo", component: TodoComponent },
];
计数器组件 src/app/counter/counter.component.ts
import { Component, signal } from "@angular/core";
@Component({
selector: "app-counter",
template: `
<div class="card">
<h2>🔢 计数器</h2>
<p class="hint">(使用 Angular Signals API)</p>
<div class="count">{{ count() }}</div>
<div class="buttons">
<button class="btn-inc" (click)="increment()">+ 1</button>
<button class="btn-dec" (click)="decrement()">- 1</button>
<button class="btn-reset" (click)="reset()">重置</button>
</div>
@if (count() >= 10) {
<div class="achievement">🎉 达到 {{ count() }} 次!</div>
}
</div>
`,
styles: [`
.card { text-align: center; padding: 24px; }
.hint { color: #888; font-size: 0.85rem; }
.count { font-size: 3rem; font-weight: 700; margin: 12px 0; }
.buttons button {
padding: 10px 22px; border: none; border-radius: 8px;
font-size: 1rem; cursor: pointer; margin: 4px; color: white;
}
.btn-inc { background: #10b981; }
.btn-dec { background: #ef4444; }
.btn-reset { background: #64748b; }
.achievement {
margin-top: 16px; padding: 12px;
background: #fef3c7; border-radius: 8px;
}
`],
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update((c) => c + 1);
}
decrement() {
this.count.update((c) => c - 1);
}
reset() {
this.count.set(0);
}
}
Todo 服务 src/app/todo.service.ts
import { Injectable } from "@angular/core";
export interface TodoItem {
id: number;
text: string;
done: boolean;
}
@Injectable({ providedIn: "root" })
export class TodoService {
private todos: TodoItem[] = [
{ id: 1, text: "学 Angular", done: true },
{ id: 2, text: "写项目", done: false },
];
getAll(): TodoItem[] {
return this.todos;
}
add(text: string): void {
this.todos.push({
id: Date.now(),
text,
done: false,
});
}
toggle(id: number): void {
const todo = this.todos.find((t) => t.id === id);
if (todo) todo.done = !todo.done;
}
remove(id: number): void {
this.todos = this.todos.filter((t) => t.id !== id);
}
}
Todo 组件 src/app/todo/todo.component.ts
import { Component, signal } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { TodoService, TodoItem } from "../todo.service";
@Component({
selector: "app-todo",
standalone: true,
imports: [FormsModule],
template: `
<div class="card">
<h2>📋 待办事项</h2>
<div class="input-row">
<input
[(ngModel)]="newTodo"
(keydown.enter)="addTodo()"
placeholder="添加新任务..."
/>
<button class="btn-primary" (click)="addTodo()">添加</button>
</div>
<ul>
@for (todo of todos; track todo.id) {
<li class="todo-item">
<span [class.done]="todo.done" (click)="toggleTodo(todo.id)">
{{ todo.text }}
</span>
<div>
<button
class="btn-sm"
[style.background]="todo.done ? '#f59e0b' : '#10b981'"
(click)="toggleTodo(todo.id)"
>
{{ todo.done ? "↩" : "✓" }}
</button>
<button class="btn-sm btn-del" (click)="removeTodo(todo.id)">
✕
</button>
</div>
</li>
}
</ul>
<p class="status">
{{ doneCount() }} / {{ todos.length }} 项完成
</p>
</div>
`,
styles: [`
.card { padding: 24px; }
.input-row { display: flex; gap: 8px; margin-bottom: 16px; }
.input-row input {
flex: 1; padding: 10px; border: 2px solid #e2e8f0;
border-radius: 8px; font-size: 1rem;
}
.btn-primary {
padding: 10px 20px; border: none; border-radius: 8px;
background: #3b82f6; color: white; cursor: pointer;
}
.todo-item {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 0; border-bottom: 1px solid #f1f5f9;
}
.done { text-decoration: line-through; color: #94a3b8; }
.btn-sm { border: none; border-radius: 6px; padding: 4px 10px;
color: white; cursor: pointer; margin-left: 4px; }
.btn-del { background: #ef4444; }
.status { margin-top: 12px; color: #888; font-size: 0.9rem; }
`],
})
export class TodoComponent {
todos: TodoItem[] = [];
newTodo = "";
constructor(private todoService: TodoService) {
this.todos = this.todoService.getAll();
}
doneCount() {
return this.todos.filter((t) => t.done).length;
}
addTodo() {
if (this.newTodo.trim()) {
this.todoService.add(this.newTodo.trim());
this.newTodo = "";
}
}
toggleTodo(id: number) {
this.todoService.toggle(id);
}
removeTodo(id: number) {
this.todoService.remove(id);
this.todos = this.todoService.getAll();
}
}
运行步骤
ng serve
预期输出
- 导航栏切换计数器 / 待办事项两个页面
- 计数器使用 Signal 响应式更新
- 待办事项支持添加、完成、删除
- 服务注入(Dependency Injection)自动管理 TodoService 单例