Opbygning af en webapplikation med Spring Boot og Angular

1. Oversigt

Spring Boot og Angular udgør en kraftig tandem, der fungerer godt til udvikling af webapplikationer med et minimalt fodaftryk.

I denne vejledning vi bruger Spring Boot til implementering af en RESTful-backend og Angular til oprettelse af en JavaScript-baseret frontend.

2. Spring Boot-applikationen

Vores demo-webapplikations funktionalitet vil faktisk være ret forenklet. Det bliver bare indsnævret til at hente og vise en Liste af JPA-enheder fra en H2-database i hukommelsen og vedvarende nye gennem en almindelig HTML-form.

2.1. Maven-afhængighederne

Her er vores Spring Boot-projekts afhængigheder:

 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 runtime 

Bemærk, at vi inkluderede spring-boot-starter-web fordi vi bruger det til at oprette REST-tjenesten, og spring-boot-starter-jpa til implementering af persistenslaget.

H2-databaseversionen administreres også af Spring Boot-forælderen.

2.2. JPA-enhedsklassen

For hurtigt at prototype vores applikations domænelag, lad os definere en simpel JPA-enhedsklasse, som er ansvarlig for modellering af brugere:

@Entity offentlig klasse bruger {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat lang id; privat endelig Navn på streng; privat endelig streng-mail; // standardkonstruktører / settere / getters / toString} 

2.3. Det UserRepository Interface

Da vi har brug for grundlæggende CRUD-funktionalitet på Bruger enheder, skal vi også definere en UserRepository grænseflade:

@Repository offentlig grænseflade UserRepository udvider CrudRepository {} 

2.4. REST-controlleren

Lad os nu implementere REST API. I dette tilfælde er det bare en simpel REST-controller.

@RestController @CrossOrigin (origins = "// localhost: 4200") offentlig klasse UserController {// standard konstruktører privat endelig UserRepository userRepository; @GetMapping ("/ brugere") offentlig Liste getUsers () {return (Liste) userRepository.findAll (); } @PostMapping ("/ brugere") ugyldig addUser (@RequestBody brugerbruger) {userRepository.save (bruger); }} 

Der er intet iboende komplekst i definitionen af UserController klasse.

Selvfølgelig er den eneste implementeringsdetalje, der er værd at bemærke her brugen af @CrossOrigin kommentar. Som navnet antyder, muliggør annoteringen CORS (Cross-Origin Resource Sharing) på serveren.

Dette trin er ikke altid nødvendigt. Da vi anvender vores kantede frontend til // lokal vært: 4200 og vores Boot backend til // localhost: 8080, ellers nægter browseren anmodninger fra den ene til den anden.

Med hensyn til controllermetoderne, getUser () henter alle de Bruger enheder fra databasen. Tilsvarende er tilføj bruger () metode fortsætter en ny enhed i databasen, som sendes i anmodningsorganet.

For at holde tingene enkle har vi bevidst udeladt af controllerimplementeringen, der udløser Spring Boot-validering, før vi vedvarer en enhed. I produktionen kan vi dog bare ikke stole på brugerinput, så validering på serversiden skal være en obligatorisk funktion.

2.5. Bootstrapping Spring Boot-applikationen

Lad os endelig oprette en standard Spring Boot bootstrapping-klasse og udfylde databasen med nogle få Bruger enheder:

@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 Bruger (navn, navn.toLowerCase () + "@ domæne.com"); brugerRepository.save (bruger);}); userRepository.findAll (). forEach (System.out :: println); }; }}

Lad os nu køre applikationen. Som forventet skal vi se en liste over Bruger enheder, der udskrives til konsollen ved opstart:

Bruger {id = 1, navn = John, [e-mailbeskyttet]} Bruger {id = 2, navn = Julie, [e-mailbeskyttet]} Bruger {id = 3, navn = Jennifer, [e-mailbeskyttet]} Bruger {id = 4 , name = Helen, [email protected]} Bruger {id = 5, name = Rachel, [email protected]}

3. Vinkelapplikationen

Med vores demo Spring Boot-applikation i gang, lad os nu oprette en simpel Angular-applikation, der er i stand til at forbruge REST-controller-API'en.

3.1. Vinklet CLI-installation

Vi bruger Angular CLI, et kraftfuldt kommandolinjeprogram til at oprette vores Angular-applikation.

Angular CLI er et ekstremt værdifuldt værktøj siden det giver os mulighed for at oprette et helt Angular-projekt fra bunden, generere komponenter, tjenester, klasser og grænseflader med blot et par kommandoer.

Når vi har installeret npm (Node Package Manager), åbner vi en kommandokonsol og skriver kommandoen:

npm install -g @ vinkel / [e-mail beskyttet]

Det er det. Ovenstående kommando installerer den nyeste version af Angular CLI.

3.2. Projektstillads med kantet CLI

Faktisk kan vi generere vores kantede applikationsstruktur fra bunden. Men ærligt talt er dette en fejlbehæftet og tidskrævende opgave, som vi i alle tilfælde bør undgå.

I stedet vil vi lade Angular CLI gøre det hårde arbejde for os. Så lad os åbne en kommandokonsol og derefter navigere til den mappe, hvor vi ønsker, at vores applikation skal oprettes, og skriv kommandoen:

ng ny vinkelklient

Det ny kommandoen genererer hele applikationsstrukturen i vinkelklient vejviser.

3.3. Den kantede applikations indgangssted

Hvis vi kigger ind i vinkelklient mappe, ser vi, at Angular CLI effektivt har oprettet et helt projekt for os.

Angular's applikationsfiler bruger TypeScript, et skrevet supersæt af JavaScript, der kompileres til almindeligt JavaScript. Indgangsstedet for enhver vinkelapplikation er dog almindeligt gammelt index.html fil.

Lad os redigere denne fil som følger:

    Spring Boot - Kantet applikation 

Som vi kan se ovenfor inkluderede vi Bootstrap 4, så vi kan give vores applikations-UI-komponenter et mere fancy look. Det er selvfølgelig muligt at hente et andet brugergrænsefladesæt fra den flok, der er tilgængelig derude.

Bemærk venligst skik tags inde i afsnit. Ved første øjekast ser de ret underlige ud som er ikke et standard HTML 5-element.

Lad os holde dem lige der, som er den rodvælger, som Angular bruger til gengivelse af programmets rodkomponent.

3.4. Det app.component.ts Rødkomponent

For bedre at forstå, hvordan Angular binder en HTML-skabelon til en komponent, lad os gå til src / app mappe og redigere app.component.ts TypeScript-fil - rodkomponenten:

importer {Component} fra '@ vinkel / kerne'; @Component ({selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']}) eksportklasse AppComponent {title: string; constructor () {this.title = 'Spring Boot - Angular Application'; }}

Af åbenlyse grunde dykker vi ikke dybt ned i at lære TypeScript. Alligevel lad os bemærke, at filen definerer en AppComponent klasse, der erklærer et felt titel af typen snor (underhus). Definitivt er det skrevet JavaScript.

Derudover initialiserer konstruktøren feltet med en snor værdi, hvilket svarer til det, vi gør i Java.

Den mest relevante del er det @Komponent metadatamarkør eller dekoratør, der definerer tre elementer:

  1. vælger - HTML-vælgeren, der bruges til at binde komponenten til HTML-skabelonfilen
  2. skabelonUrl - HTML-skabelonfilen, der er knyttet til komponenten
  3. styleUrls - en eller flere CSS-filer, der er knyttet til komponenten

Som forventet kan vi bruge app.component.html og app.component.css filer til at definere HTML-skabelonen og CSS-typografierne for rodkomponenten.

Endelig blev vælger element binder hele komponenten til vælger inkluderet i index.html fil.

3.5. Det app.component.html Fil

Siden den app.component.html fil tillader os at definere rodkomponentens HTML-skabelon - det AppComponent klasse - vi bruger den til at oprette en grundlæggende navigationslinje med to knapper.

Hvis vi klikker på den første knap, viser Angular en tabel med listen over Bruger enheder, der er gemt i databasen. Tilsvarende, hvis vi klikker på den anden, gengiver den en HTML-formular, som vi kan bruge til at tilføje nye enheder til databasen:

{{title}}

  • Liste brugere
  • Tilføj bruger

Hovedparten af ​​filen er standard HTML, med et par advarsler, der er værd at bemærke.

Den første er {{title}} udtryk. De dobbelte krøllede seler {{variabel-navn}} er den pladsholder, som Angular bruger til at udføre variabel interpolation.

Lad os huske på, at AppComponent klasse initialiseret titel felt med værdien Spring Boot - Kantet applikation. Således viser Angular værdien af ​​dette felt i skabelonen. Ligeledes vil ændring af værdien i konstruktøren blive afspejlet i skabelonen.

Den anden ting at bemærke er routerLink attribut.

Angular bruger denne attribut til routing af anmodninger gennem sit routing-modul (mere om dette senere). Indtil videre er det tilstrækkeligt at vide, at modulet sender en anmodning til / brugere sti til en bestemt komponent og en anmodning til / adduser til en anden komponent.

I begge tilfælde gengives HTML-skabelonen, der er knyttet til den matchende komponent, inden i pladsholder.

3.6. Det Bruger Klasse

Da vores Angular-applikation henter fra og fortsætter Bruger enheder i databasen, lad os implementere en simpel domænemodel med TypeScript.

Lad os åbne en terminalkonsol og oprette en model vejviser:

ng generer klasse bruger

Vinklet CLI genererer en tom Bruger klasse. Lad os udfylde det med et par felter:

eksportklasse bruger {id: streng; navn: streng; e-mail: streng; }

3.7. Det UserService Service

Med vores klientsides domæne Bruger klasse allerede indstillet, lad os nu implementere en serviceklasse, der udfører GET- og POST-anmodninger til // localhost: 8080 / brugere slutpunkt.

Dette giver os mulighed for at indkapsle adgang til REST-controlleren i en enkelt klasse, som vi kan genbruge gennem hele applikationen.

Lad os åbne en konsolterminal og derefter oprette en service bibliotek, og inden for dette bibliotek, udsted følgende kommando:

ng generer service brugerservice

Lad os nu åbne user.service.ts fil, som Angular CLI netop oprettede og refaktorer den:

importer {Injectable} fra '@ vinkel / kerne'; importer {HttpClient, HttpHeaders} fra '@ vinkel / fælles / http'; importer {User} fra '../model/user'; importer {Observable} fra 'rxjs / Observable'; @Injectable () eksportklasse UserService {private brugereUrl: streng; konstruktør (privat http: HttpClient) {this.usersUrl = '// localhost: 8080 / brugere'; } public findAll (): Observable {return this.http.get (this.usersUrl); } offentlig gem (bruger: bruger) {returner this.http.post (this.usersUrl, bruger); }}

Vi har ikke brug for en solid baggrund på TypeScript for at forstå, hvordan UserService klasse fungerer. Kort sagt, det indkapsles i en genanvendelig komponent al den funktionalitet, der kræves for at forbruge REST-controller-API'en, som vi implementerede før i Spring Boot.

Det findAll () metoden udfører en GET HTTP-anmodning til // localhost: 8080 / brugere slutpunkt via Angular's HttpClient. Metoden returnerer en Observerbar forekomst, der indeholder en række Bruger genstande.

Ligeledes Gemme() metoden udfører en POST HTTP-anmodning til // localhost: 8080 / brugere slutpunkt.

Ved at specificere typen Bruger i HttpClient'S anmodningsmetoder kan vi forbruge back-end-svar på en lettere og mere effektiv måde.

Endelig, lad os bemærk brugen af @Injicerbar () metadatamarkør. Dette signalerer, at tjenesten skal oprettes og injiceres via Angular's afhængighedsinjektorer.

3.8. Det UserListComponent Komponent

I dette tilfælde er UserService klasse er det tynde mellemliggende niveau mellem REST-tjenesten og applikationens præsentationslag. Derfor er vi nødt til at definere en komponent, der er ansvarlig for gengivelse af listen over Bruger enheder fortsatte i databasen.

Lad os åbne en terminalkonsol og derefter oprette en brugerliste mappe og generere en brugerlistekomponent:

ng generer komponent-brugerliste

Vinklet CLI genererer en tom komponentklasse, der implementerer ngOnInit interface. Interfacet erklærer en krog ngOnInit () metode, som Angular kalder, når den er færdig med at instantiere implementeringsklassen, og efter at have ringet til sin konstruktør også.

Lad os omforme klassen, så det kan tage en UserService eksempel i konstruktøren:

importer {Component, OnInit} fra '@ vinkel / kerne'; importer {User} fra '../model/user'; importer {UserService} fra '../service/user.service'; @Component ({selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css']}) eksportklasse UserListComponent implementerer OnInit {brugere: Bruger []; konstruktør (privat userService: UserService) {} ngOnInit () {this.userService.findAll (). abonner (data => {this.users = data;}); }} 

Gennemførelsen af UserListComponent klasse er ret selvforklarende. Det bruger simpelthen UserService's findAll () metode til at hente alle enheder, der er vedvarende i databasen og gemme dem i brugere Mark.

Derudover er vi nødt til at redigere komponentens HTML-fil, user-list.component.html, for at oprette tabellen, der viser listen over enheder:

#NavnE-mail
{{ bruger ID }}{{user.name}}{{user.email}}

Bemærk brugen af * ngFor direktiv. Direktivet kaldes a repeater, og vi kan bruge det til at gentage indholdet af en variabel og gengive iterativt HTML-elementer. I dette tilfælde brugte vi det til dynamisk gengivelse af tabellens rækker.

Derudover brugte vi variabel interpolation til at vise id,navnog e-mail af hver bruger.

3.9. Det UserFormComponent Komponent

På samme måde er vi nødt til at oprette en komponent, der giver os mulighed for at fastholde en ny Bruger objekt i databasen.

Lad os oprette en brugerformular bibliotek og skriv følgende:

ng generer komponent brugerform 

Lad os derefter åbne user-form.component.ts fil og tilføj til UserFormComponent klasse en metode til at gemme en Bruger objekt:

importer {Component} fra '@ vinkel / kerne'; importer {ActivatedRoute, Router} fra '@ vinkel / router'; importer {UserService} fra '../service/user.service'; importer {User} fra '../model/user'; @Component ({selector: 'app-user-form', templateUrl: './user-form.component.html', styleUrls: ['./user-form.component.css']}) eksportklasse UserFormComponent {bruger : Bruger; konstruktør (privat rute: ActivatedRoute, privat router: Router, privat brugerService: UserService) {this.user = ny bruger (); } onSubmit () {this.userService.save (this.user) .subscribe (result => this.gotoUserList ()); } gotoUserList () {this.router.navigate (['/ brugere']); }}

I dette tilfælde, UserFormComponent tager også en UserService eksempel i konstruktøren, som onSubmit () metode bruges til at gemme det leverede Bruger objekt.

Da vi har brug for at vise den opdaterede liste over enheder igen, når vi har opretholdt en ny, kalder vi gotoUserList () metode efter indsættelsen, som omdirigerer brugeren til / brugere sti.

Derudover skal vi redigere bruger-form.komponent.html fil, og opret HTML-formularen til at fastholde en ny bruger i databasen:

 Navn Navn kræves E-mail E-mail er påkrævet Send 

Oversigt ser formularen ret standard ud. Men det indkapsler en masse Angular's funktionalitet bag kulisserne.

Lad os bemærke brugen af det ngSend direktiv, der kalder onSubmit () metode, når formularen indsendes.

Dernæst har vi defineret skabelonvariabel # brugerform, så Angular tilføjer automatisk en NgForm direktiv, som giver os mulighed for at holde styr på formularen som helhed.

Det NgForm Direktivet indeholder de kontroller, vi oprettede for formelementerne, med en ngModel direktiv og en navn attribut og overvåger også deres egenskaber, herunder deres tilstand.

Det ngModel direktivet giver os tovejs dataforbindelsesfunktionalitet mellem formkontrolelementerne og klientsides domænemodel - Bruger klasse.

Det betyder, at data indtastet i formularindtastningsfelterne flyder til modellen - og omvendt. Ændringer i begge elementer reflekteres straks via DOM-manipulation.

Derudover ngModel giver os mulighed for at holde styr på tilstanden for hver formkontrol og udføre klientsides validering ved at tilføje til hver kontrol forskellige CSS-klasser og DOM-egenskaber.

I ovenstående HTML-fil brugte vi kun de egenskaber, der blev anvendt på formularkontrollerne, til at vise en advarselsboks, når værdierne i formularen er ændret.

3.10. Det app-routing.module.ts Fil

Selvom komponenterne er funktionelle isoleret, er vi stadig nødt til at bruge en mekanisme til at ringe til dem, når brugeren klikker på knapperne i navigationslinjen.

Det er her, Routermodul kommer i spil. Så lad os åbne app-routing.module.ts fil og konfigurere modulet, så det kan sende anmodninger til de matchende komponenter:

importer {NgModule} fra '@ vinkel / kerne'; importere {Routes, RouterModule} fra '@ vinkel / router'; importere {UserListComponent} fra './user-list/user-list.component'; importere {UserFormComponent} fra './user-form/user-form.component'; const routes: Routes = [{sti: 'brugere', komponent: UserListComponent}, {sti: 'adduser', komponent: UserFormComponent}]; @NgModule ({importerer: [RouterModule.forRoot (ruter)], eksport: [RouterModule]}) eksportklasse AppRoutingModule {} 

Som vi kan se ovenfor, det Ruter array instruerer routeren, hvilken komponent der skal vises, når en bruger klikker på et link eller angiver en URL i browserens adresselinje.

En rute består af to dele:

  1. Sti - -en snor der matcher URL'en i browserens adresselinje
  2. Komponent - den komponent, der skal oprettes, når ruten er aktiv (navigeret)

Hvis brugeren klikker på Liste brugere knap, der linker til / brugere sti eller indtaster URL'en i browserens adresselinje, vil routeren gengive UserListComponent komponentens skabelonfil i pladsholder.

Ligeledes hvis de klikker på Tilføj bruger knappen, gengiver den UserFormComponent komponent.

3.11. Det app.module.ts Fil

Dernæst skal vi redigere app.module.ts fil, så Angular kan importere alle de krævede moduler, komponenter og tjenester.

Derudover skal vi specificere, hvilken udbyder vi bruger til at oprette og indsprøjte UserService klasse. Ellers kan Angular ikke injicere det i komponentklasserne:

importer {BrowserModule} fra '@ vinkel / platform-browser'; importer {NgModule} fra '@ vinkel / kerne'; importer {AppRoutingModule} fra './app-routing.module'; importer {FormsModule} fra '@ vinkel / former'; importer {HttpClientModule} fra '@ vinkel / fælles / http'; importer {AppComponent} fra './app.component'; importere {UserListComponent} fra './user-list/user-list.component'; importere {UserFormComponent} fra './user-form/user-form.component'; importere {UserService} fra './service/user.service'; @NgModule ({erklæringer: [AppComponent, UserListComponent, UserFormComponent], importerer: [BrowserModule, AppRoutingModule, HttpClientModule, FormsModule], udbydere: [UserService], bootstrap: [AppComponent]}) eksportklasse AppModule {}

4. Kørsel af applikationen

Endelig er vi klar til at køre vores ansøgning.

For at opnå dette, lad os først køre Spring Boot-applikationen, så REST-tjenesten er i live og lytter efter anmodninger.

Når Spring Boot-applikationen er startet, lad os åbne en kommandokonsol og skrive følgende kommando:

ng server - åbn

Dette starter Angular's live-udviklingsserver og åbner også browseren på // localhost: 4200.

Vi skal se navigationslinjen med knapperne til liste over eksisterende enheder og til tilføjelse af nye. Hvis vi klikker på den første knap, skal vi se en tabel under navigationslinjen med listen over enheder, der er vedvarende i databasen:

Klik på den anden knap vil ligeledes vise HTML-formularen til vedvarende en ny enhed:

5. Konklusion

I denne vejledning vi lærte, hvordan man bygger en grundlæggende webapplikation med Spring Boot og Angular.

Som normalt er alle kodeeksempler vist i denne vejledning tilgængelige på GitHub.