This guide represents coding styles used when writing ES2017+ and TypeScript, specifically when working with Angular 5+. This guide takes heavily from AirBnb's ES Style Guide, but with some parts removed or modified, generally when they conflicted with the Official Angular Style Guide, which takes priority.
Wherever possible these best practices are enforced with ESLint. Violations that are likely to result in bugs are marked as errors, and code style / consistency violations are marked as warnings.
- TypeScript Specific Guidelines
- Variables
- Objects
- Use the literal syntax for object creation
- Use computed property names when creating objects with dynamic property names
- Use object method shorthand
- Use property value shorthand
- Group shorthand properties at the beginning
- Only quote invalid identifiers
- Do not call
Object.prototype
methods directly - Prefer the object spread operator over
Object.assign
- Arrays
- Destructuring
- Strings
- Functions
- Wrap immediately invoked function expressions in parentheses
- Never declare a function in a non-function block
- Never name a parameter `arguments'
- Never use
arguments
, use rest syntax...
instead - Use default parameter syntax
- Avoid side effects with default parameters
- Put default parameters last
- Never use the Function constructor
- Use consistent spacing in signiature
- Never mutate parameters
- Never reassign parameters
- Prefer spread operator
...
to call variadic functions
- Arrow Functions
- Iterators and Generators
- Properties
- Comparison Operators & Equality
- Use
===
and!==
over==
and!=
- Truthiness
- Use shortcuts for booleans, but explicit comparisons for strings and numbers
- Use braces to create blocks in
case
anddefault
clauses that contain lexical declarations - Ternaries should not be nested and generally be single line expressions
- Avoid unneeded ternary statements
- Use
- Blocks
- Comments
- Whitespace
- Use soft tabs set to 2 spaces
- Place 1 space before the leading brace
- Place 1 space before the opening parenthesis in control statements
- Set off operators with spaces
- End files with a single newline character
- Use indentation when making long method chains
- Leave a blank line after blocks and before the next statement
- Use indentation when making long method chains
- Leave a blank line after blocks and before the next statement
- Do not pad your blocks with blank lines
- Do not add spaces inside parentheses
- Do not add spaces inside brackets
- Add spaces inside curly braces
- Avoid having lines of code that are longer than 100 characters
- Commas
- Semicolons
- Type Casting & Coercion
- Naming Conventions
- Asynchronous
Why? Declaring types allows TypeScript to catch possible bugs, and provides a more explicit interface to other developers using your code
// bad
let myArray;
// bad
const myArray = [];
// good
const myArray: string[] = [];
// good, type isn't needed as it can be inferred by the value the variable is initialized to
const isGood = true;
Why? See previous rule,
any
does not enforce any typing
Remember you can always define an inline type for simple objects with only one or two properties. Create a class or interface for more complex objects.
// bad
const myObj: any = { property: '' };
// good
const myObj: MyObject = { property: '' }
// good, object type is simple enough for just an inline type literal
const myObj: { property: string } = { property: ''};
Why? The TypeScript compiler will catch access violations automatically, and IDE integration and class definitions let developers know when a property is private. The underscore is not needed.
The exception to this rule is if you want a getter and a setter with the same name
// bad
class MyClass {
private _myName
}
// good
class MyClass {
private myName;
}
// good, our private property has getters and setters with the same name
class MyClass {
private _myName;
get myName() {
return this._myName;
}
set myName(newName) {
this._myName = newName;
}
}
Why? Consistency, we don't use Hungarian Notation anywhere else, we don't put 'C' in front of classes or 'A' in front of abstract classes.
Why? Encourages better naming
See this thorough StackOverflow explanation
// bad
interface ISubaru {
centerDifferential;
frontDifferential;
rearDifferential;
clutch;
fiveSpeedGearbox;
}
class Subaru implements ISubaru {
}
// good
interface ManualTransmission {
fiveSpeedGearbox;
clutch;
}
interface AllWheelDrive {
centerDifferential;
frontDifferential;
rearDifferential;
}
class Subaru implements ManualTransmission, AllWheelDrive {
}
Why? Makes all class/interface definitions easy to find and reuse.
If you need very simple types remember you can declare them inline const myObj: { property: string } = { property: ''};
. For anything with more than two properties create a new file to hold the class/interface.
Why? Properties and methods are public by default, just as properties and methods are in vanilla JavaScript. Easier to differentiate between public and private
// bad
class MyClass {
public firstName;
public lastName;
private ssn;
public getFullName() {
return `${firstName} ${lastName}`
}
}
// good
class MyClass {
firstName;
lastName;
private ssn;
getFullName() {
return `${firstName} ${lastName}`
}
}
Why? Ensures you cannot reassign your references, which can lead to unexpected bugs. By using
const
everywhere a variable isn't reassigned, it makes it very obvious when a variable will be reassigned.
// bad
var a = 1;
var b = 2;
// good
const a = 1;
const b = 2;
// I immediately know this variable will be reassigned later without having look at any more code
let c = 1;
Why?
let
andconst
are block scoped butvar
is function scoped (or global if not declared inside a function). Not using block scoped variables can lead to unexpected behavior and bugs.
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}
Why? It's easier to add new variable declarations this way, and you never have to worry about swapping out a
;
for a,
or introducing punctuation-only diffs. You can also step through each declaration with the debugger, instead of jumping through all of them at once.
// bad
const items: number[] = getItems(),
goSportsTeam = true,
dragonball = 'z';
// bad
// (compare to above, and try to spot the mistake)
const items: number[] = getItems(),
goSportsTeam = true;
dragonball = 'z';
// good
const items: number[] = getItems();
const goSportsTeam = true;
const dragonball = 'z';
Why? This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables.
// bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
Why?
let
andconst
are block scoped and not function scoped.
// bad - unnecessary function call
function checkName(hasName: string) {
const name = getName();
if (hasName === 'test') {
return false;
}
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
// good
function checkName(hasName: string) {
if (hasName === 'test') {
return false;
}
const name: string = getName();
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
Why? Keeps consistent syntax when creating whether you're creating an empty object or an object with properties. Easier to read.
// bad
const item: any = new Object();
// good
const item: any = {};
Why? They allow you to define all the properties of an object in one place.
function getKey(k): string {
return `a key named ${k}`;
}
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
Why? It is shorter to write and descriptive.
// bad
const atom = {
value: 1,
addValue: function (value: number): number {
return atom.value + value;
},
};
// good
const atom = {
value: 1,
addValue(value: number): number {
return atom.value + value;
},
};
Why? It is shorter to write and descriptive.
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker,
};
Why? It's easier to tell which properties are using the shorthand.
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
episodeOne: 1,
twoJediWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker,
};
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};
Why? In general we consider it subjectively easier to read. It improves syntax highlighting, and is also more easily optimized by many JS engines.
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};
Why? These methods may be shadowed by properties on the object in question - consider
{ hasOwnProperty: false }
- or, the object may be a null object (Object.create(null)
).
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object, key));
// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
Why? Easier to read, more descriptive of what's happening
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
Why? Keeps consistent syntax when creating whether you're creating an empty array or an array with items in it. Easier to read.
// bad
const items: any[] = new Array();
// good
const items: any[] = [];
const someStack: string[] = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
Why? It is shorter to write and descriptive.
// bad
const len = items.length;
const itemsCopy = [];
let i: number;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
Why? Array callbacks expect a return method, not doing so could lead to strange bugs and confusing code. When using single line arrow function the return is implicit so is not needed.
// good
[1, 2, 3].map(x => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map(x => x + 1);
// bad
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[index] = flatten;
});
// good
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[index] = flatten;
return flatten;
});
// bad
inbox.filter((msg) => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
} else {
return false;
}
});
// good
inbox.filter(msg => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
}
return false;
});
Why? Destructuring saves you from creating temporary references for those properties.
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
Why? You can add new properties over time or change the order of things without breaking call sites.
// bad
function processInput(input): number[] {
// then a miracle occurs
return [left, right, top, bottom];
}
// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);
// good
function processInput(input): Rectangle {
// then a miracle occurs
return { left, right, top, bottom };
}
// the caller selects only the data they need
const { left, top } = processInput(input);
// bad
const name = "Capt. Janeway";
// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';
Why? Broken strings are painful to work with and make code less searchable.
// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';
// bad
const errorMessage = 'This is a super long error that was thrown because ' +
'of Batman. When you stop to think about how Batman had anything to do ' +
'with this, you would get nowhere fast.';
// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
Why? Template strings give you a readable, concise syntax with proper newlines and string interpolation features.
// bad
function sayHi(name: string): string {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name: string): string {
return ['How are you, ', name, '?'].join();
}
// bad
function sayHi(name: string): string {
return `How are you, ${ name }?`;
}
// good
function sayHi(name: string): string {
return `How are you, ${name}?`;
}
Why? It opens to many vulnerabilities.
Why? Backslashes harm readability, thus they should only be present when necessary.
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
const foo = `'this' is "quoted"`;
Assign the function to a variable instead. Browsers will allow you to do it, but they all interpret it differently, which is bad news bears.
Note: ECMA-262 defines a block
as a list of statements. A function declaration is not a statement. Read ECMA-262's note on this issue.
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// less bad
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
// best
let test;
if (currentUser) {
test = logYup;
}
function logYup() {
console.log('Yup.');
}
This will take precedence over the
arguments
object that is given to every function scope.
// bad
function nope(name, options, arguments) {
// ...stuff...
}
// good
function yup(name, options, args) {
// ...stuff...
}
Why?
...
is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like likearguments
.
// bad
function concatenateAll(): string {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args): string {
return args.join('');
}
// really bad
function handleThings(opts) {
// No! We shouldn't mutate function arguments.
// Double bad: if opts is falsy it'll be set to an object which may
// be what you want but it can introduce subtle bugs.
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
Why? They are confusing to reason about.
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
Why? Creating a function in this way evaluates a string similarly to eval(), which opens vulnerabilities.
// bad
var add = new Function('a', 'b', 'return a + b');
// still bad
var subtract = Function('a', 'b', 'return a - b');
Why? Consistency is good, and you shouldn’t have to add or remove a space when adding or removing a name.
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};
Why? Manipulating objects passed in as parameters can cause unwanted variable side effects in the original caller.
// bad
function f1(obj) {
obj.key = 1;
};
// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
};
Why? Reassigning parameters can lead to unexpected behavior, especially when accessing the
arguments
object. It can also cause optimization issues, especially in V8.
// bad
function f1(a) {
a = 1;
}
function f2(a) {
if (!a) { a = 1; }
}
// good
function f3(a) {
const b = a || 1;
}
function f4(a = 1) {
}
Why? It's cleaner, you don't need to supply a context, and you can not easily compose
new
withapply
.
// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);
// good
const x = [1, 2, 3, 4, 5];
console.log(...x);
// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 08, 05]));
// good
new Date(...[2016, 08, 05]);
Note: If your function is longer than one line, consider moving the logic to a named function
Why? It creates a version of the function that executes in the context of
this
, which is usually what you want, and is a more concise syntax.
Why not? If you have a fairly complicated function, move the logic into it's own function provides better readability
// bad
[1, 2, 3].map(function (item) {
const itemPlusOne = item + 1;
return item * itemPlusOne;
});
// good
[1, 2, 3].map(item => {
const itemPlusOne = item + 1;
return item * itemPlusOne;
});
// good
[1, 2, 3].map(timesItselfPlusOne);
function timesItselfPlusOne(item) {
const itemPlusOne = item + 1;
return item * itemPlusOne;
}
Why? Syntactic sugar. It reads well when multiple functions are chained together.
// bad
[1, 2, 3].map(number => {
const nextNumber = number + 1;
`A string containing the ${nextNumber}.`;
});
// good
[1, 2, 3].map(number => `A string containing the ${number}.`);
// good
[1, 2, 3].map((number) => {
const nextNumber = number + 1;
return `A string containing the ${nextNumber}.`;
});
// good
[1, 2, 3].map((number, index) => ({
[index]: number
}));
Why? Less visual clutter.
// bad
[1, 2, 3].map((x) => x * x);
// good
[1, 2, 3].map(x => x * x);
// good
[1, 2, 3].map(number => (
`A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
));
// good
[1, 2, 3].map(x => {
const y = x + 1;
return x * y;
});
Why? Placing the arrow function and a comparision operator on the same line is difficult to read.
// bad
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
// bad
const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
// good
const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
// good
const itemHeight = (item) => {
const { height, largeSize, smallSize } = item;
return height > 256 ? largeSize : smallSize;
};
Why? If you can pass a function in directly, do it, creating an empty wrapper function clutters the code and provides no gain. Directly passing a well named function greatly increases readability.
// bad
items.map(item => {
return Math.sqrt(item);
});
// bad
items.map(item => Math.sqrt(item));
// bad
getUsernames()
.then(usernames => {
processUsernames(usernames);
});
// bad
getUsernames()
.then(usernames => processUsernames(usernames));
// good
items.map(Math.sqrt);
// good
getUsernames()
.then(processUsernames);
Why? This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.
Use
map()
/every()
/filter()
/find()
/findIndex()
/reduce()
/some()
/ ... to iterate over arrays, andObject.keys()
/Object.values()
/Object.entries()
to produce arrays so you can iterate over objects.
const numbers = [1, 2, 3, 4, 5];
// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;
// good
let sum = 0;
numbers.forEach(num => sum += num);
sum === 15;
// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;
Why?
function
and*
are part of the same conceptual keyword -*
is not a modifier forfunction
,function*
is a unique construct, different fromfunction
.
// bad
function * foo() {
}
const bar = function * () {
}
const baz = function *() {
}
const quux = function*() {
}
function*foo() {
}
function *foo() {
}
// very bad
function
*
foo() {
}
const wat = function
*
() {
}
// good
function* foo() {
}
const foo = function* () {
}
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
Conditional statements such as the
if
statement evaluate their expression using coercion with theToBoolean
abstract method and always follow these simple rules:
+ **Objects** evaluate to **true**
+ **Undefined** evaluates to **false**
+ **Null** evaluates to **false**
+ **Booleans** evaluate to **the value of the boolean**
+ **Numbers** evaluate to **false** if **+0, -0, or NaN**, otherwise **true**
+ **Strings** evaluate to **false** if an empty string `''`, otherwise **true**
if ([0] && []) {
// true
// an array (even an empty one) is an object, objects will evaluate to true
}
// bad
if (isValid === true) {
// ...stuff...
}
// good
if (isValid) {
// ...stuff...
}
// bad
if (name) {
// ...stuff...
}
// good
if (name !== '') {
// ...stuff...
}
// bad
if (collection.length) {
// ...stuff...
}
// good
if (collection.length > 0) {
// ...stuff...
}
- For more information see Truth Equality and JavaScript by Angus Croll.
Why? Lexical declarations are visible in the entire
switch
block but only get initialized when assigned, which only happens when itscase
is reached. This causes problems when multiplecase
clauses attempt to define the same thing.
// bad
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
case 3:
function f() {}
break;
default:
class C {}
}
// good
switch (foo) {
case 1: {
let x = 1;
break;
}
case 2: {
const y = 2;
break;
}
case 3: {
function f() {}
break;
}
case 4:
bar();
break;
default: {
class C {}
}
}
// bad
const foo = maybe1 > maybe2
? "bar"
: value1 > value2 ? "baz" : null;
// better
const maybeNull = value1 > value2 ? 'baz' : null;
const foo = maybe1 > maybe2
? 'bar'
: maybeNull;
// best
const maybeNull = value1 > value2 ? 'baz' : null;
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
// good
const foo = a || b;
const bar = !!c;
const baz = !c;
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function foo() { return false; }
// good
function bar(): boolean {
return false;
}
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
// ...stuff...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...stuff...
return element;
}
Place single line comments on a newline above the subject of the comment. Put an empty line before the comment unless it's on the first line of a block.
// bad
const active = true; // is current tab
// good
// is current tab
const active = true;
// bad
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
// good
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
// also good
function getType() {
// set the default type to 'no type'
const type = this._type || 'no type';
return type;
}
Why? Makes comments easier to read
// bad
//is current tab
const active = true;
// good
// is current tab
const active = true;
// bad
/**
*make() returns a new element
*based on the passed-in tag name
*/
function make(tag) {
// ...stuff...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...stuff...
return element;
}
Prefixing your comments with
FIXME
orTODO
helps other developers quickly understand if you're pointing out a problem that needs to be revisited, or if you're suggesting a solution to the problem that needs to be implemented. These are different than regular comments because they are actionable. The actions areFIXME: -- need to figure this out
orTODO: -- need to implement
.
// bad
function foo() {
∙∙∙∙const name;
}
// bad
function bar() {
∙const name;
}
// good
function baz() {
∙∙const name;
}
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}
// bad
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog',
});
// good
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog',
});
Place no space between the argument list and the function name in function calls and declarations
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}
// bad
function fight () {
console.log ('Swooosh!');
}
// good
function fight() {
console.log('Swooosh!');
}
// bad
const x=y+5;
// good
const x = y + 5;
// bad
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;
// bad
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;↵
↵
// good
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;↵
Use when more than 2 method chains. Use a leading dot, which emphasizes that the line is a method call, not a new statement
// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();
// bad
$('#items').
find('.selected').
highlight().
end().
find('.open').
updateCount();
// good
$('#items')
.find('.selected')
.highlight()
.end()
.find('.open')
.updateCount();
// bad
const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true)
.attr('width', (radius + margin) * 2).append('svg:g')
.attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
.call(tron.led);
// good
const leds = stage.selectAll('.led')
.data(data)
.enter().append('svg:svg')
.classed('led', true)
.attr('width', (radius + margin) * 2)
.append('svg:g')
.attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
.call(tron.led);
// good
const leds = stage.selectAll('.led').data(data);
// bad
if (foo) {
return bar;
}
return baz;
// good
if (foo) {
return bar;
}
return baz;
// bad
const obj = {
foo() {
},
bar() {
},
};
return obj;
// good
const obj = {
foo() {
},
bar() {
},
};
return obj;
// bad
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
// good
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
// bad
function bar() {
console.log(foo);
}
// also bad
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
// good
function bar() {
console.log(foo);
}
// good
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
// bad
function bar( foo ) {
return foo;
}
// good
function bar(foo) {
return foo;
}
// bad
if ( foo ) {
console.log(foo);
}
// good
if (foo) {
console.log(foo);
}
// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);
// good
const foo = [1, 2, 3];
console.log(foo[0]);
// bad
const foo = {clark: 'kent'};
// good
const foo = { clark: 'kent' };
Note: long strings are exempt from this rule, and should not be broken up
Why? This ensures readability and maintainability.
// bad
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
// bad
$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
// good
const foo = jsonData
&& jsonData.foo
&& jsonData.foo.bar
&& jsonData.foo.bar.baz
&& jsonData.foo.bar.baz.quux
&& jsonData.foo.bar.baz.quux.xyzzy;
// good
$.ajax({
method: 'POST',
url: 'https://airbnb.com/',
data: { name: 'John' },
})
.done(() => console.log('Congratulations!'))
.fail(() => console.log('You have failed this city.'));
// bad
const story = [
once
, upon
, aTime
];
// good
const story = [
once,
upon,
aTime,
];
// bad
const hero = {
firstName: 'Ada'
, lastName: 'Lovelace'
, birthYear: 1815
, superPower: 'computers'
};
// good
const hero = {
firstName: 'Ada',
lastName: 'Lovelace',
birthYear: 1815,
superPower: 'computers',
};
Why? This leads to cleaner git diffs. Also, transpilers like Babel will remove the additional trailing comma in the transpiled code which means you don't have to worry about the trailing comma problem in legacy browsers.
// bad - git diff without trailing comma
const hero = {
firstName: 'Florence',
- lastName: 'Nightingale'
+ lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing']
};
// good - git diff with trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing'],
};
// bad
const hero = {
firstName: 'Dana',
lastName: 'Scully'
};
const heroes = [
'Batman',
'Superman'
];
// good
const hero = {
firstName: 'Dana',
lastName: 'Scully',
};
const heroes = [
'Batman',
'Superman',
];
// bad
function createHero(
firstName,
lastName,
inventorOf
) {
// does nothing
}
// good
function createHero(
firstName,
lastName,
inventorOf,
) {
// does nothing
}
// good (note that a comma must not appear after a "rest" element)
function createHero(
firstName,
lastName,
inventorOf,
...heroArgs
) {
// does nothing
}
// bad
createHero(
firstName,
lastName,
inventorOf
);
// good
createHero(
firstName,
lastName,
inventorOf,
);
// good (note that a comma must not appear after a "rest" element)
createHero(
firstName,
lastName,
inventorOf,
...heroArgs
)
Yup.
// bad
(function () {
const name = 'Skywalker'
return name
})()
// good
(function () {
const name = 'Skywalker';
return name;
}());
// good, but legacy (guards against the function becoming an argument when two files with IIFEs are concatenated)
;(() => {
const name = 'Skywalker';
return name;
}());
-
// => this.reviewScore = 9; // bad const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf() // bad const totalScore = this.reviewScore.toString(); // isn't guaranteed to return a string // good const totalScore = String(this.reviewScore);
-
const inputValue = '4'; // bad const val = new Number(inputValue); // bad const val = +inputValue; // bad const val = inputValue >> 0; // bad const val = parseInt(inputValue); // good const val = Number(inputValue); // good const val = parseInt(inputValue, 10);
-
const age = 0; // bad const hasAge = new Boolean(age); // good const hasAge = Boolean(age); // best const hasAge = !!age;
// bad
function q() {
// ...stuff...
}
// good
function queryDatabase() {
// ..stuff..
}
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
Why? JavaScript does not have the concept of privacy in terms of properties or methods. Although a leading underscore is a common convention to mean “private”, in fact, these properties are fully public, and as such, are part of your public API contract. This convention might lead developers to wrongly think that a change won't count as breaking, or that tests aren't needed. tl;dr: if you want something to be “private”, it must not be observably present.
// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';
// good
this.firstName = 'Panda';
The exception to this rule when you have a private property in your class but want identically named getters and setters for it
class MyClass {
private _name: string;
get name(): string {
return _name;
}
set name(newName: string): void {
this._name = newName;
}
}
Why? Promises are part of the regular JavaScript / Typescript language which means their behavior is more likely to remain stable (no breaking changes). Promises also allow the use of async/await, which is nicer to read.
This rule does not mean you should not use Observables, its simply about using the right tool for the job. Observables have many great features, and if you need them use an Observable, it's very easy to convert between Observables and Promises. Many projects however can easily just use Angular event handling and Promises for easier to read and more stable code (for instance RXJS 5 -> 6 introduced breaking changes).
// bad
const source = Rx.Observable.of('Hello');
const second = val => Rx.Observable.of(`${val} World`);
const error = val => Rx.Observable.throw('Error!');
function printHelloWorld() {
const example = source
.mergeMap(second)
.mergeMap(error)
.catch(val => Rx.Observable.of(`I caught: ${val}`));
const subscribe = example.subscribe(val => console.log(val));
}
// good
const getHello = () => new Promise(resolve => resolve('Hello'));
const getWorld = val => new Promise(resolve => resolve(`${val} World`));
const error = () => new Promise((resolve, reject) => reject('Error!'));
async function printHelloWorld() {
try {
const hello = await getHello();
const helloWorld = await getWorld(hello);
await error();
console.log(helloWorld);
} catch(error) {
console.log(`I caught: ${error}`)
}
}
Why? Async await is easy to read and allows synchronous and asynchronous code and error handling to work seamlessly together.
const getUsers = () => new Promise(resolve => resolve(['rob', 'jon', 'adam']));
const getArticles = () => new Promise(resolve => resolve(['angular', '.net', 'java']));
const save = favorites => new Promise(resolve => resolve('Saved!'));
// bad
function setFavorites() {
Promise.all([getUsers(), getArticles()])
.then(([users, articles]) => {
return users.map(user => {
return {
user,
favoriteArticles: articles
}
});
})
.then(save)
.catch(error => console.log(error));
}
// good
async function setFavorites() {
try {
const [users, articles] = await Promise.all([getUsers(), getArticles()]);
const favorites = users.map(user => {
return {
user,
favoriteArticles: articles
}
});
await save(favorites);
} catch(error) {
console.log(error);
}
}