Quarkus + Angular with Keycloak — Pt3
Ricardo Mello
Posted on March 23, 2023
On this article, we 'll finish the development of our daily-quotes application. If you haven’t seen the second part, click here. The main goal of this part is implement a communication between the frontend and the backend.
Angular Application
Let’s change our Angular application and generate some components that will add and list quotes. Let’s divide it into two groups:
- Create quote-list
- Create quote-add
Create quote-list
Generating Quote class:
$ ng generate class quote
Generating Service:
$ ng generate service quote-service
Generating quote-list component:
$ ng generate component quote-list
Implementing components
In this step we 'll change the generated components.
Open quote.ts and add the following fields:
export class Quote {
message?: string;
author?: string;
}
Open quote-service.service.ts and create a findAll method and define the backend restAPI http://localhost:8080/quote
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Quote } from 'src/app/model/quote';
@Injectable({
providedIn: 'root'
})
export class QuoteService {
private quoteUrl: string;
constructor(private http: HttpClient) {
this.quoteUrl = 'http://localhost:8080/quote';
}
public findAll(): Observable<Quote[]> {
return this.http.get<Quote[]>(this.quoteUrl);
}
}
Open quote-list.component.ts and inject and implement service:
import { Component, OnInit } from '@angular/core';
import { Quote } from '../model/quote';
import { QuoteService } from '../services/quote/quote-service.service';
@Component({
selector: 'app-quote-list',
templateUrl: './quote-list.component.html',
styleUrls: ['./quote-list.component.scss']
})
export class QuoteListComponent implements OnInit {
quotes?: Quote[];
constructor(private quoteService: QuoteService) {
}
async ngOnInit() {
this.quoteService.findAll().subscribe(data => {
this.quotes = data;
});
}
}
Open quote-list.component.html must be like this:
<div class="container">
<div class="row" *ngFor="let quote of quotes">
<div class="col-sm-12">
<blockquote class="blockquote">
<p class="mb-0">
{{quote.message}}
</p>
<footer class="blockquote-footer">
{{quote.author}}
</footer>
</blockquote>
</div>
</div>
</div>
Declaring QuoteListComponent and import HttpClientModule on app.module.ts:
@NgModule({
declarations: [
AppComponent,
**QuoteListComponent**,
NavbarComponent,
AdminComponent,
],
imports: [
BrowserModule,
CommonModule,
AppRoutingModule,
BrowserAnimationsModule,
MatToolbarModule,
MatButtonModule,
MatMenuModule,
MatIconModule,
KeycloakAngularModule,
**HttpClientModule**,
],
Adding QuoteListComponent route on app-routing.module.ts:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { QuoteAddComponent } from './component/quote-add/quote-add.component.component';
import { QuoteListComponent } from './component/quote-list/quote-list.component';
const routes: Routes = [
{ path: 'home', component: AppComponent },
{ path: 'quotes', component: QuoteListComponent },
{ path: 'add', component: QuoteAddComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Finally, we have to enable http.cors on application.properties in Quarkus backend:
quarkus.http.cors=true
%dev.quarkus.http.cors.origins=/.*/
Very good 😍! We have finished implementing of quote listing route. Now, we are able to get quote from backend.
Create quote-add
Generating quote-add component:
$ ng generate component quote-add
Implementing components
In this step we 'll change the generated components.
Open quote-add.component.ts
import {Component, Inject} from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
import { Quote } from 'src/app/model/quote';
import { QuoteService } from 'src/app/services/quote/quote-service.service';
export interface DialogData {
message: string;
author: string;
}
@Component({
selector: 'app-quote-add',
templateUrl: './quote-add.component.html',
styleUrls: ['./quote-add.component.scss']
})
export class QuoteAddComponent {
constructor(
public dialogRef: MatDialogRef<QuoteAddComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData,
private quoteService: QuoteService
) {}
onNoClick(): void {
this.dialogRef.close();
}
}
Open quote-add.component.html
<div class="form">
<h1 mat-dialog-title>Create new Quote</h1>
<div mat-dialog-content>
<mat-form-field class="field-author" appearance="outline">
<mat-label>Author</mat-label>
<input matInput [(ngModel)]="data.author">
</mat-form-field>
<mat-form-field class="field-message" appearance="outline">
<mat-label>Message</mat-label>
<textarea matInput [(ngModel)]="data.message"></textarea>
</mat-form-field>
</div>
</div>
<div mat-dialog-actions class="buttons">
<button mat-button (click)="onNoClick()">Cancel</button>
<button mat-button [mat-dialog-close]="data">Create</button>
</div>
Open quote-add.component.scss
.form {
max-width: 500px;
width: 100%;
margin: 10px;
}
.field-author {
align-items: center;
margin: 10px;
}
.field-message {
width: 400px;
margin: 5px;
}
.buttons {
align-items: center;
background-color: deepskyblue;
margin: 10px;
}
Open navbar.component.ts and change the content. It should be like this:
import { Component, OnInit } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
import { MatDialog } from '@angular/material/dialog';
import { QuoteAddComponent } from '../quote-add/quote-add.component';
import { QuoteService } from 'src/app/services/quote/quote-service.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
constructor(
private readonly keycloak: KeycloakService,
public dialog: MatDialog,
public quoteService: QuoteService,
public router: Router
) { }
public hasAdminRole: boolean = false;
message?: string;
author?: string;
ngOnInit(): void {
this.hasAdminRole = this.keycloak.getUserRoles().includes('admin');
}
public async logout() {
this.keycloak.logout();
}
public add() {
const dialogRef = this.dialog.open(QuoteAddComponent, {
data: {message: this.message, author: this.author},
})
dialogRef.afterClosed().subscribe(async result => {
(await this.quoteService.save(result)).subscribe( result =>
this.redirectTo('quotes')
);
}
);
}
redirectTo(uri:string){
this.router.navigateByUrl('/', {skipLocationChange: true}).then(()=>
this.router.navigate([uri]));
}
}
Open navbar.component.html and include the add method to new quote button:
<button mat-menu-item (click)="add()">New Quote</button>
Lets create our add http method on quote-service.service.ts:
async save(quote: Quote): Promise<Observable<Quote>>{
return this.http.post<Quote>(this.quoteUrl, quote);
}
Very good 😍! We have finished implementing our quote add route. Now, we are able to add new quotes.
Conclusion
On this third and last part we finished our daily-quotes application by implementing communication between the frontend and backend protected by OIDC with keycloak.
Well done guys, the whole project is available at my gitHub.
Feel comfortable to suggest, comment and contribute with this project.
Thanks ❤️
Posted on March 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.