Skip to main content

Code Standards

Coding Style

For the most part, we follow the official angular coding style guide

Git

Docs for our git workflow are in clickup

Unit Tests

Coverage expected in unit tests

Rights / Permissions

  • Test any permissions checks that your component performs, assert the appropriate state of your component for lack of permissions.
  • This includes popovers / tooltips are present if called out in the Acceptance Criteria (you do not have to interact with them and assert)

API Errors / Error Handling

  • Test API communication failure if applicable. Intercept api calls and force error scenarios, asserting your component handles them gracefully.
  • Ensure your component is still functional when an error is returned, and messaging is appropriate.
  • Use Angular's HTTPTestingController intercepting / mocking http calls. Doc here

Forms

  • Test form submission by calling your submit function with mocked values, don't try to interact with the form or click submit.
  • Ensure fields listed as required in the acceptance criteria are marked as required in your form.
  • Use the form element's css states checks to assert validation is correct. List of css states here

General

  • Unit test any functions / methods that change component state to ensure the function's output is correct for the given inputs. Refactor if necessary to get as close to pure functions as possible.

  • Do not test 3rd party code, this includes functionality provided by AgGrid(sorting / filtering of columns), the Bluewater component library, etc.

E2E Tests

Docs for writing E2E tests are in clickup

Feature Flags

Needs documentation

Injection Tokens

For managing state, prefer using lightweight injection tokens over services. You can find more info on what they are and how to use them here

Dates

Needs documentation

Http Calls and SignalR Hubs

NOTE (0.30.0): These docs are out of date. We use auto generated http clients now.

Fetching Data

Use the httpResource API introduced in angular 19.2.

export class PlaygroundComponent {
search = signal('')
products = httpResource<{ products: any[] }>(() => {
const search = this.search()
if (!search) return 'https://dummyjson.com/products'
return `https://dummyjson.com/products/search?q=${search}`
})
}

If you need to fetch data using something other than a GET request, you can return a http configuration object instead of the url.

productsViaPost = httpResource<{ products: any[] }>(() => {
const search = this.search()
return {
url: 'https://dummyjson.com/products/search',
method: 'POST',
body: { q: search || null }
}
})

Mutating Data

Directly inject and use the HttpClient. Rarely do we reuse mutation endpoints, so there's no benefit to trying to consolidate them. (ie the only place we use the AddBackflowDevice endpoint is in the AddBackflowDeviceComponent)

export class PlaygroundComponent {
http = inject(HttpClient)

addProduct(title: string) {
this.http.post<{id: number, title: string}>('https://dummyjson.com/products/add', { title }).subscribe(res => {
alert(`Product ${res.title} added`)
})
}
}

Interceptors

In the http configuration object, you can pass in a HttpContext object that the interceptors utilize to know if they should run or not.

export const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);

...

productsViaPost = httpResource<{ products: any[] }>(() => {
const search = this.search()
return {
url: 'https://dummyjson.com/products/search',
method: 'POST',
body: { q: search },
//enable the caching interceptor
context: new HttpContext().set(IS_CACHE_ENABLED, true)
}
})

SignalR Hubs

Use the HubSubject class to manage signalr connections. It extends the rxjs subject to keep things familiar. It keeps track of the hub connections globally and reuses them.

export class GeneralTabComponent {
private ucUrl = inject(API_URLS).utilityConfigurationApiUrl
private hub$ = new HubSubject(`${this.ucUrl}/Users/Patch`)

...

public bufferedQueue$ = createBufferedQueue({
subject: this.patches$,
concurrency: signal(0),
statuses: this.statuses,
patchCallback: patches => this.hub$.invoke<PatchResponse>('SendPatchV2', this.routerData().userId, patches),
});

...

ngOnInit() {
this.hub$.subscribe()
}

ngOnDestroy() {
this.hub$.complete()
}
}

//Pass in the messages you wish to listen to on the backend
const notifications$ = new HubSubject(`${this.umUrl}/Hub`, 'ReportNotification', 'ProcessNotification')

//Send a message to the backend
const query = {
accounts: null,
groupBy: 'Account number'
}
notifications$.next('GetDepositReport', query, JSON.stringify(query))

//Will emit for any of the messages you passed in constructor
notifications$.subscribe(res => {
if (res.method == 'ReportNotification' || res.method == 'ProcessNotification') {
//show toast
}
})

//You can skip the HubSubject if you just need to do a single method invoke on the server
import { fromHub } from 'data'

...

public submitParamForm() {
//fromHub will connect to the hub and close it for you
fromHub(`${this.umUrl}`, 'GetDepositReport', this.form, JSON.stringify(this.form)).subscribe(() => {
//show toast
})
}