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:
- Continuous Integration (CI): Automatically building and testing code changes to ensure they integrate well with the existing codebase.
- 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:
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:
- Trigger on push to the main branch or on pull requests to the main branch
- Set up a Node.js environment
- Install dependencies
- Run linting
- Build the Angular application
- 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:
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:
- An archiving step to save the build output
- A separate deployment job that depends on the build job
- 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:
- In your GitHub repository, go to "Settings" > "Secrets" > "New repository secret"
- 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
- 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
- name: E2E Tests
run: npm run e2e -- --configuration=ci
3. Code coverage reporting
- 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
- 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:
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:
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:
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
- Keep builds fast: Optimize your tests and build process to run as quickly as possible
- Use caching: Cache dependencies to speed up builds
- Implement different environments: Set up separate environments for development, staging, and production
- Use environment variables: Store sensitive information like API keys in environment variables
- Run all tests: Make sure unit, integration, and end-to-end tests are part of your pipeline
- Implement code quality checks: Add linting, formatting, and other code quality checks
- Version your deployments: Use versioning for your deployments to enable rollbacks
- Monitor deployments: Implement monitoring to detect issues quickly
- Implement approval workflows: Require approvals for production deployments
- 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.
- 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.
- 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
- GitHub Actions Documentation
- Angular CLI Deployment Guide
- Firebase Hosting Documentation
- GitLab CI/CD Documentation
- CircleCI Documentation
Exercises
- Set up a basic GitHub Actions workflow for an existing Angular project.
- Extend your pipeline to deploy to Firebase hosting.
- Implement environment-specific builds (development, staging, production).
- Add code coverage reporting to your pipeline.
- 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! :)