Skip to main content

Angular CI/CD Pipeline

Introduction

Continuous Integration and Continuous Deployment (CI/CD) is a modern software development practice that allows developers to frequently merge code changes into a central repository, after which automated builds and tests are run. When properly implemented, a CI/CD pipeline helps teams deliver updates more frequently and reliably. For Angular applications, a CI/CD pipeline can significantly streamline your development workflow and reduce the time spent on manual tasks.

In this guide, we'll explore how to set up a CI/CD pipeline for your Angular applications, understand the benefits, and learn some best practices for implementing an effective pipeline.

What is a CI/CD Pipeline?

A CI/CD pipeline is an automated workflow that helps developers integrate code changes more frequently and reliably. The pipeline consists of two main components:

  1. Continuous Integration (CI): Automatically building and testing code changes to ensure they integrate well with the existing codebase.
  2. Continuous Deployment (CD): Automatically deploying approved changes to production environments.

For Angular applications, a CI/CD pipeline typically includes:

  • Code linting
  • Building the application
  • Running unit tests
  • Running end-to-end tests
  • Deploying to staging or production environments

Benefits of Implementing a CI/CD Pipeline for Angular

  • Faster feedback: Developers receive immediate feedback on their code changes
  • Reduced manual errors: Automation eliminates human errors during deployment
  • Consistent environments: Pipeline ensures code is tested in the same environment before deployment
  • Improved collaboration: Team members can easily see and review code changes
  • Higher code quality: Automated testing ensures that only working code is deployed
  • Faster release cycles: Automating processes speeds up the time to market

Setting Up Your First CI/CD Pipeline for Angular

Let's explore how to set up a basic CI/CD pipeline using GitHub Actions, one of the most popular CI/CD services that's free for public repositories.

Step 1: Create a GitHub Actions Workflow File

In your Angular project, create a directory structure in the root of your project:

.github/
└── workflows/
└── main.yml

The main.yml file will contain your workflow configuration:

yaml
name: Angular CI/CD

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Build
run: npm run build --if-present

- name: Test
run: npm run test -- --watch=false --browsers=ChromeHeadless

This basic workflow will:

  1. Trigger on push to the main branch or on pull requests to the main branch
  2. Set up a Node.js environment
  3. Install dependencies
  4. Run linting
  5. Build the Angular application
  6. Run tests in headless Chrome

Step 2: Add Deployment to the Pipeline

To add deployment capabilities to your pipeline, you'll need to extend the configuration:

yaml
name: Angular CI/CD

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Build
run: npm run build --if-present

- name: Test
run: npm run test -- --watch=false --browsers=ChromeHeadless

- name: Archive build
if: github.event_name == 'push'
uses: actions/upload-artifact@v2
with:
name: dist
path: dist

deploy:
if: github.event_name == 'push'
needs: build-and-test
runs-on: ubuntu-latest

steps:
- name: Download build
uses: actions/download-artifact@v2
with:
name: dist
path: dist

- name: Deploy to Firebase
uses: w9jds/firebase-action@master
with:
args: deploy --only hosting
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

This extended workflow adds:

  1. An archiving step to save the build output
  2. A separate deployment job that depends on the build job
  3. A deployment step using Firebase Hosting (a popular hosting service for Angular applications)

Step 3: Setting Up Environment Variables and Secrets

For the deployment to work, you need to set up the necessary secrets:

  1. In your GitHub repository, go to "Settings" > "Secrets" > "New repository secret"
  2. Create a new secret named FIREBASE_TOKEN with your Firebase CI token value

For Firebase deployment, you would also need to have:

  • Firebase CLI installed globally: npm install -g firebase-tools
  • A Firebase project set up
  • Firebase initialized in your project: firebase init
  • A Firebase token: firebase login:ci

Advanced CI/CD Pipeline Features

Once you have a basic pipeline working, you might want to enhance it with these features:

1. Environment-specific builds

yaml
- name: Build for production
if: github.ref == 'refs/heads/main'
run: npm run build -- --configuration=production

- name: Build for staging
if: github.ref == 'refs/heads/develop'
run: npm run build -- --configuration=staging

2. Running end-to-end tests

yaml
- name: E2E Tests
run: npm run e2e -- --configuration=ci

3. Code coverage reporting

yaml
- name: Test with coverage
run: npm run test -- --no-watch --code-coverage

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage/
fail_ci_if_error: true

4. Caching dependencies

yaml
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

Real-World Example: Complete Angular CI/CD Pipeline

Here's a comprehensive example of a CI/CD pipeline for an enterprise Angular application:

yaml
name: Angular Enterprise CI/CD

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Check formatting
run: npm run format:check

- name: Run unit tests
run: npm run test -- --no-watch --code-coverage --browsers=ChromeHeadless

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2

- name: Build for production
if: github.ref == 'refs/heads/main'
run: npm run build -- --configuration=production

- name: Build for staging
if: github.ref == 'refs/heads/develop'
run: npm run build -- --configuration=staging

- name: Archive build
if: success() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
uses: actions/upload-artifact@v2
with:
name: dist
path: dist

e2e:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'

- name: Install dependencies
run: npm ci

- name: Run E2E tests
run: npm run e2e -- --configuration=ci

deploy-staging:
if: success() && github.ref == 'refs/heads/develop'
needs: [build, e2e]
runs-on: ubuntu-latest

steps:
- name: Download build
uses: actions/download-artifact@v2
with:
name: dist
path: dist

- name: Deploy to Firebase staging
uses: w9jds/firebase-action@master
with:
args: deploy --only hosting:staging
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

deploy-production:
if: success() && github.ref == 'refs/heads/main'
needs: [build, e2e]
runs-on: ubuntu-latest
environment: production

steps:
- name: Download build
uses: actions/download-artifact@v2
with:
name: dist
path: dist

- name: Deploy to Firebase production
uses: w9jds/firebase-action@master
with:
args: deploy --only hosting:production
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

This advanced example includes:

  • Separate build configurations for staging and production
  • Code coverage reporting
  • End-to-end tests in a separate job
  • Different deployment targets based on the branch
  • Environment protection for production deployment

Integrating with Other CI/CD Services

While we've focused on GitHub Actions, there are many other CI/CD services you can use:

GitLab CI/CD

Create a .gitlab-ci.yml file in your project root:

yaml
image: node:16

stages:
- build
- test
- deploy

cache:
paths:
- node_modules/

build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/

test:
stage: test
script:
- npm ci
- npm run lint
- npm run test -- --no-watch --browsers=ChromeHeadless

deploy:
stage: deploy
script:
- npm install -g firebase-tools
- firebase deploy --token $FIREBASE_TOKEN
only:
- main

CircleCI

Create a .circleci/config.yml file:

yaml
version: 2.1
jobs:
build-and-test:
docker:
- image: cimg/node:16.13
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- run: npm ci
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
- run: npm run lint
- run: npm run build
- run: npm run test -- --no-watch --browsers=ChromeHeadless

deploy:
docker:
- image: cimg/node:16.13
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- run: npm ci
- run: npm run build
- run: npm install -g firebase-tools
- run: firebase deploy --token "$FIREBASE_TOKEN"

workflows:
version: 2
build-test-deploy:
jobs:
- build-and-test
- deploy:
requires:
- build-and-test
filters:
branches:
only: main

Best Practices for Angular CI/CD Pipelines

  1. Keep builds fast: Optimize your tests and build process to run as quickly as possible
  2. Use caching: Cache dependencies to speed up builds
  3. Implement different environments: Set up separate environments for development, staging, and production
  4. Use environment variables: Store sensitive information like API keys in environment variables
  5. Run all tests: Make sure unit, integration, and end-to-end tests are part of your pipeline
  6. Implement code quality checks: Add linting, formatting, and other code quality checks
  7. Version your deployments: Use versioning for your deployments to enable rollbacks
  8. Monitor deployments: Implement monitoring to detect issues quickly
  9. Implement approval workflows: Require approvals for production deployments
  10. Document your pipeline: Keep documentation updated on how the pipeline works and how to troubleshoot issues

Troubleshooting Common CI/CD Issues

1. Build failing due to dependency issues

Solution: Make sure to use exact versions in your package.json or use lockfiles (package-lock.json or yarn.lock).

2. Tests failing in CI but passing locally

Solution: Ensure your test environment in CI matches your local environment as closely as possible. Use headless browsers for testing.

yaml
- name: Test
run: npm run test -- --no-watch --browsers=ChromeHeadless

3. Deployment failing due to permissions

Solution: Check that your CI service has the correct permissions and API tokens to deploy.

4. Long build times

Solution: Implement caching and optimize your build process.

yaml
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

Summary

Setting up a CI/CD pipeline for your Angular application can significantly improve your development workflow by automating testing, building, and deployment processes. We've covered:

  • What a CI/CD pipeline is and its benefits for Angular applications
  • How to set up a basic CI/CD pipeline using GitHub Actions
  • How to implement advanced features in your pipeline
  • A real-world example of a complete CI/CD pipeline
  • Integration with other CI/CD services
  • Best practices and troubleshooting tips

By implementing these practices, you'll experience faster feedback cycles, reduced manual errors, and more reliable deployments, ultimately leading to higher-quality Angular applications.

Additional Resources

Exercises

  1. Set up a basic GitHub Actions workflow for an existing Angular project.
  2. Extend your pipeline to deploy to Firebase hosting.
  3. Implement environment-specific builds (development, staging, production).
  4. Add code coverage reporting to your pipeline.
  5. Optimize your pipeline by implementing caching and parallel jobs.

Happy coding and deploying!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)