Front End Coding Conventions
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
Convention
- Do use spaces to indent, 2 spaces per indentation level
Use:
function foo() {
if (true) {
return true;
}
}
Instead of:
function foo() {
if (true)
return true;
}
- Do separate lines for each Dependency Injection
Use:
class StoreComponent {
constructor(private http: HttpClient, private router: Router, public dialog: MatDialog, private cdr: ChangeDetectorRef) {}
}
Instead of:
class StoreComponent {
constructor(
private http: HttpClient,
private router: Router,
public dialog: MatDialog,
private cdr: ChangeDetectorRef
) { }
}
- Do use camelCase for variable names and function names
@Injectable()
export class StoreService {
storeId: string;
storeName: string;
storeAddress: string;
constructor(private http: HttpClient) {}
getStores() {
this.http.get('/api/stores').subscribe(data => {
this.stores = data;
});
}
}
- Do define one thing, such as a service or component or model, per file
In store.component.ts
:
export class StoreComponent {
constructor(private service: StoreService) {}
ngOnInit() {
this.service.getStores();
}
}
In store.service.ts
:
export class StoreService {
constructor(private http: HttpClient) {}
getStores() {
this.http.get('/api/stores').subscribe(data => {
this.stores = data;
});
}
}
In store.model.ts
:
export interface Store {
id: string;
name: string;
address: string;
}
- Do place properties up top followed by methods
export class StoreComponent {
// properties up top
config: Config;
employees: Employee[];
shifts: Shift[];
constructor(private service: StoreService) {}
// methods down below
editStore(store: Store) {
this.router.navigate(['/store', store.id]);
}
deleteStore(store: Store) {
this.service.deleteStore(store.id).subscribe(() => {
this.service.getStores();
});
}
}
- Do place private members after public members, alphabetized
export class StoreComponent {
// public members
config: Config;
// private members
private balance: number;
private employees: Employee[];
}
- Prefer to use
const
overlet
. Don’t usevar
unless you have to - Clean up imports with path aliases or group by folder/functionality
// Angular
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormControl, FormGroup } from '@angular/forms';
import { MatSelectChange } from '@angular/material/select';
// RxJS
import { BehaviorSubject, of, Subject } from 'rxjs';
import { catchError, debounceTime, filter, skip, startWith, switchMap, takeUntil, tap } from 'rxjs/operators';
// Services
import { AuthService } from 'app/core/auth/auth.service';
import { StoreService } from './store.service';
// Models
import { Store, Employee } from 'app/core/models';
// Utils
import * as moment from 'moment';
- Do use suffix
$
for Observables
export class HeroComponent {
heros$: BehaviorSubject<Hero[]>;
private readonly destroy$ = new Subject();
}
- Do use prefix
_
for private members
export class HeroComponent {
private _heros$: BehaviorSubject<Hero[]>;
private _getHeros() {
this._heros$.next(this.heroService.getHeros());
}
}
- Do move reusable logic to services and keep components simple and focused on their intended purpose
- Consider using css variables to define colors, for easier customization through themes
- Place all reusable components/functions in a
shared
folder
HTML Format
- Line-Wrapping, you may consider wrapping long lines if it significantly improves readability. If the line is not too long, you can leave it as one line.
<!-- bad -->
<input matInput type="text" autocomplete="off" required [blsMaxNumber]="100" mask="separator.2" thousandSeparator="," [dropSpeΩcialCharacters]="true" [validation]="false" formControlName="value" />
<!-- bad -->
<input matInput type="text" autocomplete="off" required [blsMaxNumber]="100" mask="separator.2" thousandSeparator="," [dropSpeΩcialCharacters]="true"
[validation]="false" formControlName="value" />
<!-- good -->
<input
matInput required
type="text" autocomplete="off"
formControlName="value"
mask="separator.2"
thousandSeparator=","
[validation]="false"
[dropSpecialCharacters]="true"
[blsMaxNumber]="100"
/>
- Order/group attributes and bindings
- Structural directives (ngFor, ngIf, etc.)
- Animation triggers (@fade, [@fade])
- Element reference (#myComponent)
- HTML attributes (class, etc.)
- Non-interpolated string inputs and attributes (foo=“bar”)
- Interpolated inputs ([bar]=“theBar”)
- Two-way bindings ([(ngModel)]=“email”)
- Outputs ((click)=“onClick()”)
<!-- bad -->
<my-app-component
(click)="onClick($event)"
[(ngModel)]="fooBar"
class="foo"
#myComponent
[bar]="theBar"
@fade
foo="bar"
*ngIf="shouldShow"
(someEvent)="onSomeEvent($event)"
></my-app-component>
<!-- good -->
<my-app-component
*ngIf="shouldShow"
@fade
#myComponent
class="foo"
foo="bar"
[bar]="theBar"
[(ngModel)]="fooBar"
(click)="onClick($event)"
(someEvent)="onSomeEvent($event)"
></my-app-component>
Naming
File and Directory names
While adding new files, pay attention to the file-names you decide to use. File names should be consistent and describe the feature by dot separation.
All file name should be in lowercase
.
|-- my-feature.component.ts
or
|-- my-service.service.ts
Variable- and function names
You need to name the variables and functions so the next developer understands them. Be descriptive and use meaningful
names — clarity
over brevity.
This will help us avoid writing functions like this:
function div(x, y)) {
const val = x / y;
return val;
}
And hopefully more like this:
function divide(divident, divisor) {
const quotient = divident / divisor;
return quotient;
}
Write Small pure functions
When we write functions to execute some business logic, we should keep them small and clean. Small functions are easier to test and maintain. When you start noticing that your function is getting long and cluttered, it’s probably a good idea to abstract some of the logic to a new one.
Avoid code comments
Although there are cases for comments, you should really try to avoid them. You don’t want your comments to compensate for your failure to express the message in your code. Comments should be updated as code is updated, but a better use of time would be to write code that explains itself. Inaccurate comments are worse than no comments at all.
Code never lies, comments do.
RxJS in Angular
Pipeable operators
You should seperate each operator into lines.
const name = this.loadEmployees()
.pipe(
map(employee => employee.name),
catchError(error => of(null))
);
Avoid memory leaks
While Angular takes care of unsubscribing when using the async pipe, it quickly becomes a mess when we have to do this on our own. Failing to unsubscribe will lead to memory leaks, as the observable stream is left open.
The solution is to compose our subscription with the takeUntil operator, and use a subject that emits a value when the component gets destroyed. This will complete the observable-chain, causing the stream to unsubscribe.
This help us avoid writing code like this:
this.itemService.findItems()
.pipe(
map((items: Item[]) => items),
).subscribe()
And hopefully more like this:
private unsubscribe$: Subject<void> = new Subject<void>();
...
this.itemService.findItems()
.pipe(
map(value => value.item)
takeUntil(this._destroyed$)
)
.subscribe();
...
public ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
this.unsubscribe$.unsubscribe();
}
Don’t use nested subscriptions
There may be situations where you need to consume data from multiple observable streams. In those cases, you should generally try to avoid socalled nested subscriptions. Nested subscriptions becomes hard to understand and may introduce unexpected side effects. We should instead use chainable methods like switchMap, forkJoin and combineLatest to condense our code.
This will help us avoid writing code like this:
this.returnsObservable1(...)
.subscribe(
success => {
this.returnsObservable2(...)
.subscribe(
success => {
this.returnsObservable3(...)
.subscribe(
success => {
...
},
And hopefully more like this:
this.returnsObservable1(...)
.pipe(
flatMap(success => this.returnObservable2(...),
flatMap(success => this.returnObservable3(...)
)
.subscribe(success => {...});
Visual Studio Code Extensions
If you are using Visual Studio Code
, install these extensions to quickly find problems: