Angluar
最近在熟悉Angluar的開發,CRUD其實是最好入手的例子與範例,在此用一個Student的單一TABLE來做CRUD做一個前後端的串聯學習。
在Visual Studio 2022建立一個WebDemo專案(新增ASP.net Core WebAPI),進行建立Model資料夾,並新增Student。
[Table("Student")]
public class Student
{
[Key]
public int Id { get; set; }
public string? Name { get; set; }
public int Age { get; set; }
public string? Gender { get; set; }
}
新增一個Repository資料夾,並在底下新增一個Interface資料夾,並新增IGenericReposiotry.cs
public interface IGenericReposiotry<TEntity> where TEntity : class
{
IEnumerable<TEntity> GetAll();
IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter);
IEnumerable<TEntity> Get<TKey>(Expression<Func<TEntity, bool>> filter, int pageIndex, int pageSize, Expression<Func<TEntity, TKey>> sortKeySelector, bool isAsc = true);
void Insert(TEntity entity);
void Delete(TEntity entityToDelete);
void Update(TEntity entityToUpdate);
int SaveChanges();
}
最後新增套件
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
新增StudentContext.cs
public class StudentContext:DbContext
{
public StudentContext(DbContextOptions<StudentContext> options):base(options)
{
}
public DbSet<Student> Students { get; set; }
}
在appsettings.json
{
"ConnectionStrings": {
"DefaultDatabase": "server=(LocalDb)\\MSSQLLocalDB;database=myDb;trusted_connection=true;"
// "DefaultDatabase": "server=localhost;database=myDb;trusted_connection=true;TrustServerCertificate=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
在Repository新增GenericReposiotry.cs
public class GenericRepository<TEntity> : IGenericReposiotry<TEntity> where TEntity : class
{
public readonly StudentContext _context;
public DbSet<TEntity> _dbSet;
public GenericRepository(StudentContext context)
{
this._context = context;
_dbSet = _context.Set<TEntity>();
// 印出EF 產生SQL語法監看效能
//this._context.Database.Log = (log) => Debug.WriteLine(log);
}
public IEnumerable<TEntity> GetAll()
{
return _dbSet.AsQueryable();
}
public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter)
{
return _dbSet.Where(filter).AsQueryable();
}
public IEnumerable<TEntity> Get<TKey>(Expression<Func<TEntity, bool>> filter, int pageIndex, int pageSize, Expression<Func<TEntity, TKey>> sortKeySelector, bool isAsc = true)
{
if (isAsc)
{
return _dbSet
.Where(filter)
.OrderBy(sortKeySelector)
.Skip(pageSize * (pageIndex - 1))
.Take(pageSize).AsQueryable();
}
else
{
return _dbSet
.Where(filter)
.OrderByDescending(sortKeySelector)
.Skip(pageSize * (pageIndex - 1))
.Take(pageSize).AsQueryable();
}
}
public void Insert(TEntity entity)
{
_dbSet.Add(entity);
}
public void Delete(object id)
{
TEntity entityToDelete = _dbSet.Find(id);
Delete(entityToDelete);
}
public void Delete(TEntity entityToDelete)
{
if (_context.Entry(entityToDelete).State == EntityState.Deleted)
{
_dbSet.Attach(entityToDelete);
}
_dbSet.Remove(entityToDelete);
}
public void Update(TEntity entityToUpdate)
{
_dbSet.Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
}
public int SaveChanges()
{
//try
//{
return _context.SaveChanges();
//}
//catch (DbEntityValidationException ex)
//{
// // Retrieve the error messages as a list of strings.
// var errorMessages = ex.EntityValidationErrors
// .SelectMany(x => x.ValidationErrors)
// .Select(x => x.ErrorMessage);
// // Join the list to a single string.
// var fullErrorMessage = string.Join("; ", errorMessages);
// // Combine the original exception message with the new one.
// var exceptionMessage =
// string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// // Throw a new DbEntityValidationException with the improved exception message.
// throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
//}
}
}
補充知識:為什麼要定義GenericRepository。例如,如果我們有Employee、Product和Customer等三個實體,那麼我們需要建立EmployeeRepository、ProductRepository和CustomerRepository等三個Repository。
這實際上是在做重複的工作,特別是如果所有Repository都將執行相同類型的工作( CRUD 操作)。這違反了 DRY(不要重複自己)原則,因為我們在每個Repository中一次又一次地重複相同的Code。為了解決上述問題,Generic Repository Pattern才會誕生。
新增IStudentRepository並繼承IGenericRepository
public interface IStudentRepository : IGenericReposiotry<Student>
{
}
新增StudentRepository
public class StudentRepository : GenericRepository<Student>, IStudentRepository
{
public StudentRepository(StudentContext dbContext) : base(dbContext)
{
}
}
進行Entity Framework Core
add-migration init
update-database
注入部分
using Microsoft.EntityFrameworkCore;
using WebDemoAPI.Model;
using WebDemoAPI.Repository.Interface;
using WebDemoAPI.Repository;
var builder = WebApplication.CreateBuilder(args);
string connString = builder.Configuration.GetConnectionString("DefaultDatabase");
// Add services to the container.
builder.Services.AddDbContext<StudentContext>(options => options.UseSqlServer(connString));
builder.Services.AddScoped(typeof(IGenericReposiotry<>), typeof(GenericRepository<>));
builder.Services.AddScoped<IStudentRepository, StudentRepository>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//加入CORS
app.UseCors(builder => builder
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
);
app.UseAuthorization();
app.MapControllers();
app.Run();
Controller部分
[Route("api/[controller]/[action]")]
[ApiController]
public class StudentController : ControllerBase
{
private readonly IStudentRepository _studentRepository;
public StudentController(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
[HttpGet]
public IActionResult GetStudents()
{
var students = _studentRepository.GetAll().ToList();
return Ok(students);
}
[HttpPost]
public IActionResult Post(Student payload)
{
_studentRepository.Insert(payload);
_studentRepository.SaveChanges();
return Ok(payload);
}
[HttpPut]
public IActionResult Put(Student payload)
{
_studentRepository.Update(payload);
_studentRepository.SaveChanges();
return Ok(payload);
}
[HttpDelete]
public IActionResult Delete(int id)
{
var result = _studentRepository.Get(x => x.Id == id).FirstOrDefault();
if (result == null)
{
return NotFound();
}
_studentRepository.Delete(result);
_studentRepository.SaveChanges();
return Ok();
}
}
API開發完之後,接者開始寫前端的Angluar畫面!
ng new name-of-your-project
npm install bootstrap
到angluar.json部分設定
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts":
[
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]
ng g component student --skip-tests
ng g class student/student --skip-tests
export interface Student {
id: number;
name: string;
age: number;
gender: string;
}
ng g service student/student --skip-tests
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'
import { Student } from './student';
@Injectable({
providedIn: 'root'
})
export class StudentService {
constructor(private httpClient:HttpClient) { }
get(){
return this.httpClient.get<Student[]>("http://localhost:5069/api/Student/GetStudents")
}
}
student.component.ts
import { Component, OnInit } from '@angular/core';
import {Student} from './student';
import { StudentService } from './student.service';
@Component({
selector: 'app-student',
templateUrl: './student.component.html',
styleUrls: ['./student.component.css']
})
export class StudentComponent implements OnInit {
students:Student[] = [];
constructor(private studentService:StudentService) { }
ngOnInit(): void {
this.get();
}
get()
{
this.studentService.get()
.subscribe({
next:(data) => {
this.students = data;
},
error:(err) => {
console.log(err);
}
})
}
}
student.component.html
<table class="table">
<thead>
<tr>
<th scope="col">編號</th>
<th scope="col">姓名</th>
<th scope="col">年齡</th>
<th scope="col">性別</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let sd of students">
<th scope="row">{{sd.id}}</th>
<td>{{sd.name}}</td>
<td>{{sd.age}}</td>
<td>{{sd.gender}}</td>
</tr>
</tbody>
</table>
<nav class="navbar navbar-expand-lg navbar-light bg-primary">
<div class="container-fluid ">
<a class="navbar-brand" href="#">範例程式</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"></ul>
</div>
</div>
</nav>
<app-student></app-student>
app.module.ts如下
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { StudentComponent } from './student/student.component';
@NgModule({
declarations: [
AppComponent,
StudentComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
新增功能串接在StudentService追加post method
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'
import { Student } from './student';
@Injectable({
providedIn: 'root'
})
export class StudentService {
url = 'http://localhost:5141/api/Student/';
constructor(private httpClient:HttpClient) { }
get(){
return this.httpClient.get<Student[]>(this.url + "GetStudents")
}
post(payload: Student) {
return this.httpClient.post<Student>(
this.url + 'Post',
payload
);
}
}
student.component.ts
import { Component ,OnInit} from '@angular/core';
import { Student} from './student';
import { StudentService } from './student.service';
declare var window: any;
@Component({
selector: 'app-student',
templateUrl: './student.component.html',
styleUrls: ['./student.component.css']
})
export class StudentComponent implements OnInit {
students:Student[] = [];
addorupdatemodal: any;
studentForm: Student = {
age: 0,
gender: 'Male',
id: 0,
name: '',
};
addorupdatemodalTitle: string = '';
constructor(private studentService:StudentService) { }
ngOnInit(): void {
this.get();
this.addorupdatemodal = new window.bootstrap.Modal(
document.getElementById('addorupdatemodal')
);
}
get()
{
this.studentService.get()
.subscribe({
next:(data) => {
this.students = data;
},
error:(err) => {
console.log(err);
}
})
}
openAddOrUpdateModal(studentId:number)
{
if(studentId ===0)
{
this.addorupdatemodalTitle = '新增';
this.studentForm = {
age: 0,
gender: 'Male',
id: 0,
name: '',
};
this.addorupdatemodal.show();
}
}
createorUpdateStudent() {
if (this.studentForm.id == 0) {
this.studentService.post(this.studentForm).subscribe({
next: (data) => {
this.students.unshift(data);
this.addorupdatemodal.hide();
},
error: (error) => {
console.log(error);
},
});
}
}
}
加入新增按鈕
<table class="table">
<thead>
<tr>
<th scope="col">編號</th>
<th scope="col">姓名</th>
<th scope="col">年齡</th>
<th scope="col">性別</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let sd of students">
<th scope="row">{{sd.id}}</th>
<td>{{sd.name}}</td>
<td>{{sd.age}}</td>
<td>{{sd.gender}}</td>
</tr>
</tbody>
</table>
<!-- Modal -->
<div class="container">
<div class="row">
<div class="col col-md-4 offset-md-4">
<button type="button" (click)='openAddOrUpdateModal(0)' class="btn btn-primary">新增</button>
</div>
</div>
</div>
<div class="modal fade" id="addorupdatemodal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{{addorupdatemodalTitle}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<input type="hidden" id="studentId" [(ngModel)]="studentForm.id">
<div class="mb-3">
<label for="name" class="form-label">姓名</label>
<input type="text" class="form-control" id="name" [(ngModel)]="studentForm.name" />
</div>
<div class="mb-3">
<label for="age" class="form-label">年齡</label>
<input type="number" class="form-control" id="age" [(ngModel)]="studentForm.age" />
</div>
<div class="mb-3">
<label for="age" class="form-label">性別</label>
<select class="form-select" [(ngModel)]="studentForm.gender" aria-label="Default select example">
<option selected>Open this select menu</option>
<option value="M">男</option>
<option value="F">女</option>
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
關閉
</button>
<button type="button" (click)="createorUpdateStudent()" class="btn btn-primary">儲存</button>
</div>
</div>
</div>
</div>
若無法使用ngModel問題,則要匯入FormsModule
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StudentComponent } from './student/student.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent,
StudentComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Student.service.ts底下追加update
update(payload: Student) {
return this.httpClient.put<Student>(
this.url + 'Put',
payload
);
}
Student.component.ts
openAddOrUpdateModal(studentId:number)
{
if(studentId ===0)
{
this.addorupdatemodalTitle = '新增';
this.studentForm = {
age: 0,
gender: 'Male',
id: 0,
name: '',
};
this.addorupdatemodal.show();
}
else
{
this.addorupdatemodalTitle = '修改';
this.studentForm = this.students.filter(s => s.id === studentId)[0];
this.addorupdatemodal.show();
}
}
createorUpdateStudent()
{
if (this.studentForm.id == 0) {
this.studentService.post(this.studentForm).subscribe({
next: (data) => {
this.students.unshift(data);
this.addorupdatemodal.hide();
},
error: (error) => {
console.log(error);
},
});
}else{
this.studentService.update(this.studentForm).subscribe({
next:(data) => {
this.students = this.students.filter(_ => _.id !== data.id);
this.students.unshift(data);
this.addorupdatemodal.hide();
}
})
}
}
<tbody>
<tr *ngFor="let sd of students">
<th scope="row">{{sd.id}}</th>
<td>{{sd.name}}</td>
<td>{{sd.age}}</td>
<td>{{sd.gender}}</td>
<td>
<button class="btn btn-primary" (click)="openAddOrUpdateModal(sd.id)">編輯</button>
</td>
</tr>
</tbody>
Student.service.ts底下追加Delete功能
delete(studetId: number) {
return this.httpClient.delete(
this.url + '/Delete?id=${studetId}'
);
}
import { Component ,OnInit} from '@angular/core';
import { Student} from './student';
import { StudentService } from './student.service';
declare var window: any;
@Component({
selector: 'app-student',
templateUrl: './student.component.html',
styleUrls: ['./student.component.css']
})
export class StudentComponent implements OnInit {
students:Student[] = [];
addorupdatemodal: any;
studentForm: Student = {
age: 0,
gender: 'Male',
id: 0,
name: '',
};
addorupdatemodalTitle: string = '';
deleteModal: any;
studentIdToDelete: number = 0;
constructor(private studentService:StudentService) { }
ngOnInit(): void {
this.get();
this.addorupdatemodal = new window.bootstrap.Modal(
document.getElementById('addorupdatemodal')
);
this.deleteModal = new window.bootstrap.Modal(
document.getElementById('deleteModal')
);
}
get()
{
this.studentService.get()
.subscribe({
next:(data) => {
this.students = data;
},
error:(err) => {
console.log(err);
}
})
}
openAddOrUpdateModal(studentId:number)
{
if(studentId ===0)
{
this.addorupdatemodalTitle = '新增';
this.studentForm = {
age: 0,
gender: 'Male',
id: 0,
name: '',
};
this.addorupdatemodal.show();
}
else
{
this.addorupdatemodalTitle = '修改';
this.studentForm = this.students.filter(s => s.id === studentId)[0];
this.addorupdatemodal.show();
}
}
createorUpdateStudent() {
if (this.studentForm.id == 0) {
this.studentService.post(this.studentForm).subscribe({
next: (data) => {
this.students.unshift(data);
this.addorupdatemodal.hide();
},
error: (error) => {
console.log(error);
},
});
}else{
this.studentService.update(this.studentForm).subscribe({
next:(data) => {
this.students = this.students.filter(_ => _.id !== data.id);
this.students.unshift(data);
this.addorupdatemodal.hide();
}
})
}
}
openDeleteModal(studentId: number) {
this.studentIdToDelete = studentId;
this.deleteModal.show();
}
confirmDelete(){
this.studentService.delete(this.studentIdToDelete)
.subscribe({
next:(data) => {
this.students = this.students.filter(_ => _.id !== this.studentIdToDelete);
this.deleteModal.hide();
},
error:(error) => {
console.log(error);
}
})
}
}
<table class="table">
<thead>
<tr>
<th scope="col">編號</th>
<th scope="col">姓名</th>
<th scope="col">年齡</th>
<th scope="col">性別</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let sd of students">
<th scope="row">{{sd.id}}</th>
<td>{{sd.name}}</td>
<td>{{sd.age}}</td>
<td>{{sd.gender}}</td>
<td>
<button class="btn btn-primary" (click)="openAddOrUpdateModal(sd.id)">編輯</button>
<button class="btn btn-primary" (click)="openDeleteModal(sd.id)">刪除</button>
</td>
</tr>
</tbody>
</table>
<!-- Modal -->
<div class="container">
<div class="row">
<div class="col col-md-4 offset-md-4">
<button type="button" (click)='openAddOrUpdateModal(0)' class="btn btn-primary">新增</button>
</div>
</div>
</div>
<div class="modal fade" id="addorupdatemodal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{{addorupdatemodalTitle}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<input type="hidden" id="studentId" [(ngModel)]="studentForm.id">
<div class="mb-3">
<label for="name" class="form-label">姓名</label>
<input type="text" class="form-control" id="name" [(ngModel)]="studentForm.name" />
</div>
<div class="mb-3">
<label for="age" class="form-label">年齡</label>
<input type="number" class="form-control" id="age" [(ngModel)]="studentForm.age" />
</div>
<div class="mb-3">
<label for="age" class="form-label">性別</label>
<select class="form-select" [(ngModel)]="studentForm.gender" aria-label="Default select example">
<option selected>Open this select menu</option>
<option value="M">男</option>
<option value="F">女</option>
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
關閉
</button>
<button type="button" (click)="createorUpdateStudent()" class="btn btn-primary">儲存</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">刪除</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="關閉"></button>
</div>
<div class="modal-body">
<h1>是否確認刪除 </h1>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">關閉</button>
<button type="button" (click)="confirmDelete()" class="btn btn-primary">刪除</button>
</div>
</div>
</div>
</div>
完成如下
元哥的筆記