Building a Web Application with Spring Boot and Angular – 用Spring Boot和Angular构建Web应用

最后修改: 2019年 3月 11日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Spring Boot and Angular form a powerful tandem that works great for developing web applications with a minimal footprint.

Spring BootAngular形成一个强大的串联,对于开发占用空间最小的Web应用程序非常有效。

In this tutorial, we’ll use Spring Boot for implementing a RESTful backend, and Angular for creating a JavaScript-based frontend.

在本教程中,我们将使用Spring Boot来实现RESTful后端,以及Angular来创建基于JavaScript的前端。

2. The Spring Boot Application

2.Spring Boot应用程序

Our demo web application’s functionality will be pretty simplistic indeed. It’ll be narrowed to fetching and displaying a List of JPA entities from an in-memory H2 database, and persisting new ones through a plain HTML form.

我们的演示Web应用程序的功能将非常简单。它将被缩小到从内存H2数据库中获取和显示JPA实体的List,并通过普通的HTML表单持久化新实体。

2.1. The Maven Dependencies

2.1.Maven的依赖性

Here are our Spring Boot project’s dependencies:

下面是我们的Spring Boot项目的依赖性。

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId> 
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-jpa</artifactId> 
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Note that we included spring-boot-starter-web because we’ll use it for creating the REST service, and spring-boot-starter-jpa for implementing the persistence layer.

请注意,我们包括spring-boot-starter-web,因为我们将用它来创建REST服务,而spring-boot-starter-jpa用于实现持久层。

The H2 database version is also managed by the Spring Boot parent.

H2数据库版本也是由Spring Boot父代管理。

2.2. The JPA Entity Class

2.2.JPA实体类

To quickly prototype our application’s domain layer, let’s define a simple JPA entity class, which will be responsible for modelling users:

为了快速建立我们应用程序领域层的原型,让我们定义一个简单的JPA实体类,它将负责对用户进行建模。

@Entity
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private final String name;
    private final String email;
    
    // standard constructors / setters / getters / toString
}

2.3. The UserRepository Interface

2.3.用户存储库接口

Since we’ll need basic CRUD functionality on the User entities, we must also define a UserRepository interface:

由于我们需要在User实体上实现基本的CRUD功能,我们还必须定义一个UserRepository接口。

@Repository
public interface UserRepository extends CrudRepository<User, Long>{}

2.4. The REST Controller

2.4.REST控制器

Now let’s implement the REST API. In this case, it’s just a simple REST controller:

现在让我们来实现REST API。在这种情况下,它只是一个简单的REST控制器。

@RestController
@CrossOrigin(origins = "http://localhost:4200")
public class UserController {

    // standard constructors
    
    private final UserRepository userRepository;

    @GetMapping("/users")
    public List<User> getUsers() {
        return (List<User>) userRepository.findAll();
    }

    @PostMapping("/users")
    void addUser(@RequestBody User user) {
        userRepository.save(user);
    }
}

There’s nothing inherently complex in the definition of the UserController class.

UserController类的定义中,并没有什么内在的复杂性。

Of course, the implementation detail worth noting here is the use of the @CrossOrigin annotation. As the name implies, the annotation enables Cross-Origin Resource Sharing (CORS) on the server.

当然,这里值得注意的实现细节是使用@CrossOrigin 注解。顾名思义,该注解在服务器上启用了跨源资源共享(CORS)。

This step isn’t always necessary, but since we’re deploying our Angular frontend to http://localhost:4200, and our Boot backend to http://localhost:8080, the browser would otherwise deny requests from one to the other.

这一步并不总是必要的,但是由于我们将 Angular 前端部署到 http://localhost:4200,而将 Boot 后端部署到 http://localhost:8080否则浏览器将拒绝从一个到另一个的请求。

Regarding the controller methods, getUser() fetches all the User entities from the database. Similarly, the addUser() method persists a new entity in the database, which is passed in the request body.

关于控制器方法,getUser()从数据库中获取了所有的User实体。同样,addUser()方法在数据库中持久化一个新的实体,该实体在request body中传递。

To keep things simple, we deliberately left out the controller implementation triggering Spring Boot validation before persisting an entity. In production, however, we can’t trust user input alone, so server-side validation should be a mandatory feature.

为了保持简单,我们特意忽略了在持久化实体之前触发Spring Boot验证的控制器实现。然而,在生产中,我们不能只相信用户的输入,所以服务器端验证应该是一个强制性的功能。

2.5. Bootstrapping the Spring Boot Application

2.5.引导Spring Boot应用程序

Finally, let’s create a standard Spring Boot bootstrapping class, and populate the database with a few User entities:

最后,让我们创建一个标准的Spring Boot引导类,并用一些User实体来填充数据库。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CommandLineRunner init(UserRepository userRepository) {
        return args -> {
            Stream.of("John", "Julie", "Jennifer", "Helen", "Rachel").forEach(name -> {
                User user = new User(name, name.toLowerCase() + "@domain.com");
                userRepository.save(user);
            });
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

Now let’s run the application. As expected, we should see a list of User entities printed out to the console on startup:

现在让我们运行该应用程序。正如预期的那样,我们应该在启动时看到一个User实体的列表被打印到控制台。

User{id=1, name=John, email=john@domain.com}
User{id=2, name=Julie, email=julie@domain.com}
User{id=3, name=Jennifer, email=jennifer@domain.com}
User{id=4, name=Helen, email=helen@domain.com}
User{id=5, name=Rachel, email=rachel@domain.com}

3. The Angular Application

3.Angular应用程序

With our demo Spring Boot application up and running, we can now create a simple Angular application capable of consuming the REST controller API.

随着我们的演示Spring Boot应用程序的运行,我们现在可以创建一个简单的Angular应用程序,能够消费REST控制器API。

3.1. Angular CLI Installation

3.1.Angular CLI安装

We’ll use Angular CLI, a powerful command-line utility, to create our Angular application.

我们将使用Angular CLI,一个强大的命令行工具,来创建我们的Angular应用程序。

Angular CLI is an extremely valuable tool since it allows us to create an entire Angular project from scratch, generating components, services, classes, and interfaces with just a few commands.

Angular CLI是一个非常有价值的工具,因为它允许我们从头开始创建整个Angular项目,只需几个命令就能生成组件、服务、类和接口

Once we’ve installed npm (Node Package Manager), we’ll open a command console and type the command:

一旦我们安装了npm(Node Package Manager),我们将打开一个命令控制台并输入命令。

npm install -g @angular/cli@1.7.4

That’s it. The above command will install the latest version of Angular CLI.

就这样了。上述命令将安装最新版本的Angular CLI。

3.2. Project Scaffolding With Angular CLI

3.2.使用Angular CLI的项目脚手架

We can generate our Angular application structure from the ground up, but honestly, this is an error-prone and time-consuming task that we should avoid in all cases.

我们可以从头开始生成我们的Angular应用结构,但说实话,这是一项容易出错且耗时的任务,我们应该在任何情况下都避免。

Instead, we’ll let Angular CLI do the hard work for us. So we can open a command console, then navigate to the folder where we want our application to be created, and type the command:

相反,我们将让Angular CLI为我们做这些艰苦的工作。所以我们可以打开一个命令控制台,然后导航到我们想要创建应用程序的文件夹,并输入命令。

ng new angularclient

The new command will generate the entire application structure within the angularclient directory.

new命令将在angularclient目录下生成整个应用程序结构。

3.3. The Angular Application’s Entry Point

3.3.Angular应用程序的进入点

If we look inside the angularclient folder, we’ll see that Angular CLI has effectively created an entire project for us.

如果我们看一下angularclient文件夹里面,我们会看到Angular CLI已经有效地为我们创建了一个完整的项目。

Angular’s application files use TypeScript, a typed superset of JavaScript that compiles to plain JavaScript. However, the entry point of any Angular application is a plain old index.html file.

Angular的应用程序文件使用TypeScript一种类型化的JavaScript超集,可编译为普通的JavaScript。然而,任何Angular应用程序的入口点都是一个普通的index.html文件。

Let’s edit this file:

让我们来编辑这个文件。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Spring Boot - Angular Application</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="stylesheet" 
    href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" 
    integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
    crossorigin="anonymous">
</head>
<body>
  <app-root></app-root>
</body>
</html>

As we can see above, we included Bootstrap 4 so we can give our application UI components a more fancy look. Of course, it’s possible to pick up another UI kit from the bunch available out there.

正如我们在上面看到的,我们包含了Bootstrap 4,因此我们可以给我们的应用程序的UI组件一个更花哨的外观。当然,我们也可以从现有的一堆UI工具包中挑选出另一个。

Please notice the custom <app-root></app-root> tags inside the <body> section. At first glance, they look rather weird, as <app-root> is not a standard HTML 5 element.

请注意<app-root></app-root>标签在<body>部分内的自定义标签。乍一看,它们看起来相当奇怪,因为<app-root>不是一个标准的HTML 5元素。

We’ll keep them there, as <app-root> is the root selector that Angular uses for rendering the application’s root component.

我们会把它们放在那里,因为<app-root>是Angular用来渲染应用程序根组件的根选择器

3.4. The app.component.ts Root Component

3.4.app.component.ts 根部组件

To better understand how Angular binds an HTML template to a component, let’s go to the src/app directory and edit the app.component.ts TypeScript file, the root component:

为了更好地理解Angular如何将HTML模板绑定到组件上,让我们进入src/app目录,编辑app.component.ts TypeScript文件,即根组件。

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

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

  title: string;

  constructor() {
    this.title = 'Spring Boot - Angular Application';
  }
}

For obvious reasons, we won’t dive deep into learning TypeScript. Even so, let’s note that the file defines an AppComponent class, which declares a field title of type string (lower-cased). Definitively, it’s typed JavaScript.

由于明显的原因,我们不会深入学习TypeScript。即便如此,让我们注意到该文件定义了一个AppComponent类,它声明了一个title类型的string(小写)字段。可以肯定的是,它是类型化的JavaScript。

Additionally, the constructor initializes the field with a string value, which is pretty similar to what we do in Java.

此外,构造函数用一个字符串值初始化该字段,这与我们在Java中的做法非常相似。

The most relevant part is the @Component metadata marker or decorator, which defines three elements:

最相关的部分是@Component元数据标记或装饰器,它定义了三个元素。

  1. selector – the HTML selector used to bind the component to the HTML template file
  2. templateUrl – the HTML template file associated with the component
  3. styleUrls – one or more CSS files associated with the component

As expected, we can use the app.component.html and app.component.css files to define the HTML template and the CSS styles of the root component.

正如预期的那样,我们可以使用app.component.htmlapp.component.css文件来定义HTML模板和根组件的CSS样式。

Finally, the selector element binds the whole component to the <app-root> selector included in the index.html file.

最后,selector元素将整个组件与<app-root>选择器绑定在index.html文件中。

3.5. The app.component.html File

3.5.app.component.html文件

Since the app.component.html file allows us to define the root component’s HTML template, the AppComponent class, we’ll use it for creating a basic navigation bar with two buttons.

由于app.component.html文件允许我们定义根组件的HTML模板,AppComponent类,我们将用它来创建一个有两个按钮的基本导航条。

If we click the first button, Angular will display a table containing the list of User entities stored in the database. Similarly, if we click the second one, it will render an HTML form, which we can use for adding new entities to the database:

如果我们点击第一个按钮,Angular将显示一个包含存储在数据库中的User实体列表的表格。同样地,如果我们点击第二个按钮,它将呈现一个HTML表单,我们可以用它来向数据库添加新的实体。

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <div class="card bg-dark my-5">
        <div class="card-body">
          <h2 class="card-title text-center text-white py-3">{{ title }}</h2>
          <ul class="text-center list-inline py-3">
            <li class="list-inline-item">
              <a routerLink="/users" class="btn btn-info">List Users</a>
                </li>
            <li class="list-inline-item">
              <a routerLink="/adduser" class="btn btn-info">Add User</a>
                </li>
          </ul>
        </div>
      </div>
      <router-outlet></router-outlet>
    </div>
  </div>
</div>

The bulk of the file is standard HTML, with a few caveats worth noting.

文件的大部分是标准的HTML,但有一些值得注意的注意事项。

The first one is the {{ title }} expression. The double curly braces {{ variable-name }} is the placeholder that Angular uses for performing variable interpolation.

第一个是{{标题}}表达式。双层大括号{{变量名称}}是Angular用于执行变量插值的占位符

Let’s keep in mind that the AppComponent class initialized the title field with the value Spring Boot – Angular Application. Thus, Angular will display the value of this field in the template. Likewise, changing the value in the constructor will be reflected in the template.

让我们记住,AppComponent类将title字段初始化为Spring Boot – Angular Application值。因此,Angular将在模板中显示这个字段的值。同样,改变构造函数中的值也会反映在模板中。

The second thing to note is the routerLink attribute.

第二件要注意的事是routerLink属性

Angular uses this attribute for routing requests through its routing module (more on this later). For now, it’s sufficient to know that the module will dispatch a request to the /users path to a specific component and a request to /adduser to another component.

Angular通过其路由模块使用该属性来路由请求(稍后将详细介绍)。现在,我们只需知道该模块将把对/users路径的请求分配给一个特定的组件,而把对/adduser的请求分配给另一个组件。

In each case, the HTML template associated with the matching component will be rendered within the <router-outlet></router-outlet> placeholder.

在每种情况下,与匹配组件相关的HTML模板将在<router-outlet></router-outlet>占位符中呈现。

3.6. The User Class

3.6.用户

Since our Angular application will fetch from and persist User entities in the database, let’s implement a simple domain model with TypeScript.

由于我们的Angular应用程序将从数据库中获取并持久化User实体,让我们用TypeScript实现一个简单的领域模型。

Let’s open a terminal console and create a model directory:

让我们打开一个终端控制台,创建一个model目录。

ng generate class user

Angular CLI will generate an empty User class, so let’s populate it with a few fields:

Angular CLI将生成一个空的User类,所以让我们用一些字段来填充它。

export class User {
    id: string;
    name: string;
    email: string;
}

3.7. The UserService Service

3.7.UserService服务

With our client-side domain User class already set, we can now implement a service class that performs GET and POST requests to the http://localhost:8080/users endpoint.

由于我们的客户端域User类已经设置好了,我们现在可以实现一个服务类,它可以向http://localhost:8080/users端点执行GET和POST请求。

This will allow us to encapsulate access to the REST controller in a single class, which we can reuse throughout the entire application.

这将使我们能够在一个单一的类中封装对REST控制器的访问,我们可以在整个应用程序中重复使用该类

Let’s open a console terminal, then create a service directory, and within that directory, issue the following command:

让我们打开一个控制台终端,然后创建一个service目录,并在该目录中发出以下命令。

ng generate service user-service

Now let’s open the user.service.ts file that Angular CLI just created and refactor it:

现在让我们打开Angular CLI刚刚创建的user.service.ts文件并重构它。

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { User } from '../model/user';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class UserService {

  private usersUrl: string;

  constructor(private http: HttpClient) {
    this.usersUrl = 'http://localhost:8080/users';
  }

  public findAll(): Observable<User[]> {
    return this.http.get<User[]>(this.usersUrl);
  }

  public save(user: User) {
    return this.http.post<User>(this.usersUrl, user);
  }
}

We don’t need a solid background on TypeScript to understand how the UserService class works. Simply put, it encapsulates within a reusable component all the functionality required to consume the REST controller API that we implemented before in Spring Boot.

我们不需要坚实的TypeScript背景来理解UserService类是如何工作的。简单地说,它在一个可重用的组件中封装了我们之前在Spring Boot中实现的REST控制器API所需的所有功能

The findAll() method performs a GET HTTP request to the http://localhost:8080/users endpoint via Angular’s HttpClient. The method returns an Observable instance that holds an array of User objects.

findAll()方法通过Angular的HttpClient端点执行 GET HTTP 请求。该方法返回一个Observable实例,该实例持有一个 User对象的数组。

Likewise, the save() method performs a POST HTTP request to the http://localhost:8080/users endpoint.

同样,save()方法向http://localhost:8080/users端点执行一个POST HTTP请求。

By specifying the type User in the HttpClient‘s request methods, we can consume back-end responses in an easier and more effective way.

通过在HttpClient的请求方法中指定User类型,我们可以以一种更简单、更有效的方式消费后端响应。

Lastly, let’s note the use of the @Injectable() metadata marker. This signals that the service should be created and injected via Angular’s dependency injectors.

最后,让我们注意@Injectable()元数据标记的使用。这表明该服务应通过Angular的依赖注入器来创建和注入

3.8. The UserListComponent Component

3.8.UserListComponent组件

In this case, the UserService class is the thin middle-tier between the REST service and the application’s presentation layer. Therefore, we need to define a component responsible for rendering the list of User entities persisted in the database.

在这种情况下,UserService类是REST服务和应用程序的表现层之间的中间层。因此,我们需要定义一个组件,负责呈现数据库中持久化的User实体的列表。

Let’s open a terminal console, then create a user-list directory, and generate a user list component:

让我们打开一个终端控制台,然后创建一个user-list目录,并生成一个用户列表组件。

ng generate component user-list

Angular CLI will generate an empty component class that implements the ngOnInit interface. The interface declares a hook ngOnInit() method, which Angular calls after it has finished instantiating the implementing class, and also after calling its constructor.

Angular CLI将生成一个空的组件类,它实现了ngOnInit接口。该接口声明了一个钩子ngOnInit()方法,Angular在完成对实现类的实例化后,也在调用其构造函数后调用该方法。

Let’s refactor the class so that it can take a UserService instance in the constructor:

让我们重构这个类,以便它能在构造函数中接受一个UserService实例。

import { Component, OnInit } from '@angular/core';
import { User } from '../model/user';
import { UserService } from '../service/user.service';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {

  users: User[];

  constructor(private userService: UserService) {
  }

  ngOnInit() {
    this.userService.findAll().subscribe(data => {
      this.users = data;
    });
  }
}

The implementation of the UserListComponent class is pretty self-explanatory. It simply uses the UserService’s findAll() method to fetch all the entities persisted in the database and stores them in the users field.

UserListComponent类的实现是不言自明的。它只是使用UserService的findAll()方法来获取数据库中的所有实体,并将它们存储在users字段中。

In addition, we need to edit the component’s HTML file, user-list.component.html, to create the table that displays the list of entities:

此外,我们需要编辑该组件的HTML文件,user-list.component.html,以创建显示实体列表的表格。

<div class="card my-5">
  <div class="card-body">
    <table class="table table-bordered table-striped">
      <thead class="thead-dark">
        <tr>
          <th scope="col">#</th>
          <th scope="col">Name</th>
          <th scope="col">Email</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let user of users">
          <td>{{ user.id }}</td>
          <td>{{ user.name }}</td>
          <td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

We should note the use of the *ngFor directive. The directive is called a repeater, and we can use it for iterating over the contents of a variable and iteratively rendering HTML elements. In this case, we used it for dynamically rendering the table’s rows.

我们应该注意*ngFor指令的使用。该指令被称为repeater,我们可以用它来迭代一个变量的内容并迭代呈现HTML元素。在本例中,我们用它来动态地呈现表格的行。

In addition, we used variable interpolation for showing the id, name, and email of each user.

此外,我们使用变量插值来显示每个用户的id, nameemail

3.9. The UserFormComponent Component

3.9.UserFormComponent 组件

Similarly, we need to create a component that allows us to persist a new User object in the database.

同样地,我们需要创建一个组件,让我们在数据库中持久化一个新的User对象。

Let’s create a user-form directory and type the following:

让我们创建一个user-form目录,并输入以下内容。

ng generate component user-form

Next let’s open the user-form.component.ts file, and add to the UserFormComponent class a method for saving a User object:

接下来让我们打开user-form.component.ts文件,在UserFormComponent类中添加一个方法来保存User对象。

import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from '../service/user.service';
import { User } from '../model/user';

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

  user: User;

  constructor(
    private route: ActivatedRoute, 
      private router: Router, 
        private userService: UserService) {
    this.user = new User();
  }

  onSubmit() {
    this.userService.save(this.user).subscribe(result => this.gotoUserList());
  }

  gotoUserList() {
    this.router.navigate(['/users']);
  }
}

In this case, UserFormComponent also takes a UserService instance in the constructor, which the onSubmit() method uses for saving the supplied User object.

在这种情况下,UserFormComponent在构造函数中也需要一个UserService实例,onSubmit()方法使用它来保存提供的User对象。

Since we need to re-display the updated list of entities once we have persisted a new one, we call the gotoUserList() method after the insertion, which redirects the user to the /users path.

由于我们需要在持久化一个新的实体后重新显示更新的实体列表,我们在插入后调用gotoUserList()方法,将用户重定向到/users路径。

In addition, we need to edit the user-form.component.html file, and create the HTML form for persisting a new user in the database:

此外,我们需要编辑user-form.component.html文件,并创建用于在数据库中持久化一个新用户的HTML表单。

<div class="card my-5">
  <div class="card-body">
    <form (ngSubmit)="onSubmit()" #userForm="ngForm">
      <div class="form-group">
        <label for="name">Name</label>
        <input type="text" [(ngModel)]="user.name" 
          class="form-control" 
          id="name" 
          name="name" 
          placeholder="Enter your name"
          required #name="ngModel">
      </div>
      <div [hidden]="!name.pristine" class="alert alert-danger">Name is required</div>
      <div class="form-group">
        <label for="email">Email</label>
        <input type="text" [(ngModel)]="user.email" 
          class="form-control" 
          id="email" 
          name="email" 
          placeholder="Enter your email address"
          required #email="ngModel">
        <div [hidden]="!email.pristine" class="alert alert-danger">Email is required</div>
      </div>
      <button type="submit" [disabled]="!userForm.form.valid" 
        class="btn btn-info">Submit</button>
    </form>
  </div>
</div>

At a glance, the form looks pretty standard, but it encapsulates a lot of Angular’s functionality behind the scenes.

乍一看,这个表单看起来很标准,但它在幕后封装了很多Angular的功能。

Let’s note the use of the ngSubmit directive, which calls the onSubmit() method when the form is submitted.

让我们注意一下ngSubmit指令的使用,它在表单提交时调用onSubmit()方法

Next we defined the template variable #userForm, so Angular automatically adds an NgForm directive, which allows us to keep track of the form as a whole.

接下来我们定义了模板变量#userForm,所以Angular自动添加了NgForm指令,这让我们能够跟踪整个表单

The NgForm directive holds the controls that we created for the form elements with an ngModel directive and a name attribute. It also monitors their properties, including their state.

NgForm指令持有我们为表单元素创建的控件,这些控件带有ngModel指令和一个name属性。它还监视它们的属性,包括它们的状态。

The ngModel directive gives us two-way data binding functionality between the form controls and the client-side domain model, the User class.

ngModel指令为我们提供了表单控件和客户端域模型(User类)之间的双向数据绑定功能。

This means that data entered in the form input fields will flow to the model, and the other way around. Changes in both elements will be reflected immediately via DOM manipulation.

这意味着,在表单输入字段中输入的数据将流向模型,反之亦然。两个元素的变化将通过DOM操作立即反映出来。

Additionally, ngModel allows us to keep track of the state of each form control, and perform client-side validation by adding different CSS classes and DOM properties to each control.

此外,ngModel允许我们跟踪每个表单控件的状态,并通过向每个控件添加不同的CSS类和DOM属性来执行客户端的验证

In the above HTML file, we used the properties applied to the form controls only to display an alert box when the values in the form have been changed.

在上面的HTML文件中,我们只使用了应用于表单控件的属性,以便在表单中的值被改变时显示一个警告框。

3.10. The app-routing.module.ts File

3.10.app-routing.module.ts 文件

Although the components are functional in isolation, we still need to use a mechanism for calling them when the user clicks the buttons in the navigation bar.

尽管这些组件是独立运作的,但我们仍然需要使用一种机制,在用户点击导航栏中的按钮时调用它们。

This is where the RouterModule comes into play. Let’s open the app-routing.module.ts file and configure the module, so it can dispatch requests to the matching components:

这就是RouterModule开始发挥作用的地方。让我们打开app-routing.module.ts文件并配置该模块,这样它就可以将请求分派给匹配的组件。

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UserListComponent } from './user-list/user-list.component';
import { UserFormComponent } from './user-form/user-form.component';

const routes: Routes = [
  { path: 'users', component: UserListComponent },
  { path: 'adduser', component: UserFormComponent }
];

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

As we can see above, the Routes array instructs the router which component to display when a user clicks a link or specifies a URL into the browser address bar.

正如我们在上面看到的,Routes数组指示当用户点击一个链接或在浏览器地址栏中指定一个URL时,路由器将显示哪个组件。

A route is composed of two parts:

一条路线由两部分组成。

  1. Path –  a string that matches the URL in the browser address bar
  2. Component – the component to create when the route is active (navigated)

If the user clicks the List Users button, which links to the /users path, or enters the URL in the browser address bar, the router will render the UserListComponent component’s template file in the <router-outlet> placeholder.

如果用户点击链接到/users路径的List Users按钮,或者在浏览器地址栏中输入URL,路由器将在<router-outlet>占位符中渲染UserListComponent组件的模板文件。

Likewise, if they click the Add User button, it will render the UserFormComponent component.

同样地,如果他们点击添加用户按钮,它将呈现UserFormComponent组件。

3.11. The app.module.ts File

3.11.app.module.ts文件

Next we need to edit the app.module.ts file, so Angular can import all the required modules, components, and services.

接下来我们需要编辑app.module.ts文件,以便Angular能够导入所有需要的模块、组件和服务。

Additionally, we need to specify which provider we’ll use for creating and injecting the UserService class. Otherwise, Angular won’t be able to inject it into the component classes:

此外,我们需要指定我们将使用哪个提供者来创建和注入UserService类。否则,Angular将无法将其注入到组件类中。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserFormComponent } from './user-form/user-form.component';
import { UserService } from './service/user.service';

@NgModule({
  declarations: [
    AppComponent,
    UserListComponent,
    UserFormComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    FormsModule
  ],
  providers: [UserService],
  bootstrap: [AppComponent]
})
export class AppModule { }

4. Running the Application

4.运行应用程序

Finally, we’re ready to run our application.

最后,我们准备运行我们的应用程序。

To accomplish this, we’ll first run the Spring Boot application, so the REST service is alive and listening for requests.

为了实现这个目标,我们首先要运行Spring Boot应用程序,这样REST服务就会活起来,并倾听请求。

Once the Spring Boot application has been started, we’ll open a command console and type the following command:

一旦Spring Boot应用程序被启动,我们将打开一个命令控制台并输入以下命令。

ng serve --open

This will start Angular’s live development server and also open the browser at http://localhost:4200.

这将启动Angular的实时开发服务器,同时在http://localhost:4200打开浏览器。

We should see the navigation bar with the buttons for listing existing entities and for adding new ones. If we click the first button, we should see below the navigation bar a table with the list of entities persisted in the database:
user-list
Similarly, clicking the second button will display the HTML form for persisting a new entity:

我们应该看到导航条上有列出现有实体和添加新实体的按钮。如果我们点击第一个按钮,我们应该看到导航栏下面有一个表格,上面有数据库中持久存在的实体列表:
user-list
同样地,点击第二个按钮将显示用于持久化一个新实体的HTML表单。

user-form

5. Conclusion

5.结论

In this article, we learned how to build a basic web application with Spring Boot and Angular.

在这篇文章中,我们学习了如何用Spring Boot和Angular构建一个基本的Web应用

As usual, all the code samples shown in this article are available over on GitHub.

像往常一样,本文中显示的所有代码样本都可以在GitHub上找到