1[
2 {
3 "name": "foo",
4 "albums": [
5 {
6 "title": "album_one",
7 "artist": "foo",
8 "ntracks": 12
9 },
10 {
11 "title": "album_two",
12 "artist": "foo",
13 "ntracks": 15
14 }
15 ]
16 },
17 {
18 "name": "bar",
19 "albums": [
20 {
21 "title": "foo walks into a bar",
22 "artist": "bar",
23 "ntracks": 12
24 }
25 ]
26 },
27 {
28 "name": "dj",
29 "albums": [
30 ]
31 }
32]
Contents
- What you’ll learn
- Getting started
- Starting the service
- Project configuration
- Creating the root Angular module
- Creating the Angular service to fetch data
- Defining the component to consume the service
- Creating the Angular component template
- Building the front end
- Testing the Angular client
- Great work! You’re done!
- Guide Attribution
Tags
Consuming a RESTful web service with Angular
Prerequisites:
Explore how to access a simple RESTful web service and consume its resources with Angular in OpenLiberty.
What you’ll learn
Angular is a framework for creating interactive web applications. Angular applications are written in HTML, CSS, and TypeScript, a variant of JavaScript. Angular helps you create responsive and intuitive applications that download once and run as a single web page. Consuming REST services with your Angular application allows you to request only the data and operations that you need, minimizing loading times.
You will learn how to access a REST service and deserialize the returned JSON that contains a list of artists and their albums by using an Angular service and the Angular HTTP Client. You will then present this data using an Angular component.
The REST service that provides the artists and albums resource was written for you in advance and responds with the artists.json
.
The Angular application was created and configured for you in the frontend
directory. It contains the default starter application. There are many files that make up an Angular application, but you only need to edit a few to consume the REST service and display its data.
Angular applications must be compiled before they can be used. The Angular compilation step was configured as part of the Maven build. You can use the start
folder of this guide as a template for getting started with your own applications built on Angular and Open Liberty.
artists.json
You will implement an Angular client that consumes this JSON and displays its contents
at the http://localhost:9080/app
URL.
To learn more about REST services and how you can write them, see Creating a RESTful web service.
Getting started
The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:
git clone https://github.com/openliberty/guide-rest-client-angular.git
cd guide-rest-client-angular
The start
directory contains the starting project that you will build upon.
The finish
directory contains the finished project that you will build.
Before you begin, make sure you have all the necessary prerequisites.
Try what you’ll build
The finish
directory in the root of this guide contains the finished application. Give it a try before you proceed.
To try out the application, first go to the finish
directory and run the following Maven goal to build the application and deploy it to Open Liberty:
cd finish
mvn liberty:run
After you see the following message, your Liberty instance is ready:
The defaultServer server is ready to run a smarter planet.
Then, point your browser to the web application root http://localhost:9080/app to see the following output:
foo wrote 2 albums: Album titled album_one by foo contains 12 tracks Album tilted album_two by foo contains 15 tracks bar wrote 1 albums: Album titled foo walks into a bar by bar contains 12 tracks dj wrote 0 albums:
After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C
in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop
goal from the finish
directory in another shell session:
mvn liberty:stop
Starting the service
Before you begin the implementation, start the provided REST service so that the artist JSON is available to you.
Navigate to the start
directory to begin.
When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:
mvn liberty:dev
After you see the following message, your Liberty instance is ready in dev mode:
************************************************************** * Liberty is running in dev mode.
Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.
You can find your artist JSON at the http://localhost:9080/artists URL.
Project configuration
The front end of your application uses Node.js to execute your Angular code. The Maven project is configured for you to install Node.js and produce the production files, which are copied to the web content of your application.
Node.js is server-side JavaScript runtime that is used for developing networking applications. Its convenient package manager, npm, is used to execute the Angular scripts found in the package.json
file. To learn more about Node.js, see the official Node.js documentation.
The frontend-maven-plugin
is used to install
the dependencies listed in your package.json
file from the npm registry into a folder called node_modules
. The node_modules
folder is found in your working
directory. Then, the configuration produces
the production files to the src/main/frontend/src/app
directory.
The src/main/frontend/src/angular.json
file is defined so that the production build is copied into the web content of your application.
pom.xml
1<?xml version='1.0' encoding='utf-8'?>
2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3
4 <modelVersion>4.0.0</modelVersion>
5
6 <groupId>io.openliberty.guides</groupId>
7 <artifactId>guide-rest-client-angular</artifactId>
8 <version>1.0-SNAPSHOT</version>
9 <packaging>war</packaging>
10
11 <properties>
12 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
14 <maven.compiler.source>11</maven.compiler.source>
15 <maven.compiler.target>11</maven.compiler.target>
16 </properties>
17
18 <dependencies>
19 <!-- Provided dependencies -->
20 <dependency>
21 <groupId>jakarta.platform</groupId>
22 <artifactId>jakarta.jakartaee-api</artifactId>
23 <version>10.0.0</version>
24 <scope>provided</scope>
25 </dependency>
26 <dependency>
27 <groupId>org.eclipse.microprofile</groupId>
28 <!-- tag::microprofile[] -->
29 <artifactId>microprofile</artifactId>
30 <!-- end::microprofile[] -->
31 <version>6.1</version>
32 <type>pom</type>
33 <scope>provided</scope>
34 </dependency>
35 </dependencies>
36
37 <build>
38 <finalName>${project.artifactId}</finalName>
39 <plugins>
40 <plugin>
41 <groupId>org.apache.maven.plugins</groupId>
42 <artifactId>maven-war-plugin</artifactId>
43 <version>3.4.0</version>
44 </plugin>
45 <!-- Enable liberty-maven plugin -->
46 <plugin>
47 <groupId>io.openliberty.tools</groupId>
48 <artifactId>liberty-maven-plugin</artifactId>
49 <version>3.11.1</version>
50 </plugin>
51 <!-- tag::frontend-plugin[] -->
52 <plugin>
53 <groupId>com.github.eirslett</groupId>
54 <artifactId>frontend-maven-plugin</artifactId>
55 <version>1.15.1</version>
56 <configuration>
57 <!-- tag::working-dir[] -->
58 <workingDirectory>src/main/frontend</workingDirectory>
59 <!-- end::working-dir[] -->
60 </configuration>
61 <executions>
62 <!-- tag::node-resource-install[] -->
63 <execution>
64 <id>install node and npm</id>
65 <goals>
66 <goal>install-node-and-npm</goal>
67 </goals>
68 <configuration>
69 <nodeVersion>v16.20.1</nodeVersion>
70 <npmVersion>8.5.0</npmVersion>
71 </configuration>
72 </execution>
73 <!-- end::node-resource-install[] -->
74 <execution>
75 <id>npm install</id>
76 <goals>
77 <goal>npm</goal>
78 </goals>
79 <!-- Optional configuration for running
80 any npm command -->
81 <configuration>
82 <arguments>install</arguments>
83 </configuration>
84 </execution>
85 <!-- tag::node-resource-build[] -->
86 <execution>
87 <id>npm run build</id>
88 <goals>
89 <goal>npm</goal>
90 </goals>
91 <configuration>
92 <arguments>run build</arguments>
93 </configuration>
94 </execution>
95 <!-- end::node-resource-build[] -->
96 </executions>
97 </plugin>
98 <!-- end::frontend-plugin[] -->
99 </plugins>
100 </build>
101</project>
Creating the root Angular module
Your application needs a way to communicate with and retrieve resources from RESTful web services. In this case, the provided Angular application needs to communicate with the artists service to retrieve the artists JSON. While there are various ways to perform this task, Angular contains a built-in HttpClientModule
that you can use.
Angular applications consist of modules, which are groups of classes that perform specific functions. The Angular framework provides its own modules for applications to use. One of these modules, the HTTP Client module, includes convenience classes that make it easier and quicker for you to consume a RESTful API from your application.
You will create the module that organizes your application, which is called the root module. The root module includes the Angular HTTP Client module.
Create theapp.module.ts
file.src/main/frontend/src/app/app.module.ts
app.module.ts
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3// tag::importHttpClientModule[]
4import { HttpClientModule } from '@angular/common/http';
5// end::importHttpClientModule[]
6import { AppComponent } from './app.component';
7
8// tag::atNgModule[]
9@NgModule({
10 declarations: [
11 AppComponent
12 ],
13 // tag::importsArray[]
14 imports: [
15 BrowserModule,
16 // tag::httpClientModule[]
17 HttpClientModule,
18 // end::httpClientModule[]
19 ],
20 // end::importsArray[]
21 providers: [],
22 bootstrap: [AppComponent]
23})
24// end::atNgModule[]
25export class AppModule { }
The HttpClientModule
imports the class into the file. By using the @NgModule
tag, you can declare a module and organize your dependencies within the Angular framework. The imports
array is a declaration array that imports the HttpClientModule
so that you can use the HTTP Client module in your application.
Creating the Angular service to fetch data
You need to create the component that is used in the application to acquire and display data from the REST API. The component file contains two classes: the service, which handles data access, and the component itself, which handles the presentation of the data.
Services are classes in Angular that are designed to share their functionality across entire applications. A good service performs only one function, and it performs this function well. In this case, the ArtistsService
class requests artists data from the REST service.
Create theapp.component.ts
file.src/main/frontend/src/app/app.component.ts
app.component.ts
The file imports the HttpClient
class and the Injectable
decorator.
The ArtistsService
class is defined. While it shares the file of the component class AppComponent
, it can also be defined in its own file. The class is annotated by @Injectable
so instances of it can be provided to other classes anywhere in the application.
The class injects an instance of the HttpClient
class, which it uses to request data from the REST API. It contains the ARTISTS_URL
constant, which points to the API endpoint it requests data from. The URL does not contain a host name because the artists API endpoint is accessible from the same host as the Angular application. You can send requests to external APIs by specifying the full URL. Finally, it implements a fetchArtists()
method that makes the request and returns the result.
To obtain the data for display on the page, the fetchArtists()
method tries to use the injected http
instance to perform a GET
HTTP request to the ARTISTS_URL
constant. If successful, it returns the result. If an error occurs, it prints the error message to the console.
The fetchArtists()
method uses a feature of JavaScript called async
, await
to make requests and receive responses without preventing the application from working while it waits. For the result of the HttpClient.get()
method to be compatible with this feature, it must be converted to a Promise by invoking its toPromise()
method. APromise is how JavaScript represents the state of an asynchronous operation. If you want to learn more, check out promisejs.org for an introduction.
Defining the component to consume the service
Components are the basic building blocks of Angular application user interfaces. Components are made up of a TypeScript class annotated with the @Component
annotation and the HTML template file (specified by templateUrl
) and CSS style files (specified by styleUrls
.)
Update the AppComponent
class to use the artists service to fetch the artists data and save it so the component can display it.
Update theapp.component.ts
file.src/main/frontend/src/app/app.component.ts
app.component.ts
1// tag::importOnInitAndAngularCorePackage[]
2import { Component, OnInit } from '@angular/core';
3// end::importOnInitAndAngularCorePackage[]
4// tag::importHttpClient[]
5import { HttpClient } from '@angular/common/http';
6// end::importHttpClient[]
7// tag::importInjectable[]
8import { Injectable } from '@angular/core';
9// end::importInjectable[]
10
11// tag::atInjectable[]
12@Injectable()
13// end::atInjectable[]
14// tag::artistsServiceClass[]
15export class ArtistsService {
16 // tag::httpClientInstance[]
17 constructor(private http: HttpClient) { }
18 // end::httpClientInstance[]
19
20 // tag::artistsUrl[]
21 private static ARTISTS_URL = '/artists';
22 // end::artistsUrl[]
23
24 // tag::fetchArtistsMethod[]
25 // tag::asyncFeature[]
26 async fetchArtists() {
27 // end::asyncFeature[]
28 try {
29 // tag::httpInstanceAndAwaitFeatureAndHttpGetAndToPromise[]
30 const data: any = await this.http.get(ArtistsService.ARTISTS_URL).toPromise();
31 // end::httpInstanceAndAwaitFeatureAndHttpGetAndToPromise[]
32 return data;
33 } catch (error) {
34 console.error('Error occurred: ' + error);
35 }
36 }
37 // end::fetchArtistsMethod[]
38}
39// end::artistsServiceClass[]
40
41// tag::atComponent[]
42@Component({
43 selector: 'app-root',
44 // tag::templateUrl[]
45 templateUrl: './app.component.html',
46 // end::templateUrl[]
47 // tag::providersProperty[]
48 providers: [ ArtistsService ],
49 // end::providersProperty[]
50 // tag::styleUrls[]
51 styleUrls: ['./app.component.css']
52 // end::styleUrls[]
53})
54// end::atComponent[]
55// tag::appComponentClass[]
56// tag::onInitInterface[]
57export class AppComponent implements OnInit {
58// end::onInitInterface[]
59 // tag::artistsClassMember[]
60 artists: any[] = [];
61 // end::artistsClassMember[]
62
63 // tag::artistsServiceInstanceDeclaration[]
64 constructor(private artistsService: ArtistsService) { }
65 // end::artistsServiceInstanceDeclaration[]
66
67 // tag::ngOnInitMethod[]
68 ngOnInit() {
69 // tag::thenClause[]
70 // tag::artistsServiceInstance[]
71 this.artistsService.fetchArtists().then(data => {
72 // end::artistsServiceInstance[]
73 this.artists = data;
74 });
75 // end::thenClause[]
76 }
77 // end::ngOnInitMethod[]
78}
79// end::appComponentClass[]
Replace the entire AppComponent
class along with the @Component
annotation. Add OnInit
to the list of imported classes at the top.
The providers
property on the @Component
annotation indicates that this component provides the ArtistsService
to other classes in the application.
AppComponent
implements OnInit
, which is a special interface called a lifecycle hook. When Angular displays, updates, or removes a component, it calls a specific function, the lifecycle hook, on the component so the component can run code in response to this event. This component responds to the OnInit
event via the ngOnInit
method, which fetches and populates the component’s template with data when it is initialized for display. The file imports the OnInit
interface from the @angular/core
package.
artists
is a class member of type any[]
that starts out as an empty array. It holds the artists retrieved from the service so the template can display them.
An instance of the ArtistsService
class is injected into the constructor and is accessible by any function that is defined in the class. The ngOnInit
function uses the artistsService
instance to request the artists data. The fetchArtists()
method is an async
function so it returns a Promise. To retrieve the data from the request, ngOnInit
calls the then()
method on the Promise which takes in the data and stores it to the artists
class member.
Creating the Angular component template
Now that you have a service to fetch the data and a component to store it in, you will create a template to specify how the data will be displayed on the page. When you visit the page in the browser, the component populates the template to display the artists data with formatting.
Create theapp.component.html
file.src/main/frontend/src/app/app.component.html
app.component.html
1<!-- tag::artistsDiv[] -->
2<div *ngFor="let artist of artists">
3 <!-- tag::artistNameAndAlbumsLengthPlaceholders[] -->
4 <p>{{ artist.name }} wrote {{ artist.albums.length }} albums: </p>
5 <!-- end::artistNameAndAlbumsLengthPlaceholders[] -->
6 <!-- tag::albumDiv[] -->
7 <div *ngFor="let album of artist.albums">
8 <p style="text-indent: 20px">
9 Album titled <b>{{ album.title }}</b> by
10 <b>{{ album.artist }}</b> contains
11 <b>{{ album.ntracks }}</b> tracks
12 </p>
13 </div>
14 <!-- end::albumDiv[] -->
15</div>
16<!-- end::artistsDiv[] -->
The template contains a div
element that is enumerated by using the ngFor
directive. The artist
variable is bound to the artists
member of the component. The div
element itself and all elements contained within it are repeated for each artist, and the {{ artist.name }}
and {{ artist.albums.length }}
placeholders are populated with the information from each artist. The same strategy is used to display each album
by each artist.
Building the front end
The Open Liberty instance is already started, and the REST service is running. In a new command-line session, build the front end by running the following command in the start
directory:
mvn generate-resources
The build might take a few minutes to complete. You can rebuild the front end at any time with the generate-resources
Maven goal. Any local changes to your TypeScript or HTML are picked up when you build the front end.
Point your browser to the http://localhost:9080/app web application root to see the following output:
foo wrote 2 albums: Album titled album_one by foo contains 12 tracks Album tilted album_two by foo contains 15 tracks bar wrote 1 albums: Album titled foo walks into a bar by bar contains 12 tracks dj wrote 0 albums:
If you use the curl
command to access the web application root URL, you see only the application root page in HTML. The Angular framework uses JavaScript to render the HTML to display the application data. A web browser runs JavaScript, and the curl
command doesn’t.
Testing the Angular client
No explicit code directly uses the consumed artist JSON, so you don’t need to write any test cases.
Whenever you change and build your Angular implementation, the application root at http://localhost:9080/app reflects the changes automatically.
When you are done checking the application root, exit dev mode by pressing CTRL+C
in the command-line session where you ran the Liberty.
Although the Angular application that this guide shows you how to build is simple, when you build more complex Angular applications, testing becomes a crucial part of your development lifecycle. If you need to write test cases, follow the official unit testing and end-to-end testing documentation on the official Angular page.
Great work! You’re done!
You just accessed a simple RESTful web service and consumed its resources by using Angular in Open Liberty.
Guide Attribution
Consuming a RESTful web service with Angular by Open Liberty is licensed under CC BY-ND 4.0
Prerequisites:
Nice work! Where to next?
What did you think of this guide?
Thank you for your feedback!
What could make this guide better?
Raise an issue to share feedback
Create a pull request to contribute to this guide
Need help?
Ask a question on Stack Overflow