Angular CRUD app: from zero to GCP

At Engel & Völkers Technology, we have the opportunity to work on aside projects and dedicate some of our working hours to learn new things. Few months ago, I had the chance to work on a web app for another team (or to be honest, I was the only frontend developer available at that time to do the job :p ) on a basis of one day per week.

a basic CRUD app which consumes two restful endpoints. It offers the following functionalities:

  • listing, creating, editing and deleting business units


  • listing, creating, editing, deleting divisions and linking a division to a business unit.

Motivation:

As I have been working with AngularJS for almost 3 years, I chose the new Angular framework Angular2(v4/v5) to develop the app. I wanted to discover what made Angular team rewrite the framework from scratch and there is no better way to learn than to do it.

In this article, I will try to summarize this short experience with Angular framework and give details about building a CI/CD pipeline to deploy an angular app to GCP.

I started developing the app using Angular(v4), Angular-CLI to create the app, Angular Material to have a nice feel and look and ngx-translate for internationalization(i18n).

Ups and downs:

I think the Angular command line tool is one of the improvements that I really liked, it helps focusing on writing code rather than configuring, getting your app up and running in a short time and building it for production environments. What I liked also is that Angular is still a complete framework with routing, dependency injection, http client, form validation (especially reactive form validation)… and now offers on top of that modularity.

Angular makes use of a lot of new technologies such as Typescript and Reactive programming (rxjs) which makes the learning curve harder at the beginning.

I started the project using version 4 and meanwhile version 5 was released, so I upgraded my app and obviously it was broken: Http Client was deprecated, needed to upgrade Typescript and RxJS versions... but thankfully, the Angular team offers this update guide to help upgrading your Angular app: https://angular-update-guide.firebaseapp.com/

The same happened when I upgraded Angular Material from the beta version to the first stable version: They replaced all “md” prefixes with “mat” prefixes for their components, so none of my components worked anymore.

One last pain point was when googling for information about Angular, either I end up with AngularJS related content or I’m confused about which version(v2, v4 or v5) is the author talking about.

*one hint would be to filter Google results per date so you limit AngularJS related results (thanks to Angular meetup Hamburg)

That was the most important learned lessons during this short journey, now let’s move to how we can deploy this Angular app to Google Cloud Platform

CI/CD pipeline

First, I added these commands to my package.json, which will help executing some tasks in the pipeline:

...
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod --build-optimizer",
"test": "ng test --single-run",
"test:ci": "ng test --browser ChromeHeadlessCI --code-coverage=true --single-run=true",
"lint": "ng lint",
"e2e": "ng e2e"
},
...

As you can see, running npm run build will build the app for production meaning it will activate the Ahead Of Time compilation (AOT) and building optimizer provided by Angular compiler and running npm run test:ci will run unit tests in our pipeline which I will cover in the next part.

Containerizing the app:

As the app is a standalone app, it will be served through a web server like Apache or Nginx. Using Docker, I created a 2-stage build where I build the app for a production environment in the first stage inside a node image and then copy the files to a Nginx image

FROM node:8-alpine as builder
COPY package.json package-lock.json ./
RUN npm set progress=false && npm config set depth 0 && npm cache clean --force
## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
RUN npm i && mkdir /ng-app && cp -R ./node_modules ./ng-app
WORKDIR /ng-app
COPY . .
## Build the angular app in production mode and store the artifacts in dist folder
RUN npm run build
FROM nginx:1.13.3-alpine
## Copy our default nginx config
COPY nginx/default.conf /etc/nginx/conf.d/
## Remove default nginx website
RUN rm -rf /usr/share/nginx/html/*
## From 'builder' stage copy over the artifacts in dist folder to default nginx public folder
COPY --from=builder /ng-app/dist /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]

Now we can build the docker image: docker build -t <image_tag> . and then run it:

docker run -d -p 4200:80 <image_tag>

Jenkins Pipeline:

We are using Jenkins to build our CI/CD pipeline. For this project, I had 5 steps: installing dependencies, linting (using angular-cli default configuration), unit testing (no end-to-end tests yet) then building the app using Google Cloud Container Builder and finally deploy it to GCP.

    1. Unit testing environment:

To be able to run unit tests in Jenkins, we need to have a headless browser (a browser without a GUI). For that I created a docker image with a headless chromium browser, inside which I will be running the unit tests.

FROM node:8.9.4-alpine
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.7/community" >> /etc/apk/repositories
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.7/main" >> /etc/apk/repositories
RUN apk --no-cache update
RUN apk add --no-cache --virtual .build-deps ttf-opensans chromium
RUN rm -rf /var/cache/apk/* /tmp/*
ENV CHROME_BIN /usr/bin/chromium-browser
ENV LIGHTHOUSE_CHROMIUM_PATH /usr/bin/chromium-browser

I’m using Jasmine to write unit tests and Karma as a test runner (default frameworks proposed by Angular). To configure Karma to use the headless browser, we have to add it to its configuration file:

...
customLaunchers: {
ChromeHeadlessCI: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
...
browsers: ['ChromeHeadless'],
...

Another option could be using Puppeteer (the new project from Google Chrome team) as a dev- dependency which will install headless chromium when you install your dependencies.

    2. Docker image build:

    As I mentioned before, our engineering team agreed to delegate docker image building to Google Cloud Container Builder. So I needed a configuration file from which Jenkins will trigger the build step using GoogleCloudContainerBuilder plugin: 
steps:
- name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', 'eu.gcr.io/$PROJECT_ID/<image_tag>', '.' ]
images:
- 'eu.gcr.io/$PROJECT_ID/<image_tag>'

At the end of this step, the docker image will be saved in the Container Registry.

Hamburg - Engel & Völkers Technology

Now that our docker image is ready, we will deploy it to GCP.


    3. Deployment to GCP:


    We are using Kubernetes to manage our containerized apps. So I created a deployment and a LoadBalancer service: 
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: licence-app-frontend-deployment
spec:
selector:
matchLabels:
app: licence-app-frontend
replicas: 3
template: # create pods using pod definition in this template
metadata:
labels:
app: licence-app-frontend
spec:
containers:
- name: licence-app-frontend
image: <url_to_registery_container>
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: licence-app-frontend-service
labels:
name: licence-app-frontend-service
service: licence-app-frontend-service
spec:
selector:
app: licence-app-frontend
ports:
- protocol: TCP
port: 80
type: LoadBalancer

Finally my Jenkins pipeline looked like this:

...
docker.withRegistry('https://eu.gcr.io', '<certificates>') {
docker.image('<url_to_the_unit_test_docker_image>').inside {
...
stage('Git checkout') {
echo 'Repository checkout..'
checkout scm
}
stage('NPM install') {
sh 'npm install'
}
stage('Lint') {
sh 'npm run lint'
}
stage('Unit Tests') {
sh 'npm run test:ci'
}
}
}
stage('GCP Container build') {
googleCloudBuild \
credentialsId: '<credentials_id>',
source: local('.'),
request: file('cloudbuild.yaml')
}
stage('Deploy') {
withCredentials([file(credentialsId: '<credentials_id>', variable: '<PRIVATE_KEY_FILE_VARIABLE>')]) {
sh "gcloud auth activate-service-account --key-file=<PRIVATE_KEY_FILE_VARIABLE>"
sh "gcloud container clusters get-credentials <cluster_name> --project <project_name> --zone <zone>"
sh "kubectl set image deployment/licence-app-frontend-deployment licence-app-frontend=<docker_image_in_registery_container>"
}
}
...
Hamburg - Engel & Völkers Technology

And the app is up and running.




Hamburg - Engel & Völkers Technology

Although the journey with Angular framework was short, it was really enriching. In fact, it will be very helpful for us to decide which frontend frameworks we want to adopt for our future projects and products in the company.




Hamburg - Engel & Völkers

Contact us now

Engel & Völkers Tech Blog
Email
Back
Contact
Enter your contact details here
Thank you for your request. We will contact you shortly.

Your Engel & Völkers Team
Salutation
  • Mr.
  • Mrs.
Send now

Follow us on social media


Array
(
[EUNDV] => Array
(
[67d842e2b887a402186a2820b1713d693dd854a5_csrf_offer-form] => MTM5MjE5NzU3NkJ4d29xancwTDVhZWFIRzEycXAxcW9SdElHdVBqMTdV
[67d842e2b887a402186a2820b1713d693dd854a5_csrf_contact-form] => MTM5MjE5NzU3NnlHcUR0Y2VlTXVPUndLMHZkMW9zMnRmRlgxaUcwaFVG
)
)