This is an Angular SSR application. There are two main reasons for this:
-
the web server for when the app is deployed in Kubernetes.
-
to proxy API requests to internally-facing backend API services, such as the opal-fines-service.
- Getting Started
- Local Development Strategy
- Production Server
- Running Unit Tests
- Running End-to-End Tests
- Accessibility Tests
- Switching Between Local and Published Common Libraries
Running the application requires the following tools to be installed in your environment:
Install dependencies by executing the following command:
yarn
Clone the opal-fines-service repository and follow the instructions in there to get it up and running. This is required by the front end to make local API requests.
Clone the opal-frontend-common-ui-lib repository and run:
yarn
yarn buildThis is required if you want to develop the frontend against the local version of the UI library using yarn dev:local-lib:ssr.
Clone the opal-frontend-common-node-lib repository and run:
yarn
yarn buildThis is required if you want to develop the frontend against the local version of the Node library using yarn dev:local-lib:ssr.
There are two ways to run the Angular SSR application depending on whether you are developing against local or published versions of the common libraries:
-
To use the published versions of the libraries:
yarn dev:ssr
This starts the SSR dev server using the versions pinned in your
package.json/yarn.lock. No packages are reinstalled. -
To use local versions of the libraries:
First, ensure you've built the libraries locally and set the environment variables:
export COMMON_UI_LIB_PATH="[INSERT PATH TO COMMON UI LIB DIST FOLDER]" export COMMON_NODE_LIB_PATH="[INSERT PATH TO COMMON NODE LIB DIST FOLDER]"
Ensure you've built both libraries and exported the environment variables before running this command.
Then run:
yarn dev:local-lib:ssr
This will import the local builds and start the SSR dev server with those versions.
The application's home page will be available at http://localhost:4200.
Note this is running the Angular SSR application and expects the opal-fines-service to also be running locally to function correctly.
There are two options depending on whether you're working with local or published versions of the common libraries. This command builds the Angular SSR application for production and serves it. You will not have hot reloading in this mode.
-
To build and serve the application using the published libraries:
yarn build:serve:ssr
This will:
- Build the application for production using the versions pinned in your
package.json/yarn.lock - Serve it on http://localhost:4000
- Build the application for production using the versions pinned in your
-
To build and serve the application using local libraries:
First, ensure you've built both common libraries and set the environment variables:
export COMMON_UI_LIB_PATH="[INSERT PATH TO COMMON UI LIB DIST FOLDER]" export COMMON_NODE_LIB_PATH="[INSERT PATH TO COMMON NODE LIB DIST FOLDER]"
Ensure you've built both libraries and exported the environment variables before running this command.
Then run:
yarn build:serve:local-lib:ssr
This will:
- Import the local builds of the common libraries
- Build the application for production
- Serve it on http://localhost:4000
The application's home page will be available at http://localhost:4000.
Note this is running the Angular SSR application and expects the opal-fines-service to also be running locally to function correctly.
By default Redis is disabled for local development. If desired, start up a Redis instance locally:
docker run -p 6379:6379 -it redis/redis-stack-server:latest
And enable Redis integration within the application by setting the environment variable FEATURES_REDIS_ENABLED to true. The application will connect to Redis on the next startup.
By default Launch Darkly is disabled by default for local development. To enable set the following environment variables. Replace XXXXXXXXXXXX with the project client id.
export FEATURES_LAUNCH_DARKLY_ENABLED=true
export LAUNCH_DARKLY_CLIENT_ID=XXXXXXXXXXXX
The streaming of flags is disabled by default, if you would like to enable set the following environment variable.
export FEATURES_LAUNCH_DARKLY_STREAM=true
Run yarn build:ssr to build the project. The build artifacts will be stored in the dist/opal-frontend directory. This compiles both the node.js server-side code and angular code.
Running the linting:
yarn lint
You can fix prettier formatting issues using:
yarn prettier:fix
There is a custom lint rule for member ordering to ensure members in the code are ordered in the following format:
[
"private-static-field",
"protected-static-field",
"public-static-field",
"private-instance-field",
"protected-instance-field",
"public-instance-field",
"constructor",
"private-static-method",
"protected-static-method",
"public-static-method",
"private-instance-method",
"protected-instance-method",
"public-instance-method"
]Run yarn test to execute the unit tests via karma.
To check code coverage, run yarn test --code-coverage to execute the unit tests via karma but with code coverage.
Code coverage can then be found in the coverage folder of the repository locally.
We are using cypress for our end to end tests.
Run yarn test:smoke to execute the end-to-end smoke tests.
yarn test:smoke
Run yarn test:functional to execute the end-to-end functional tests.
yarn test:functional
Run yarn cypress to open the cypress console, very useful for debugging tests.
yarn cypress
We are using axe-core and cypress-axe to check the accessibility. Run the production server and once running you can run the smoke or functional test commands.
See opal-frontend-common-ui-lib and opal-frontend-common-node-lib for usage and build instructions.
This project supports switching between local and published versions of the opal-frontend-common and opal-frontend-common-node libraries using the following scripts:
First, ensure you've built the libraries locally and exported the paths to the built dist folders:
# In your shell config file (.zshrc, .bash_profile, etc.)
export COMMON_UI_LIB_PATH="[INSERT PATH TO COMMON UI LIB FOLDER]"
export COMMON_NODE_LIB_PATH="[INSERT PATH TO COMMON NODE LIB DIST FOLDER]"Then, run the following scripts:
yarn import:local:common-ui-lib
yarn import:local:common-node-libThese commands will remove the published versions and install the local builds from the paths you specified.
To reinstall the published packages at the exact versions declared in package.json (not the latest):
yarn import:published:common-ui-lib
yarn import:published:common-node-libThis is useful when you're no longer working on the libraries directly or want to verify against the published versions that your project is pinned to.
Note: Version upgrades should come via Renovate PRs. These commands do not upgrade to the latest; they reinstall the exact versions specified in package.json. For extra safety in CI, consider using yarn install --immutable to prevent lockfile drift.
Run yarn ng generate component component-name to generate a new component. You can also use yarn ng generate directive|pipe|service|class|guard|interface|enum|module.
Note the requirement for prefixing the ng commands with yarn
https://angular.dev/ai/develop-with-ai
Paste the following prompt into your AI assistant of choice.
You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices.
## TypeScript Best Practices
- Use strict type checking
- Prefer type inference when the type is obvious
- Avoid the `any` type; use `unknown` when type is uncertain
## Angular Best Practices
- Always use standalone components over NgModules
- Do NOT set `standalone: true` inside the `@Component`, `@Directive` and `@Pipe` decorators
- Use signals for state management
- Implement lazy loading for feature routes
- Use `NgOptimizedImage` for all static images.
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
## Components
- Keep components small and focused on a single responsibility
- Use `input()` and `output()` functions instead of decorators
- Use `computed()` for derived state
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
- Prefer inline templates for small components
- Prefer Reactive forms instead of Template-driven ones
- Do NOT use `ngClass`, use `class` bindings instead
- DO NOT use `ngStyle`, use `style` bindings instead
## State Management
- Use signals for local component state
- Use `computed()` for derived state
- Keep state transformations pure and predictable
- Do NOT use `mutate` on signals, use `update` or `set` instead
## Templates
- Keep templates simple and avoid complex logic
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
- Use the async pipe to handle observables
## Services
- Design services around a single responsibility
- Use the `providedIn: 'root'` option for singleton services
- Use the `inject()` function instead of constructor injectionPrompt:
“How do Angular signals work?”
What Copilot does:
Calls search_documentation("signals") and returns official Angular documentation context.
Prompt:
“Generate a service for user authentication”
What Copilot does:
Runs ng generate service user-auth through the MCP server — adds the file in the correct directory.
Prompt:
“List all Angular modules in this project”
What Copilot does:
Uses list_projects and get_file_tree to find and display modules across the workspace.
Prompt:
“What routes are defined in this app?”
What Copilot does:
Parses routing modules and shows route paths, guards, and lazy-loaded modules.
Prompt:
“Convert this component to use the standalone API”
What Copilot does:
Updates component metadata with standalone: true, refactors imports, and removes old NgModule references.
Prompt:
“Add Angular Material”
What Copilot does:
Triggers ng add @angular/material to install the package and configure animations + theming.