探索前沿技术
      展示技术风采

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分

概要
在本系列的第3部分中,我们为Angular应用程序创建了脚手架,并将我们的数据显示在第一个简单的表格中。

系列章节
第1部分:简介和初始设置:Maven,Spring和JPA /后端(数据库)
第2部分:中间层:使用REST服务公开我们的数据
第3部分:前端 – 初步实施
第4部分:前端 – 网格功能和CRUD(创建,读取,更新和删除)

为了使我们的数据有用,我们需要将其提供给用户。 到目前为止,我们已经通过前一部分的REST服务公开了我们的数据,但是现在让我们的用户可以在浏览器中使用它。

我们将运行Angular应用程序。 启动Angular应用程序的最快方法之一是使用Angular CLI,我们将在这里使用它。

使用Angular CLI进行脚手架
首先,让我们安装Angular CLI。 在我们的例子中,我们将在全球范围内安装它,因为它更容易以这种方式使用,但如果您愿意,可以在本地安装(即本地项目)。

npm install -g @angular/cli

接下来,我们将在项目的根目录中创建一个新的Angular应用程序:

ng new frontend

Angular CLI将创建一个已经准备就绪的脚手架项目 – 我们可以按如下方式测试它:

cd frontend
ng serve

您现在可以导航到http://localhost:4200/并查看脚手架的结果:

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分

然后
您可以通过多种方式构建整个应用程序 – 在本系列中,我们将保持前端和后端在结构和执行方面分开,至少在开发模式下是这样。

这样做可以使前端开发更容易,并允许我们将两层(前端和中间)分开。 我们将在本系列的后面部分介绍应用程序打包(到单个可部署工件)。

我们将在开发模式下单独运行服务器和前端代码:

// server
mvn spring-boot:run

// in a separate terminal/window, serve the front end code
ng serve

首先调用服务器
我们将在前端使用一个简单的应用程序架构:

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分

作为第一次通过,让我们尝试从服务器检索所有奥运数据。 为了做到这一点,我们将把我们的前端应用程序分解为更多的包:一个用于我们的模型,另一个用于我们的服务。

模型类非常简单,几乎是Java对应的镜像:

import {Country} from './country.model';
import {Result} from './result.model';

export class Athlete {
    id: number;
    name: string;
    country: Country;
    results: Result[];
}

我们还将创建一个与我们的REST端点交互的AthleteService:

import {Injectable} from '@angular/core';
import {Athlete} from '../model/athlete.model';
import {Http, Response} from '@angular/http';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';
import {Observable} from 'rxjs/Observable';

@Injectable()
export class AthleteService {

    private apiUrl = 'http://localhost:8080/athletes';

    constructor(private http: Http) {
    }

    findAll(): Observable<Athlete[]> {
        return this.http.get(this.apiUrl)
            .map((res: Response) => res.json())
            .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
    }
}

这里有一点点 – 我们正在创建一个可以利用Angular的Http服务的服务。 为了访问它,我们使用Angular Dependency Injection工具,所以我们需要做的就是在构造函数中指定它。

在findAll方法中,我们提供了一个Observable,它将调用我们的REST端点,并在检索时将它映射到我们在上面创建的模型类。 我们实际上从这里没有太多代码行获得了很多功能,这很棒。

到目前为止一切顺利 – 让我们将此服务插入到我们的应用程序中:

export class AppComponent implements OnInit {
    private athletes: Athlete[];

    constructor(private athleteService: AthleteService) {
    }

    ngOnInit() {
        this.athleteService.findAll().subscribe(
            athletes => {
                this.athletes = athletes
            },
            error => {
                console.log(error);
            }
        )
    }
}

我们将创建一个快速模板来输出结果:

<div *ngFor=”let athlete of athletes”> <span>{{athlete.id}}</span> <span>{{athlete.name}}</span> <span>{{athlete?.country?.name}}</span> <span>{{athlete?.results?.length}}</span> </div>

app.component.ts

import {Component, OnInit} from '@angular/core';
import {AthleteService} from './services/athlete.service';
import {Athlete} from './model/athlete.model';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    private athletes: Athlete[];

    constructor(private athleteService: AthleteService) {
    }

    ngOnInit() {
        this.athleteService.findAll().subscribe(
            athletes => {
                this.athletes = athletes
            },
            error => {
                console.log(error);
            }
        )
    }
}

app.component.html

<div *ngFor="let athlete of athletes">
    <span>{{athlete.id}}</span>
    <span>{{athlete.name}}</span>
    <span>{{athlete?.country?.name}}</span>
    <span>{{athlete?.results?.length}}</span>
</div>

athlete.service.ts

import {Injectable} from '@angular/core';
import {Athlete} from '../model/athlete.model';
import {Http, Response} from '@angular/http';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';
import {Observable} from 'rxjs/Observable';

@Injectable()
export class AthleteService {

    private apiUrl = 'http://localhost:8090/athletes';

    constructor(private http: Http) {
    }

    findAll(): Observable<Athlete[]> {
        return this.http.get(this.apiUrl)
            .map((res: Response) => res.json())
            .catch((error: any) => Observable.throw(error.json().error || 'Server error'));
    }
}

athlete.model.ts

import {Country} from './country.model';
import {Result} from './result.model';

export class Athlete {
    id: number;
    name: string;
    country: Country;
    results: Result[];
}

country.model.ts

export class Country {
    id: number;
    name: string;
}

result.model.ts

import {Sport} from './sport.model';

export class Result {
    id: number;
    age: number;
    year: number;
    date: string;
    gold: number;
    silver: number;
    bronze: number;
    sport: Sport;
}

sport.model.ts

export class Sport {
    id: number;
    name: string;
}

好的,太好了 – 我们现在应该好好走吧? 不幸的是 – 如果我们同时运行前端和后端,我们将收到以下错误:

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分

这里的问题是我们的Angular应用程序在localhost:4200中运行,但我们的后端应用程序在localhost:8080上运行。 默认情况下,浏览器会因为存在恶意间接风险而阻止此操作 – 您可以在此处阅读有关CORS的更多信息,但是现在我们可以轻松解决此问题。

让我们回到我们的AthleteController.java控制器并启用CORS:

@CrossOrigin(origins = "http://localhost:4200")
@RestController
public class AthleteController {
... rest of the class

有了这一行,我们很高兴。 请注意,在实际应用程序中,您可能只希望为本地开发启用CORS(可能使用Spring配置文件)。 您还希望能够通过使用属性来外部化您运行的端口。

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/11 下午7:27
 * @since 1.0.0
 */
@CrossOrigin(origins = "http://localhost:4200")
@RestController
public class AthleteController {


    private final AthleteRepository athleteRepository;

    @Autowired
    public AthleteController(AthleteRepository athleteRepository){
        this.athleteRepository = athleteRepository;
    }

    @GetMapping("/athletes")
    public Iterable<Athlete> getAthletes() {
        return athleteRepository.findAll();
    }

    @GetMapping("/athlete")
    public Optional<Athlete> getAthlete(@RequestParam(value = "id") Long athleteId) {
        return athleteRepository.findById(athleteId);
    }

    @PostMapping("/saveAthlete")
    public Athlete saveAthlete(@RequestBody Athlete athlete) {
        return athleteRepository.save(athlete);
    }

    @PostMapping("/deleteAthlete")
    public void deleteAthlete(@RequestBody Long athleteId) {
        athleteRepository.deleteById(athleteId);
    }

}

好的,让我们再试一次 – 让我们启动我们的应用程序并检查结果:

// server
mvn spring-boot:run

// in a separate terminal/window, serve the front end code
ng serve

一旦两者都启动,您可以导航到localhost:4200。 你应该看到这样的东西:

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分

迄今为止取得了很好的进展 – 我们现在知道我们可以成功调用后端!

ag-grid中文实战
我们终于可以开始将我们的数据挂钩到ag-Grid了!

首先,让我们安装ag-Grid依赖项 – 因为我们将使用ag-Grid提供的一些Enterprise功能,我们将安装ag-grid和ag-grid-enterprise依赖项。

如果您没有使用任何Enterprise功能,则只需安装ag-grid依赖项。

npm install --save ag-grid ag-grid-enterprise ag-grid-angular

ag-grid-angular允许我们与网格对话并提供我们想要的丰富的Angular功能。

我们还需要让Angular CLI了解我们想要使用的样式。 在我们的演示中,我们将使用Fresh主题,但还有其他可用 – 请参阅主题文档以获取更多详细信息。

为了让CLI了解我们想要添加的样式,我们需要将它们添加到.angular-cli.json文件中。 查找样式部分并添加以下CSS条目:

"styles": [
    "styles.css",
    "../node_modules/ag-grid/dist/styles/ag-grid.css",
    "../node_modules/ag-grid/dist/styles/ag-theme-balham.css"
],

styles.css是由Angular CLI生成的样式文件 – 您可以保留它或将其删除。 我们不会在这里的演示中使用它。

接下来,我们需要将AgGridModule添加到我们的应用程序中。 我们通过将其添加到app.module.ts来完成此操作:

... other imports
import {AgGridModule} from 'ag-grid-angular';

@NgModule({
    declarations: [
    AppComponent
    ],
    imports: [
        BrowserModule,
        HttpModule,
        AgGridModule
    ...rest of module

我们现在已经设置了ag-Grid依赖项 – 我们下一步是实际使用ag-Grid来显示一些数据。

我们的网格组件
让我们创建一个新组件,负责在ag-Grid中显示我们的数据:

ng generate component Grid

这将为我们创建一个新的Angular组件,并自动将其注册到我们的Angular模块中。

installing component
create src/app/grid/grid.component.css
create src/app/grid/grid.component.html
create src/app/grid/grid.component.spec.ts
create src/app/grid/grid.component.ts
update src/app/app.module.ts

让我们打开我们的新组件并像以前一样注入我们的AthleteService。 AthleteService将负责向网格提供数据。 之后,它也将负责更新和删除。

我们还需要两个属性:一个用于行数据,一个用于列定义 – 网格至少需要Grid中需要的列,以及要显示的数据。

最后,我们将从Grid接入gridReady事件。 我们这样做有两个原因:首先,访问GridApi和ColumnApi,然后在初始化时自动调整列的大小。

export class GridComponent implements OnInit {
    // row data and column definitions
    private rowData: Athlete[];
    private columnDefs: ColDef[];

    // gridApi and columnApi
    private api: GridApi;
    private columnApi: ColumnApi;

    // inject the athleteService
    constructor(private athleteService: AthleteService) {
        this.columnDefs = this.createColumnDefs();
    }

    // on init, subscribe to the athelete data
    ngOnInit() {
        this.athleteService.findAll().subscribe(
            athletes => {
                this.rowData = athletes
            },
            error => {
                console.log(error);
            }
        )
    }

    // one grid initialisation, grap the APIs and auto resize the columns to fit the available space
    onGridReady(params): void {
        this.api = params.api;
        this.columnApi = params.columnApi;

        this.api.sizeColumnsToFit();
    }

    // create some simple column definitions
    private createColumnDefs() {
        return [
            {field: 'id'},
            {field: 'name'},
            {field: 'country'},
            {field: 'results'}
        ]
    }
}

我们的视图模板如下所示:

<ag-grid-angular style="width: 100%; height: 800px;"
    class="ag-theme-balham"
    (gridReady)="onGridReady($event)"
    [columnDefs]="columnDefs"
    [rowData]="rowData">
</ag-grid-angular>

请注意,这是我们绑定到行数据和列定义以及挂接到gridReady事件的位置。 还有其他方法可以做到这一点,但从Angular的角度来看,这更加清晰,更加惯用。

由于我们现在在Grid Component中使用AthleteService,我们可以从app.component.ts中删除它。

最后,我们可以将我们的组件挂钩到app.component.html:

<app-grid></app-grid>

有了这个,我们现在可以运行我们的应用程序……但我们看不到我们希望的内容:

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分

原因很简单 – Country和Results都是复杂的数据。 与其他数据不同,它们没有简单的键值关系。

我们可以通过使用Value Getter轻松解决这个问题,它将复杂的原始数据转换为更友好的显示内容:

private createColumnDefs() {
    return [
        {field: 'id'},
        {field: 'name'},
        {field: 'country', valueGetter: (params) => params.data.country.name},
        {field: 'results', valueGetter: (params) => params.data.results.length}
    ]
}

这里将为country和results的每一行调用valueGetter回调,其中我们分别返回国家名称和结果长度。

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分

app.module.ts

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {HttpModule} from '@angular/http';

import {AgGridModule} from 'ag-grid-angular';

import {AppComponent} from './app.component';
import {AthleteService} from './services/athlete.service';
import {GridComponent} from './grid/grid.component';

@NgModule({
    declarations: [
        AppComponent,
        GridComponent,
    ],
    imports: [
        BrowserModule,
        HttpModule,
        AgGridModule.withComponents([])
    ],
    providers: [AthleteService],
    bootstrap: [AppComponent]
})
export class AppModule {
}

app.component.ts

import {Component} from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
}

app.component.html

<app-grid></app-grid>

grid.component.html

<ag-grid-angular style="width: 100%; height: 800px;"
                 class="ag-theme-balham"
                 (gridReady)="onGridReady($event)"
                 [columnDefs]="columnDefs"
                 [rowData]="rowData">
</ag-grid-angular>

grid.component.ts

<ag-grid-angular style="width: 100%; height: 800px;"
                 class="ag-theme-balham"
                 (gridReady)="onGridReady($event)"
                 [columnDefs]="columnDefs"
                 [rowData]="rowData">
</ag-grid-angular>

概要
这似乎是一项相当大的工作,但值得注意的是,我们只需导入一个模块,然后在单个组件中引用网格以启动并运行网格。

通过添加一些简单属性,我们可以启用过滤,排序等功能。 我们也可以开始提供其余的CRUD操作(创建,更新和删除)。

我们将在本系列的下一部分中详细介绍所有这些内容。

下次见springboot实战开发

×

感谢您的支持,我们会一直保持!

扫码支持
请土豪扫码随意打赏

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

赞(0) 打赏
未经允许不得转载:醉探索戈壁 » SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第3部分
分享到: 更多 (0)
标签:

给戈壁浇点水

支付宝扫一扫打赏

微信扫一扫打赏