angular框架的成绩管理(增删改查)
1.应用简介
页面链接:https://zmh914.github.io/grade/
应用为基于angular框架的简易“学生成绩管理平台”,对学生成绩数据进行增、删、改、查等操作。内容包含根组件、学生组件、成绩详情组件、不及格组件、消息组件、学生查询组件。
组件间建立路由进行交互,组件只负责信息的展示,信息的获取与更改由服务进行,通过不同组件间注入相同的服务实现组件间的数据共享,且将数据的显示与操作进行分离后,降低组件间的耦合性,使得后期应用修改更新更加便捷。
2.开发过程
2.1服务说明
student.service.ts
本服务包含学生信息的增、删、改、查等方法,通过将本服务注入到其他组件中,调用服务方法,完成信息的获取与展示。并注入了消息服务,在完成一个操作后便发送一条消息。
import { Injectable } from '@angular/core';
import { Student } from './student';
import { Observable, of } from 'rxjs';
import { MessageService } from './message.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { catchError, map, tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class StudentService {
//虚拟内存API链接
private studentsUrl = 'api/students'; // URL to web api
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
//获取全部学生信息
getStudents(): Observable<Student[]> {
return this.http.get<Student[]>(this.studentsUrl).pipe(tap(_ =>
this.log('fetched students')), catchError(this.handleError<Student[]>('getStudents', [])));
}
//获取单个学生信息
getStudent(id: number): Observable<Student> {
const url = `${this.studentsUrl}/${id}`;
return this.http.get<Student>(url).pipe(
tap(_ => this.log(`fetched student id=${id}`)),
catchError(this.handleError<Student>(`getStudent id=${id}`))
);
}
//错误处理
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error); // log to console instead
this.log(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
//封装消息发送服务
private log(message: string) {
this.messageService.add(`StudentService: ${message}`);
}
//成绩更新服务
updateGrade(student: Student): Observable<any> {
return this.http.put(this.studentsUrl, student, this.httpOptions).pipe(
tap(_ => this.log(`updated ${student.name} grade=${student.grade}`)),
catchError(this.handleError<any>('updateGrade'))
);
}
//添加学生信息服务
addStudent(student: Student): Observable<Student> {
return this.http.post<Student>(this.studentsUrl, student, this.httpOptions).pipe(
tap((newStudent: Student) => this.log(`added student w/ id=${newStudent.id}`)),
catchError(this.handleError<Student>('addStudent'))
);
}
//删除学生信息服务
deleteStudent(id: number): Observable<Student> {
const url = `${this.studentsUrl}/${id}`;
return this.http.delete<Student>(url, this.httpOptions).pipe(
tap(_ => this.log(`deleted students id=${id}`)),
catchError(this.handleError<Student>('deleteStudent'))
);
}
/* 学生成绩查询 */
searchStudents(term: number): Observable<Student[]> {
if (!term) {
return of([]);
}
return this.http.get<Student[]>(`${this.studentsUrl}/?id=${term}`).pipe(
tap( x => x ?
this.log(`found students matching "${term}"`) :
this.log(`no students matching "${term}"`)),
catchError(this.handleError<Student[]>('searchStudents', []))
);
}
constructor( private http: HttpClient, private messageService: MessageService) { }
}
message.service.ts
本服务主要进行消息的发送与清空,将本服务注入到其他服务或组件中,可是实现在完成一个操作后发送一条消息。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MessageService {
messages : string[] = [];
add(message: string ){
this.messages.push(message);
}
clear(){
this.messages = [];
}
constructor() { }
}
2.2组件说明
组件完成各功能部分的界面展示,一个组件包含HTML文件、CSS文件、以及对应组件的TS类文件。
(1)学生组件
显示所有学生名单,并进行学生信息录入。在显示学生信息时调用学生服务中的获取全部学生信息方法,从虚拟内存中获取全部学生的信息。添加了学生成绩详情的组件路由,点击可跳转到成绩详情页面。
录入学生信息时调用学生服务中的添加学生信息服务,将新添加的信息传入虚拟内存。
HTML
<div class="input">
<label for="new-hero">学生姓名: </label>
<input id="new-hero" #studentName />
<!-- (click) passes input value to add() and then clears the input -->
<button class="add-button" (click)="add(studentName.value);studentName.value=''">
录入
</button>
</div>
<table>
<tr>
<th>学号</th>
<th>姓名</th>
<th>删除</th>
</tr>
<tr *ngFor="let student of students">
<td>
<a routerLink="/detail/{{student.id}}">{{student.id}}</a> <!--学生成绩详情路由-->
</td>
<td>
<a routerLink="/detail/{{student.id}}">{{student.name}}</a>
</td>
<td>
<button (click)="delete(student)"> x </button>
</td>
</tr>
</table>
TS
import { Component, OnInit } from '@angular/core';
import { Student } from '../student';
import { StudentService } from '../student.service';
import { MessageService } from '../message.service';
@Component({
selector: 'app-students',
templateUrl: './students.component.html',
styleUrls: ['./students.component.css']
})
export class StudentsComponent implements OnInit {
students: Student[];
//调用服务获取学生信息
getStudents(): void {
this.studentService.getStudents().subscribe(students => this.students = students);
}
//添加学生信息
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.studentService.addStudent({ name } as Student)
.subscribe(student => {
this.students.push(student);
});
}
//删除学生信息
delete(student: Student): void {
this.students = this.students.filter(h => h !== student);
this.studentService.deleteStudent(student.id).subscribe();
}
constructor(private studentService: StudentService , private messageService: MessageService ) { }
//进入学生组件时便调用获取学生方法
ngOnInit(): void {
this.getStudents();
}
}
(2)成绩详情组件
本组件显示单个学生的成绩信息,并可对其进行更改。在学生组件页面中点击学生名字或id后,跳转到成绩详情组件,并调用学生服务中的获取单个学生信息方法,展示单个学生的成绩信息。
修改成绩时使用插值绑定,然后调用学生服务中的更新学生信息方法,将修改后的成绩信息更新到虚拟内存中。
HTML
<div *ngIf="student">
<h2>{{student.name }} </h2>
<div class = "id">
<span>学号: </span>{{student.id}}
</div>
<div class = "grade">
<label >成绩: </label>
<input [(ngModel)]="student.grade" placeholder="成绩">
</div>
<button class = "back" (click)="goBack()">返回</button>
<button class = "save" (click)="save()">保存</button>
</div>
TS
import { Component, OnInit, Input } from '@angular/core';
import { Student } from '../student';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { StudentService } from '../student.service';
@Component({
selector: 'app-student-detail',
templateUrl: './student-detail.component.html',
styleUrls: ['./student-detail.component.css']
})
export class StudentDetailComponent implements OnInit {
@Input() student?: Student;
constructor(
private route: ActivatedRoute,
private studentService: StudentService,
private location: Location
) {}
//调用获取单个学生信息的服务方法
getStudent(): void {
const id =
Number(this.route.snapshot.paramMap.get('id'));
this.studentService.getStudent(id).subscribe(student => this.student = student);
}
goBack(): void {
this.location.back();
}
//调用更新学生成绩信息的服务方法
save(): void {
if (this.student) {
this.studentService.updateGrade(this.student)
.subscribe(() => this.goBack());
}
}
ngOnInit(): void {
this.getStudent();
}
}
(3)不及格组件
本组件显示学生名单中不及格学生,调用学生服务中的获取全部学生信息方法,然后选取需要显示的学生名单进行显示。
HTML
<div class="heroes-menu">
<a *ngFor="let student of students"
routerLink="/detail/{{student.id}}">
{{student.name}}
</a>
</div>
TS
import { Component, OnInit } from '@angular/core';
import { Student } from '../student';
import { StudentService } from '../student.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [ './dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
students: Student[] = [];
constructor(private studentService: StudentService) { }
ngOnInit() {
this.getStudents();
}
getStudents(): void {
this.studentService.getStudents()
.subscribe(students => this.students = students.slice(0, 8));
}
}
(4)根组件
根组件显示导航条,作为路由模块的路由出口,导航条链接了其他各个组件的路由链接,进行各个组件间的转换。
HTML
<div class="title">
<img src="assets/成绩单.png" alt="成绩管理" ><h1>{{title}}</h1>
</div>
<ul>
<li><a routerLink="/students">学生名单</a></li>
<li><a routerLink="/dashboard">不及格名单</a></li>
<li><a routerLink="/search">成绩查询</a></li>
<li><a routerLink="/history">操作历史</a></li>
<li><a routerLink="/blog">报告</a></li>
</ul>
<router-outlet></router-outlet>
TS
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = '学生成绩管理';
}
(5)消息组件
显示消息内容。
HTML
<div *ngIf="messageService.messages.length">
<button class="clear"
(click)="messageService.clear()">清空</button>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
</div>
TS
import { Component, OnInit } from '@angular/core';
import { MessageService } from '../message.service';
@Component({
selector: 'app-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.css']
})
export class MessagesComponent implements OnInit {
constructor( public messageService:MessageService ) { }
ngOnInit(): void {
}
}
2.3路由模块说明
新建一个独立的顶层模块,加载和配置路由器,它专注于路由功能,然后由根模块 AppModule
导入它。
这个模块类的名字叫做 AppRoutingModule
,并且位于 src/app
下的 app-routing.module.ts
文件中。
在路由模块中配置各个组件的路由链接地址,在其他模组中导入路由,然后便可以使用超链接通过组件的路由uRL在不同组件间跳转。
app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { StudentsComponent } from './students/students.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { StudentDetailComponent } from './student-detail/student-detail.component';
import { StudentSearchComponent } from './student-search/student-search.component';
import { MessagesComponent } from './messages/messages.component';
import { BlogComponent } from './blog/blog.component';
const routes: Routes = [
//path: 用来匹配浏览器地址栏中 URL 的字符串。component: 导航到该路由时,路由器应该创建的组件。
{ path: 'detail/:id', component: StudentDetailComponent },
{ path: 'students', component: StudentsComponent },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'search', component: StudentSearchComponent },
{ path: 'history', component: MessagesComponent },
{ path: 'blog', component: BlogComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
2.4虚拟内存说明
HttpClient
是 Angular 通过 HTTP 与远程服务器通讯的机制。本次实操由于没有架设服务器,所以选择了模拟数据服务器。
安装angular-in-memory-web-api
模块,应用将会通过HttpClient
来发起请求和接收响应,而不用在乎实际上是这个内存 Web API 在拦截这些请求、操作一个内存数据库,并且给出仿真的响应。
in-memory-data.service.ts
import { Injectable } from '@angular/core';
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Student } from './student';
@Injectable({
providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const students = [
{ id: 1, name: '张安', grade: 120 },
{ id: 2, name: '王五', grade: 120 },
{ id: 3, name: 'Bombasto', grade: 120 },
{ id: 4, name: '李四', grade: 120 },
{ id: 5, name: '曹操', grade: 120 },
{ id: 6, name: 'RubberMan', grade: 120 },
{ id: 7, name: 'Dynama', grade: 120 },
{ id: 8, name: '诸葛亮', grade: 120 },
{ id: 9, name: 'Tornado', grade: 120 },
{ id: 10, name: 'Magma', grade: 120 }
];
return {students};
}
genId(students: Student[]): number {
return students.length > 0 ? Math.max(...students.map(student => student.id)) + 1 : 11;
}
}
3.问题记录与解决方法
3.1图片无法显示
开始时按照原先静态页面的方法将图片与HTML文件放到同一文件夹中,使用标签进行图片的显示,但是图片无法显示。
经过查阅博客资料,得知需将图片文件放到src/assets
中,然后调用图片便可以显示。
3.2GitHub部署
使用angular-cli-ghpages
工具进行打包发布,需要先编译项目,然后将编译后的项目文件推送到GitHub仓库,开始时,使用了错误的github仓库链接进行编译,导致页面无内容显示。
修改正确GitHub仓库地址后,便可正常显示内容。
3.3未解决问题
在添加学生信息时,想多个信息同时添加,但是添加后无法调用学生成绩详情,新添加的信息在删除后不会对虚拟数据进行更改,重新加载组建后还是会显示。
所以在添加信息时,今天加了姓名信息,随机生成id信息,然后进入详情页面进行成绩录入。
4.总结
通过这样一个简单的angular项目,对现在的前端应用框架有了基本的认识。与静态网站不同,动态网站更加适合大型应用的开发,各组件与服务的分离,使得结构分工明确,更加便于应用开发与后期更新操作。
有了angular的基础,再去学习其他前端框架会更加容易。本次实操进行了简单的应用,对模块、组件与服务等进行了练习。功能尚不完整,代码书写尚未规范,未采用样式框架,界面不美观,后续还需要改进。