-
Adhere to English rules (grammar, spelling,...)
-
Variable name must be self-explanatory and describe the stored value.
-
Variable name must be nouns.
-
Function name must be verbs.
-
Using
camelCase
for identifier names (for both variable and function) -
Always use the lowercase version with primitive types
string
,boolean
, andnumber
instead ofString
,Boolean
, andNumber
-
Interface must be end with
Model
.
interface User {
firstName: string;
lastName: string;
}
must be
interface UserModel {
firstName: string;
lastName: string;
}
- Use
UpperCamelCase
for module-level constants and enum values, andlowerCamelCase
for constants or variables instantiated multiple times.
// Module-level constant
const PageSizes = [0,5,10]
// Constants or variables that can be instantiated multiple times
const pageSizes = [0,5,10]
- Prefer
interfaces
overtype literal aliases
when defining the shape of objects
// Using an interface
interface User {
firstName: string;
lastName: string;
}
// Using a type alias (not recommended for objects)
type User = { firstName: string; lastName: string; }
-
Prefer using the ternary operator (
? :
) overif-else
for concise conditional expressions. -
Private variables must have
#
prefix from the ES2022.
However, while ES2021 or less, TS will use WeakMap
in place of #
so using TS private
and _
prefix combination instead.
// Latest ES (from 2022)
readonly #fixedHeight = 10;
#userName: string;
// ES2021 or less
private readonly _fixedHeight = 10;
private _userName: string;
- Boolean variables must begin with has/is. For example:
public isLoaded = false;
public hasInnerText = true;
- Variable with type Map, Set must end with Map, Set respectively. For example:
public readonly locationMap = new Map([]);
public readonly uniqueDataSet = new Set([]);
- Variable with Array type must be in plural form. For Example:
public readonly locations: string[] = [];
- Variables without initial value must have pre-defined type. For example:
public isLoaded: boolean;
public locations: string[];
public locationMap: Map;
- Avoid to using
any
as variable type. If there is an unknown property of interface or type, useunkown
instead.
If the unknown type is an object, Record
type must be used instead of Object
type.
-
For empty object, do not use
{}
symbol, usingRecond<string, never>
instead. -
Don't use
function
in template. -
Use effect less, try signals in computed.
-
Observable
andSubject
must end with$
suffix.
public users$: Observable<User[]>;
Subject
should beprivate readonly
and they should expose viaasObservable()
if needed:
readonly #sampleSuject$ = new Subject<void>();
public sampleSubject$ = this.#sampleSuject$.asObservable();
- When
Subscribe
a stream, passed anObserver
is recommended:
this.sampleSubject$.subscribe({
next: () => { // do somthing },
error: () => { // do somthing },
complete: () => { // do somthing }
})
- Using
noop
for empty callback.
import { noop } from 'rxjs';
navigator.clipboard.writeText('Writing Data').then(noop);
-
Prefer to use Standalone component over
NgModule
. -
Prefer to use signal
input
,output
andmodel
instead of@Input
and@Output
decorator. -
All class methods and functions must have return type:
-
Variables ordering must follow the rule:
-
Private readonly variables
-
Private variables
-
protected readonly variables
-
protected variables
-
Public readonly variables
-
Public variables
-
Class methods ordering must follow the rule:
-
Constructor
-
Angular component lifecycle methods
-
ngOnChanges
-
ngOnInit
-
ngDoCheck
-
ngAfterViewInit
-
ngAfterViewChecked
-
ngAfterContentInit
-
ngAfterContentChecked
-
Public methods
-
Private methods
-
Protected methods
@Component({
standalone: true,
selector: 'example',
templateUrl: './example.component.html',
styleUrl './example.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Example implements OnChanges, OnInit {
readonly #innerIsLoaded$ = new BehaviorSubject<boolean>(false);
readonly #salaryRange = 1000;
@ViewChild(SomeService) #someService: SomeService;
@ViewChildren(SomeService) #someServices: QueryList<SomeService>;
#isDirector: boolean;
protected readonly salaryFormName = 'Salary Form';
protected hasMarker: boolean;
public readonly isLoaded$: Observable<boolean> = this.innerIsLoaded$.asObservable();
public readonly formGroup = new FormGroup({});
public assignedColor: Color;
set positionName(value: string) {
// Implementation here
}
get positionName(): string {
// Implementation here
}
@Input() public hasBorder = false;
@Output() public positionChanged = new EventEmitter();
@HostBinding('class.dark')
public isDarkTheme = true;
public constructor() {}
public ngOnChanges(): void {
// Do something
this.doSomethingElse();
}
public ngOnInit(): void {
// Implementation here
this.doSomething()
}
@HostListener('click', '$event.target')
public hideInput(element: HTMLElement): void {
// Implementation here
}
public changePosition(): void {
// Do something
this.fireSomeone();
}
public fireSomeone(): void {
// Implementation here
}
private doSomething(): void {
// Implementation here
}
private doSomethingElse(): void {
// Implementation here
}
}