首页  >  文章  >  后端开发  >  使用 Go API 构建 Angular CRUD 应用程序

使用 Go API 构建 Angular CRUD 应用程序

Patricia Arquette
Patricia Arquette原创
2024-11-04 21:36:02477浏览

Building an Angular CRUD App with a Go API

CRUD 操作(创建、读取、更新、删除)是大多数 Web 应用程序的支柱。在本教程中,我们将向您展示如何构建前端使用 Angular、后端使用 GoAPI 的 CRUD 应用程序,从而形成完全集成且高效的全栈解决方案。

先决条件

  • Node.js
  • 去1.21
  • MySQL

设置 Angular 项目

安装 Angular 18 并使用以下命令创建一个新项目。

npm install -g @angular/cli@18.0.0
ng new view --minimal --routing --style css --no-standalone --ssr=false

Angular 项目结构

└─ src
   ├─ app
   │  ├─ app-routing.module.ts
   │  ├─ app.component.ts
   │  ├─ app.interceptor.ts
   │  ├─ app.module.ts
   │  └─ components
   │     └─ product
   │        ├─ Create.component.ts
   │        ├─ Delete.component.ts
   │        ├─ Detail.component.ts
   │        ├─ Edit.component.ts
   │        ├─ Index.component.ts
   │        └─ Product.service.ts
   ├─ index.html
   ├─ main.ts
   └─ styles.css

*此项目结构将仅显示我们计划创建或修改的文件和文件夹。

角度项目文件

主要.ts

import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'

import { AppModule } from './app/app.module'

platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e))

这个 main.ts 文件通过使用 platformBrowserDynamic 函数引导 AppModule 来初始化 Angular 应用程序。它将应用程序设置为在浏览器中运行并处理引导过程中发生的任何错误。

应用程序模块.ts

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { AppInterceptor } from './app.interceptor'

import { ProductIndex } from './components/product/Index.component'
import { ProductCreate } from './components/product/Create.component'
import { ProductDetail } from './components/product/Detail.component'
import { ProductEdit } from './components/product/Edit.component'
import { ProductDelete } from './components/product/Delete.component'

@NgModule({
  declarations: [
    AppComponent,
    ProductIndex,
    ProductCreate,
    ProductDetail,
    ProductEdit,
    ProductDelete,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AppInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

AppModule 是 Angular 应用程序的主要模块。它导入核心 Angular 模块并使用 AppRoutingModule 设置路由。该模块声明了各种与产品相关的组件。它还将 AppInterceptor 注册为 HTTP 拦截器。 AppComponent 被设置为引导组件,使其成为应用程序的入口点。

应用程序组件.ts

import { Component } from '@angular/core'

@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`
})

export class AppComponent { }

app.component.ts 文件定义了根组件 AppComponent,它使用 。其模板中的指令用于显示路由视图。该组件由应用程序根选择器标识,并作为 Angular 应用程序的入口点。

应用程序拦截器.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor } from '@angular/common/http';
import { HttpRequest, HttpErrorResponse } from '@angular/common/http'
import { Observable, throwError } from 'rxjs'
import { HttpHandler } from '@angular/common/http'
import { HttpEvent } from '@angular/common/http'

@Injectable({
  providedIn: 'root'
})

export class AppInterceptor implements HttpInterceptor {

  baseURL = 'http://localhost:8080/api'

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request.clone({
      url: this.baseURL + request.url,
    }))
  }
}

AppInterceptor 类是一个 Angular HTTP 拦截器,它在将所有传出 HTTP 请求 URL 发送到服务器之前将可配置的 baseURL 附加到它们。这允许应用程序集中并轻松管理基本 API 端点。

应用程序路由.module.ts

import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { ProductIndex } from './components/product/Index.component'
import { ProductCreate } from './components/product/Create.component'
import { ProductDetail } from './components/product/Detail.component'
import { ProductEdit } from './components/product/Edit.component'
import { ProductDelete } from './components/product/Delete.component'

const routes: Routes = [
  { path: '', redirectTo: 'product', pathMatch: 'full' },
  { path: 'product', component: ProductIndex },
  { path: 'product/create', component: ProductCreate },
  { path: 'product/:id', component: ProductDetail },
  { path: 'product/edit/:id', component: ProductEdit },
  { path: 'product/delete/:id', component: ProductDelete }
]

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

AppRoutingModule 为 Angular 应用程序设置路由,包括用于列出、创建、查看、编辑和删除产品的产品相关路径。它还包括一条从根路径“/”重定向到产品列表页面“/product”的路由。

创建.component.ts

import { Component } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { ProductService } from './Product.service'

@Component({
  selector: 'product-create',
  template: `
    <div class="container">
      <div class="row">
        <div class="col">
          <form ngNativeValidate method="post" (submit)="create()">
            <div class="row">
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_name">Name</label>
                <input id="product_name" name="name" class="form-control" [(ngModel)]="product.Name" maxlength="50" />
                <span *ngIf="errors.name" class="text-danger">{{errors.name}}</span>
              </div>
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_price">Price</label>
                <input id="product_price" name="price" class="form-control" [(ngModel)]="product.Price" type="number" />
                <span *ngIf="errors.price" class="text-danger">{{errors.price}}</span>
              </div>
              <div class="col-12">
                <a class="btn btn-secondary" routerLink="/product">Cancel</a>
                <button class="btn btn-primary">Submit</button>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>`
})
export class ProductCreate {

  product?: any = {}
  errors?: any = {}
  constructor(private router: Router, private route: ActivatedRoute, private ProductService: ProductService) { }

  create() {
    this.ProductService.create(this.product).subscribe(() => {
      this.router.navigateByUrl('/product')
    }, (e) => {
      alert(e.error)
    })
  }
}

ProductCreate 组件提供了一个用于创建新产品的表单,将名称和价格的输入字段绑定到产品对象。提交后,它会调用 ProductService 创建产品并导航回产品列表。验证错误会显示在相应字段旁边,任何创建错误都会触发警报。

删除.component.ts

npm install -g @angular/cli@18.0.0
ng new view --minimal --routing --style css --no-standalone --ssr=false

Delete.component.ts 中的 ProductDelete 组件是一个处理产品删除的 Angular 组件。它显示一个带有只读字段的表单,其中显示产品的详细信息(ID、名称和价格)。当组件初始化时,它会使用路由中的产品 ID 获取产品详细信息。提交表单时,delete() 方法调用 ProductService 删除产品,然后重定向到产品列表。如果删除过程中出现错误,则会显示警报。

详细信息.组件.ts

└─ src
   ├─ app
   │  ├─ app-routing.module.ts
   │  ├─ app.component.ts
   │  ├─ app.interceptor.ts
   │  ├─ app.module.ts
   │  └─ components
   │     └─ product
   │        ├─ Create.component.ts
   │        ├─ Delete.component.ts
   │        ├─ Detail.component.ts
   │        ├─ Edit.component.ts
   │        ├─ Index.component.ts
   │        └─ Product.service.ts
   ├─ index.html
   ├─ main.ts
   └─ styles.css

ProductDetail 组件显示特定产品的详细信息。它根据路线中的 ID 检索产品信息,并在只读字段中显示产品的 ID、名称和价格。该组件提供“后退”和“编辑”按钮用于导航。组件初始化时会获取并显示产品详细信息。

编辑.component.ts

import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'

import { AppModule } from './app/app.module'

platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e))

ProductEdit 组件允许用户编辑现有产品。它使用路线中的产品 ID 检索产品详细信息,并将其显示在带有可编辑名称和价格字段的表单中。提交表单后,它通过 ProductService 更新产品并导航回产品列表。获取或更新期间的任何错误都会显示为警报,并且验证错误会显示在相关字段旁边。

索引.component.ts

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { AppInterceptor } from './app.interceptor'

import { ProductIndex } from './components/product/Index.component'
import { ProductCreate } from './components/product/Create.component'
import { ProductDetail } from './components/product/Detail.component'
import { ProductEdit } from './components/product/Edit.component'
import { ProductDelete } from './components/product/Delete.component'

@NgModule({
  declarations: [
    AppComponent,
    ProductIndex,
    ProductCreate,
    ProductDetail,
    ProductEdit,
    ProductDelete,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AppInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

ProductIndex 组件以表格格式显示产品列表。它在初始化时从 ProductService 获取产品列表,并显示每个产品的 ID、名称和价格,以及用于查看、编辑和删除每个产品的操作按钮。它还包括一个用于导航到产品创建页面的按钮。

产品.服务.ts

import { Component } from '@angular/core'

@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`
})

export class AppComponent { }

ProductService 使用 Angular 的 HttpClient 来执行产品管理的相关 HTTP 请求。它提供了以下方法:

  • get(id?):通过 ID 获取产品详细信息,如果没有提供 ID,则获取产品列表。
  • create(data?): 如果提供了数据,则创建一个新产品,否则获取创建产品页面。
  • edit(id, data?):如果提供了数据,则通过 ID 更新产品,否则获取产品详细信息进行编辑。
  • delete(id, data?):如果提供了数据,则按ID删除产品,否则获取产品详细信息以进行确认。

样式.css

import { Injectable } from '@angular/core';
import { HttpInterceptor } from '@angular/common/http';
import { HttpRequest, HttpErrorResponse } from '@angular/common/http'
import { Observable, throwError } from 'rxjs'
import { HttpHandler } from '@angular/common/http'
import { HttpEvent } from '@angular/common/http'

@Injectable({
  providedIn: 'root'
})

export class AppInterceptor implements HttpInterceptor {

  baseURL = 'http://localhost:8080/api'

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request.clone({
      url: this.baseURL + request.url,
    }))
  }
}

CSS 通过在容器上方添加空间并水平间隔按钮来调整布局。

索引.html

import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { ProductIndex } from './components/product/Index.component'
import { ProductCreate } from './components/product/Create.component'
import { ProductDetail } from './components/product/Detail.component'
import { ProductEdit } from './components/product/Edit.component'
import { ProductDelete } from './components/product/Delete.component'

const routes: Routes = [
  { path: '', redirectTo: 'product', pathMatch: 'full' },
  { path: 'product', component: ProductIndex },
  { path: 'product/create', component: ProductCreate },
  { path: 'product/:id', component: ProductDetail },
  { path: 'product/edit/:id', component: ProductEdit },
  { path: 'product/delete/:id', component: ProductDelete }
]

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

HTML 作为 Angular 应用程序的主要入口点,包括用于样式的 Bootstrap、用于图标的 Font Awesome 和 。作为 Angular 应用程序的占位符。

设置 Go API 项目

npm install -g @angular/cli@18.0.0
ng new view --minimal --routing --style css --no-standalone --ssr=false

创建名为“example”的测试数据库,并执行database.sql文件导入表和数据。

Go API 项目结构

└─ src
   ├─ app
   │  ├─ app-routing.module.ts
   │  ├─ app.component.ts
   │  ├─ app.interceptor.ts
   │  ├─ app.module.ts
   │  └─ components
   │     └─ product
   │        ├─ Create.component.ts
   │        ├─ Delete.component.ts
   │        ├─ Detail.component.ts
   │        ├─ Edit.component.ts
   │        ├─ Index.component.ts
   │        └─ Product.service.ts
   ├─ index.html
   ├─ main.ts
   └─ styles.css

Go API 项目文件

.env

import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'

import { AppModule } from './app/app.module'

platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e))

此文件保存连接数据库的配置详细信息。

数据库Go

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { AppInterceptor } from './app.interceptor'

import { ProductIndex } from './components/product/Index.component'
import { ProductCreate } from './components/product/Create.component'
import { ProductDetail } from './components/product/Detail.component'
import { ProductEdit } from './components/product/Edit.component'
import { ProductDelete } from './components/product/Delete.component'

@NgModule({
  declarations: [
    AppComponent,
    ProductIndex,
    ProductCreate,
    ProductDetail,
    ProductEdit,
    ProductDelete,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AppInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

这个 db.go 文件配置与 GORM 的数据库连接。 SetupDatabase函数加载环境变量,构造MySQL连接字符串,并初始化GORM实例,该实例存储在全局DB变量中。

路由器.go

import { Component } from '@angular/core'

@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`
})

export class AppComponent { }

此 router.go 文件使用 Gin 框架为 Go 应用程序设置路由。 SetupRouter 函数使用 CORS 中间件初始化 Gin 路由器以允许所有来源。它定义了在 /api/products 路径下处理产品相关操作的路由,每个路由映射到 ProductController 中的一个方法。最后,它启动了 Gin 服务器。

产品.go

import { Injectable } from '@angular/core';
import { HttpInterceptor } from '@angular/common/http';
import { HttpRequest, HttpErrorResponse } from '@angular/common/http'
import { Observable, throwError } from 'rxjs'
import { HttpHandler } from '@angular/common/http'
import { HttpEvent } from '@angular/common/http'

@Injectable({
  providedIn: 'root'
})

export class AppInterceptor implements HttpInterceptor {

  baseURL = 'http://localhost:8080/api'

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request.clone({
      url: this.baseURL + request.url,
    }))
  }
}

此product.go 文件定义了与 GORM 一起使用的产品模型。它指定了一个具有三个字段的 Product 结构体:Id(自动递增主键)、Name、Price。

产品控制器.go

import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { ProductIndex } from './components/product/Index.component'
import { ProductCreate } from './components/product/Create.component'
import { ProductDetail } from './components/product/Detail.component'
import { ProductEdit } from './components/product/Edit.component'
import { ProductDelete } from './components/product/Delete.component'

const routes: Routes = [
  { path: '', redirectTo: 'product', pathMatch: 'full' },
  { path: 'product', component: ProductIndex },
  { path: 'product/create', component: ProductCreate },
  { path: 'product/:id', component: ProductDetail },
  { path: 'product/edit/:id', component: ProductEdit },
  { path: 'product/delete/:id', component: ProductDelete }
]

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

product_controller.go 文件定义了一个 ProductController 结构,其中包含使用 Gin 框架处理 Go 应用程序中产品的 CRUD 操作的方法。

  • Index 检索并返回所有产品的列表。
  • Get 通过 ID 获取并返回单个产品。
  • 创建 从请求正文创建新产品。
  • 更新 使用请求正文中的数据更新现有产品。
  • 删除 按产品 ID 删除产品。

主程序

import { Component } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { ProductService } from './Product.service'

@Component({
  selector: 'product-create',
  template: `
    <div class="container">
      <div class="row">
        <div class="col">
          <form ngNativeValidate method="post" (submit)="create()">
            <div class="row">
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_name">Name</label>
                <input id="product_name" name="name" class="form-control" [(ngModel)]="product.Name" maxlength="50" />
                <span *ngIf="errors.name" class="text-danger">{{errors.name}}</span>
              </div>
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_price">Price</label>
                <input id="product_price" name="price" class="form-control" [(ngModel)]="product.Price" type="number" />
                <span *ngIf="errors.price" class="text-danger">{{errors.price}}</span>
              </div>
              <div class="col-12">
                <a class="btn btn-secondary" routerLink="/product">Cancel</a>
                <button class="btn btn-primary">Submit</button>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>`
})
export class ProductCreate {

  product?: any = {}
  errors?: any = {}
  constructor(private router: Router, private route: ActivatedRoute, private ProductService: ProductService) { }

  create() {
    this.ProductService.create(this.product).subscribe(() => {
      this.router.navigateByUrl('/product')
    }, (e) => {
      alert(e.error)
    })
  }
}

这个 main.go 文件是 Go 应用程序的入口点。它导入配置和路由包,然后调用 config.SetupDatabase() 初始化数据库连接,调用 router.SetupRouter() 设置应用程序的路由。

运行项目

运行 Angular 项目

import { Component } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { ProductService } from './Product.service'

@Component({
  selector: 'product-delete',
  template: `
    <div class="container">
      <div class="row">
        <div class="col">
          <form ngNativeValidate method="post" (submit)="this.delete()">
            <div class="row">
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_id">Id</label>
                <input readonly id="product_id" name="id" class="form-control" value="{{product.Id}}" type="number" required />
              </div>
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_name">Name</label>
                <input readonly id="product_name" name="name" class="form-control" value="{{product.Name}}" maxlength="50" />
              </div>
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_price">Price</label>
                <input readonly id="product_price" name="price" class="form-control" value="{{product.Price}}" type="number" />
              </div>
              <div class="col-12">
                <a class="btn btn-secondary" routerLink="/product">Cancel</a>
                <button class="btn btn-danger">Delete</button>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>`
})
export class ProductDelete {

  product?: any = {}
  constructor(private router: Router, private route: ActivatedRoute, private ProductService: ProductService) { }

  ngOnInit() {
    this.get()
  }

  get() {
    return this.ProductService.delete(this.route.snapshot.params['id']).subscribe(data => {
      this.product = data
    }, e => {
      alert(e.error)
    })
  }

  delete() {
    this.ProductService.delete(this.route.snapshot.params['id'], this.product).subscribe(() => {
      this.router.navigateByUrl('/product')
    }, (e) => {
      alert(e.error)
    })
  }
}

运行 Go API 项目

import { Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ProductService } from './Product.service'

@Component({
  selector: 'product-detail',
  template: `
    <div class="container">
      <div class="row">
        <div class="col">
          <form ngNativeValidate method="post">
            <div class="row">
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_id">Id</label>
                <input readonly id="product_id" name="id" class="form-control" value="{{product.Id}}" type="number" required />
              </div>
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_name">Name</label>
                <input readonly id="product_name" name="name" class="form-control" value="{{product.Name}}" maxlength="50" />
              </div>
              <div class="mb-3 col-md-6 col-lg-4">
                <label class="form-label" for="product_price">Price</label>
                <input readonly id="product_price" name="price" class="form-control" value="{{product.Price}}" type="number" />
              </div>
              <div class="col-12">
                <a class="btn btn-secondary" routerLink="/product">Back</a>
                <a class="btn btn-primary" routerLink="/product/edit/{{product.Id}}">Edit</a>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>`
})
export class ProductDetail {

  product?: any = {}
  constructor(private route: ActivatedRoute, private ProductService: ProductService) { }

  ngOnInit() {
    this.get()
  }

  get() {
    return this.ProductService.get(this.route.snapshot.params['id']).subscribe(data => {
      this.product = data
    }, e => {
      alert(e.error)
    })
  }
}

打开网络浏览器并转到http://localhost:4200

你会发现这个产品列表页面。

Building an Angular CRUD App with a Go API

测试

点击“查看”按钮即可查看商品详情页面。

Building an Angular CRUD App with a Go API

点击“编辑”按钮可以修改产品并更新其详细信息。

Building an Angular CRUD App with a Go API

点击“提交”按钮保存更新的产品详细信息。

Building an Angular CRUD App with a Go API

点击“创建”按钮添加新产品并输入其详细信息。

Building an Angular CRUD App with a Go API

点击“提交”按钮保存新产品。

Building an Angular CRUD App with a Go API

点击“删除”按钮即可删除之前创建的产品。

Building an Angular CRUD App with a Go API

点击“删除”按钮确认删除该产品。

Building an Angular CRUD App with a Go API

结论

总之,我们学习了如何创建一个包含组件、视图和路由的基本 Angular 项目,同时使用 Gin 框架作为后端设置 API。通过利用 GORM 进行数据库操作,我们成功构建了一个动态前端,该前端与强大而高效的后端无缝集成。这种组合为开发现代全栈 Web 应用程序奠定了坚实的基础。

源代码:https://github.com/stackpuz/Example-CRUD-Angular-18-Go

在几分钟内创建一个 Angular CRUD 应用程序:https://stackpuz.com

以上是使用 Go API 构建 Angular CRUD 应用程序的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn