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
Needs documentation
E2E Tests
Docs for writing E2E tests are in clickup
Feature Flags
Needs documentation
Injection Tokens
Needs documentation
Dates
Needs documentation
Http Calls and SignalR Hubs
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
})
}