Post's thumbnail

Jun 27, 2022

Containerize Angular Apps

As we have seen in the last entry of the series, it is rather simple to containerize a miminal Flask application. However, as applications tend to comprise also of a frondend, let's take a closer look at running an Angular application in a Docker container.

Step 1: Create an Angular Application

Our Angular application should do not much more than retrieving a message form a backend, such as the Flask application we've created in the apprevious post of the series and displaying it to the screen. The easiest to initialize a new Angular application is to use the ng new command provided by Angular's CLI. Angular Material is an optional library that provides a range of UI elements, I personally like although not strictly required here.

ng new project
cd project
npm add @angular/material

A freshly initialised Angular project comes already with a quite a few automatically generated files. As we like to build a minimal application, let's focus on the src/app directory, where the app module and app component reside. First jump to the directory by running the following command.

cd src/app && ls

The console output should look roughly similar to the one dipected below.

First, we need a way to retrieve a message from a backend. There are several ways we could achieve that. But one standard solution would be to create a service handling the backend communication. Angular's CLI provides the ng generate service [SERVICE_NAME] command or just ng g s [SERVICE_NAME] to create new services quickly.

ng g s rest

After having generated the RestService, we want to implement a fetchMessage() method performing a get request to a backend running on port 8080 on our localhost. The complete file should look similar to the one depicted below.

rest.service.ts

import { Injectable } from '@angular/core';
import { catchError, Observable, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class RestService {

  constructor(private readonly httpClient: HttpClient) { }

  public fetchMessage(): Observable<string> {
    return this.httpClient.get<string>('http://127.0.0.1:8080').pipe(catchError(() => of("not connected")))
  }
}

Now, we can override the default content of the AppComponent. The controller's main purpose is to call the RestService and to bind the result of the service's fetchMessage() to a local variable. The complete class should similar to the one depicted below.

app.component.ts

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

  public readonly message: Observable<string> = this.restService.fetchMessage();

  constructor(private readonly restService: RestService) {}
}

The message received from the backend is aviable in the AppComponent but is not yet renders. One way to show the message on the page would be to utilize Angular's async pipe to subscribe to the message Observable of the AppComponent. The message could simply be rendered as text or as depiced below inside an Angular Material MatCard component.

app.component.html

<mat-card *ngIf="message | async as message">
  {{ message }}
</mat-card>

It is purely optional to provide a stylesheet of the component, but the scss file shown below formats the MatCard in which the message is displayed a bit.

app.component.scss

.mat-card {
  width: 250px;
  height: 100px;
  border-radius: 20px;
  text-align: center;
  background: linear-gradient(180deg, rgba(240,218,57,1) 0%, rgba(197,100,36,1) 100%);
  transition: box-shadow 1s, margin-top 1s, width 1s, height 1s, transform 0.7s;
  font-family: 'Courier New', Courier, monospace;
  padding-top: 100px;
  margin: auto;
  margin-top: 200px;
}

.mat-card:hover {
  box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
  transform: scale(1.02);
}

Step 2: Define a Dockerfile

There are several ways to create a Docker image for an Angular application. One option would be to split the process into two parts, A first one to build the Angular application and a second one to run the bundled application on a webserver like Nginx. The build step takes a node image, copies all files in the present working directory to the build stage, installs all npm packaged defined in the packages.json file and finally runs npm's build command.

The second state takes an image of a webserver, in the example depicted below we use and nginx:alpine image. The image contains an Alpine Linux distribution on which a Nginx webserver is installed. The second stage takes nginx's html folder as working directory, removes all boilerplate code on that location, copies the dist directory of the first stage into the working directory and finally starts Nginx. Setting the /app/dist/[PROJECT_NAME] path correcly is rather important. In the examples below, the project's name is just project, thus leading to the /app/dist/project path. If a Angular project had a different name, the path would have to be replaced.

FROM node:18-alpine3.15 AS builder

WORKDIR /app

COPY . .

RUN npm i && npm run build


FROM nginx:alpine

WORKDIR /usr/share/nginx/html

RUN rm -rf .

COPY --from=builder /app/dist/project .

ENTRYPOINT ["nginx", "-g", "daemon off;"]

Step 3: Build the Image

Once the Dockerfile has been defined, building the Docker image is rather straight-forward by running the command shown below.

docker build -t angular-introduction:latest .

Step 4: Start a Container

A new container based on the angular-introduction image, can be created by running hte following command.

docker run -d -p 80:80 angular-introduction
docker ps

After the container has started, it should be possible to open localhost:80 and see a page looking similar to one depicted below.

Closing Remarks

At the moment, there is no backend available to fetch a message from. Please take a look at the next post to bring the pieces together by defining a Docker compose file starting the Flask backend from the previous post and the Angular application described in this post.

Previous

Containerize Flask Apps

Up next

Multi Container Applications