Skip to main content

Angular

At Bitwarden we use Angular as our client side framework. It's recommended to have a basic understanding of Angular before continuing reading, and when uncertain please refer to the official Angular Docs or the Getting started with Angular. We also make an effort at following the Angular Style Guide.

This document aims to cover the best practices we follow. Many of them are originally based on different ADRs, however while ADRs are good at describing the why, they provide a suboptimal reading experience.

Naming (ADR-0012)

We follow the Naming section from the Angular Style Guide. More specifically use dashes to septate words in the descriptive name, and dots to separate name from the type.

We use the following conventional suffixes suggested by Style-02-01:

  • service - Service (At Bitwarden this type denotes an abstract service)
  • component - Angular Components
  • pipe - Angular Pipe
  • module - Angular Module
  • directive - Angular Directive

At Bitwarden we also use a couple of more types:

  • .api - Api Model
  • .data - Data Model (used for serializing domain model)
  • .view - View Model (decrypted domain model)
  • .export - Export Model
  • .request - Api Request
  • .response - Api Response
  • .type - Enum
  • .service.implementation - Implementation of an abstract service

The class names are expected to use the suffix as part of their class name as well. I.e. a service implementation will be named FolderServiceImplementation, a request model will be named FolderRequest.

Since abstracts are referenced far more frequently than implementations, they use the simplified type while implementations must specify they are implementations e.g .service for the abstract vs .service.implementation for the implantation.

Observables (ADR-0003)

We are currently in the middle of a migration towards reactive data layer using RxJS. What this essentially means is that a component subscribes to a state service, e.g. FolderService and will continually get updates should the data ever change. This ensures that the components always stay up to date.

Previously we manually implemented an event system for sending basic messages which told other components to reload their state. This was error prone, and hard to maintain.

// Example component which displays a list of folders
@Component({
selector: "app-folders-list",
template: `
<ul>
<li *ngFor="let folder of folders$ | async">
{{ folder.name }}
</li>
</ul>
`,
})
export class FoldersListComponent {
folders$: Observable<FolderView[]>;

constructor(private folderService: FolderService) {}

ngOnInit() {
this.folders$ = this.folderService.folderViews$;
}
}

Reactive Forms (ADR-0001)

We almost exclusively use Angular Reactive forms instead of Template Driven forms. And the Bitwarden Component library is designed to integrate with Reactive forms.

Provide direct, explicit access to the underlying form's object model. Compared to template-driven forms, they are more robust: they're more scalable, reusable, and testable. If forms are a key part of your application, or you're already using reactive patterns for building your application, use reactive forms.

https://angular.io/guide/forms-overview#choosing-an-approach

Thin Components

Components should be thin and only contain the logic required to render the view. All other logic belongs to services. This way components that behave almost identical but looks quite different visually can avoid code duplication by sharing the same service. Services tends to be much easier to test than components as well.

Composition over Inheritance (ADR-0009)

Due to the multi client nature of Bitwarden, we have a lot of shared components with slightly different behavior client to client. Traditionally this was implemented using inheritance, however as the application continues to grow this has resulted in large components that are difficult to work with, as well as a multi level inheritance tree.

To avoid this, components should be broken up into logical standalone pieces. The different clients should use the the shared components by customizing the page level components.

plantuml

Understandably this is difficulty to properly implement when the different clients use different css frameworks, which until everything is properly migrated to Bootstrap means there will be a Login Component for each client that extends the generic Login Component. However it should only expose a client specific template.

The diagram below showcases how the reports share logic by extracting the list into a shared component. Instead of the organization component directly extending the base report page component. For more details, read the PR for the refactor.

plantuml