Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign updocs: create coding standards doc #37700
Conversation
Create initial document for Angular framework coding standards. This document will evolve over time. This version contains all non-controversial rules as discussed in a recent team meeting. Some text and examples were copied from angular/components.
|
||
##### Observables | ||
|
||
Don't suffix observables with `$`. |
This comment has been minimized.
This comment has been minimized.
Prefer `for of` to `Array.prototype.forEach`. The `for of` construct makes debugging easier and may | ||
reduce overhead from unnecessary function invocations (though browsers do generally optimize this | ||
well). |
This comment has been minimized.
This comment has been minimized.
JoostK
Jun 24, 2020
Member
I would add an exception for hot code paths here, e.g. in the Ivy runtime only for (const i = 0; i < a.length; i++)
is used when iterating arrays.
In general, maybe it would be a good idea to include a chapter for performance sensitive code, where some of these standards may not be desirable.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Consider private constructors for all new code. This prevents extension outside of the framework, | ||
which is generally discouraged unless otherwise documented. |
This comment has been minimized.
This comment has been minimized.
JoostK
Jun 24, 2020
Member
This could use some samples as to what a private constructor is and how such classes have to be instantiated instead using named constructors in the form of static members.
|
||
##### Variable declarations | ||
|
||
Prefer `const` wherever possible. Avoid `var` unless absolutely necessary. |
This comment has been minimized.
This comment has been minimized.
JoostK
Jun 24, 2020
Member
I would mention let
as the alternative for var
, as currently this may be interpreted as var
being the one alternative to const
.
This comment has been minimized.
This comment has been minimized.
gkalpak
Jun 24, 2020
Member
Also, where would var
be absolutely necessary? (I can't think of any case.)
This comment has been minimized.
This comment has been minimized.
dgp1130
Jun 24, 2020
Contributor
var
is useful if you want to declare the value on the global scope:
// At root scope:
var foo = 1;
let bar = 2;
console.log(window.foo); // 1
console.log(window.bar); // undefined
I agree that this behavior is usually undesired in the first place and is very rarely actually useful. If this is ever needed there should be a sizeable comment next to it explaining exactly why it is necessary.
class InputWithNgModel { /* ... */ } | ||
/** AVOID: does not fully describe the scenario under test. */ |
This comment has been minimized.
This comment has been minimized.
#### Boolean arguments | ||
|
||
Avoid adding boolean arguments to a method in cases where that argument means "do something extra". | ||
In these cases, prefer breaking the behavior up into different functions. |
This comment has been minimized.
This comment has been minimized.
JoostK
Jun 24, 2020
Member
An alternative alternative could be to have an enum to replace the boolean.
This comment has been minimized.
This comment has been minimized.
mhevery
Jun 24, 2020
Member
Could we add a caveat. I think the above is very true for public API, but for private(internal) API we often do boolean arguments for perf reasons and or size. We should make that distinction here as well.
} | ||
function createTargetElement() { | ||
// ... |
This comment has been minimized.
This comment has been minimized.
In TypeScript code, use JsDoc-style comments for descriptions (on classes, members, etc.) and | ||
use `//` style comments for everything else (explanations, background info, etc.). | ||
|
||
### API Design |
This comment has been minimized.
This comment has been minimized.
gkalpak
Jun 24, 2020
Member
Does this apply to Angular public API (i.e. stuff on our docs), public API of a class/module (i.e. public methods, exported functions, etc.) or any method/function?
Prefer `for of` to `Array.prototype.forEach`. The `for of` construct makes debugging easier and may | ||
reduce overhead from unnecessary function invocations (though browsers do generally optimize this | ||
well). |
This comment has been minimized.
This comment has been minimized.
|
||
Properties should have a concise description of what the property means: | ||
```typescript | ||
/** The label position relative to the checkbox. Defaults to 'after' */ |
This comment has been minimized.
This comment has been minimized.
* Opens a modal dialog containing the given component. | ||
* @param component Type of the component to load into the dialog. | ||
* @param config Dialog configuration options. | ||
* @returns Reference to the newly-opened dialog. |
This comment has been minimized.
This comment has been minimized.
gkalpak
Jun 24, 2020
Member
We need a rule that says it must be @returns
and not @return
. At all const
|
||
##### Variable declarations | ||
|
||
Prefer `const` wherever possible. Avoid `var` unless absolutely necessary. |
This comment has been minimized.
This comment has been minimized.
gkalpak
Jun 24, 2020
Member
Also, where would var
be absolutely necessary? (I can't think of any case.)
##### General | ||
|
||
* Prefer writing out words instead of using abbreviations. | ||
* Prefer *exact* names over short names (within reason). E.g., `labelPosition` is better than |
This comment has been minimized.
This comment has been minimized.
gkalpak
Jun 24, 2020
Member
const myCoolComponentFactoryResolvedComponentFactory = componentFactoryResolver.resolveComponentFactory(myCoolComponentFactoryResolvedComponent);
|
||
##### Observables | ||
|
||
Don't suffix observables with `$`. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
spock123
Jun 24, 2020
Very surprised seeing this as well.
I was under impression that was a good practise.
I'm definitely fond of it.
class InputComp { /* ... */ } | ||
``` | ||
|
||
|
This comment has been minimized.
This comment has been minimized.
##### Test names | ||
|
||
Use descriptive names for jasmine tests. Ideally, test names should read as a sentence, often of | ||
the form "it should...". |
This comment has been minimized.
This comment has been minimized.
#### Boolean arguments | ||
|
||
Avoid adding boolean arguments to a method in cases where that argument means "do something extra". | ||
In these cases, prefer breaking the behavior up into different functions. |
This comment has been minimized.
This comment has been minimized.
mhevery
Jun 24, 2020
Member
Could we add a caveat. I think the above is very true for public API, but for private(internal) API we often do boolean arguments for perf reasons and or size. We should make that distinction here as well.
#### Optional arguments | ||
|
||
Use optional function arguments only when such an argument makes sense for an API. Don't use | ||
optional arguments merely for convenience in implementation. |
This comment has been minimized.
This comment has been minimized.
mhevery
Jun 24, 2020
Member
NOTE: under some circumstances (When the method can't be inlined, the VM needs to create a trampoline with matching argument sets), option arguments have negative perf impact.
#### Getters and Setters | ||
|
||
* Only use getters and setters for `@Input` properties or when otherwise required for API | ||
compatibility. |
This comment has been minimized.
This comment has been minimized.
mhevery
Jun 24, 2020
Member
compatibility. | |
compatibility. (Using getters/setters implies side-effect, and creates a higher cognitive load to the reader. Use only when it is necessary.) |
Prefer `for of` to `Array.prototype.forEach`. The `for of` construct makes debugging easier and may | ||
reduce overhead from unnecessary function invocations (though browsers do generally optimize this | ||
well). |
This comment has been minimized.
This comment has been minimized.
|
||
#### Try-Catch | ||
|
||
Avoid `try-catch` blocks, instead preferring to prevent an error from being thrown in the first |
This comment has been minimized.
This comment has been minimized.
mhevery
Jun 24, 2020
Member
Not sure I agree, perhaps justification as to what you are trying to avoid would be helpfull. Why is try-catch bad?
|
||
#### Try-Catch | ||
|
||
Avoid `try-catch` blocks, instead preferring to prevent an error from being thrown in the first |
This comment has been minimized.
This comment has been minimized.
dgp1130
Jun 24, 2020
Contributor
Not to start a bikeshedding war on code style, but the one advantage of throwing errors is that they can contain string messages about what went wrong which can be easily rethrown and composed to maintain useful error messages.
For example, consider the error-less implementation:
function computeAThing(): number|null {
const firstResult = computeFirstPiece();
if (!firstResult) return null;
const secondResult = computeFirstPiece();
if (!secondResult) return null;
return firstResult + secondResult;
}
console.log(computeAThing()); // If this logs `null`, why?
There is no way to know which of the two pieces went wrong. Alternatively consider:
function computeAThing(): number {
let firstResult: number;
try {
firstResult = computeFirstPiece();
} catch (err) {
throw new Error(`Failed to compute the first piece: ${err.message}`);
}
let secondResult: number;
try {
secondResult = computeSecondPiece();
} catch (err) {
throw new Error(`Failed to compute the second piece: ${err.message}`);
}
return firstResult + secondResult;
}
try {
console.log(computeAThing());
} catch (err) {
console.log('Failed to compute a thing: ${err.message}');
}
The terrible try-catch
syntax aside, this has the advantage of more accurately communicating exactly what went wrong at any given point in a computation with composable error messages giving full context about the operation. It also allows types to be much clearer; because computeAThing()
always returns a number
there is no ambiguity as to what null
represents. Errors naturally propagate as well, so omitting a try-catch
at one level of abstraction will still be caught at a higher level of abstraction.
I do agree that Error
in JavaScript is not ideal. You can't wrap an Error
like in other languages, you just have to throw a new Error
with a concatenated message. try-catch
syntax is awkward due to scoping and often requires a mutable let
. It's also a bit of a pain to disambiguate different kinds of errors (though whether that is a good idea is a separate discussion).
My main point here is just that I don't feel we should outright ban them when we lack effective alternative options to fill that need. A contextual error message can be incredibly helpful for debugging problems.
A couple notable alternatives:
"Return an abstract data type (ADT)": As much as I like this pattern in general, I don't think it can be used in public-facing APIs because anyone using them in TypeScript can use an exhaustive switch statement, which means that adding a new failure state is actually a breaking change. Exhaustive switch is a useful feature, but most developers don't see this caveat and tend to misuse it as a result.
"Use a Result
monad" (a la Result<T, Err>
in Rust or Maybe
is Haskell): Personally I think this an ideal solution but it just isn't well supported in the JavaScript ecosystem. We'd really need a standardized form of the type to be able to use it consistently and effectively.
|
||
##### Variable declarations | ||
|
||
Prefer `const` wherever possible. Avoid `var` unless absolutely necessary. |
This comment has been minimized.
This comment has been minimized.
dgp1130
Jun 24, 2020
Contributor
var
is useful if you want to declare the value on the global scope:
// At root scope:
var foo = 1;
let bar = 2;
console.log(window.foo); // 1
console.log(window.bar); // undefined
I agree that this behavior is usually undesired in the first place and is very rarely actually useful. If this is ever needed there should be a sizeable comment next to it explaining exactly why it is necessary.
``` | ||
|
||
|
||
#### RxJS |
This comment has been minimized.
This comment has been minimized.
dgp1130
Jun 24, 2020
•
Contributor
This may not belong in style guide, but I'll put it here just in case:
One rule I always use is to prefer defer()
over from()
when translating a Promise
into an Observable
. Consider:
declare function doAsyncThing(): Promise<string>;
function doObservableThing(): Observable<string> {
return from(doAsyncThing()); // doAsyncThing() is immediately invoked.
}
vs:
declare function doAsyncThing(): Promise<string>;
function doObservableThing(): Observable<string> {
return defer(() => from(doAsyncThing())); // doAsyncThing() is invoked on `subscribe()`
}
You almost always want the latter option, so I'd love to see a little more awareness about this particular foot-gun. Though a style guide may not be the best place to put it.
``` | ||
|
||
#### Testing | ||
|
This comment has been minimized.
This comment has been minimized.
AndrewKushnir
Jun 24, 2020
Contributor
Just as an idea/proposal: include human-language description of the test in case it has some non-trivial setup or scenario (in a trivial cases it's typically excessive). For example:
it('should calculate transitive scopes when overrides are present', () => {
// Set up an NgModule hierarchy with two modules, A and B, each with their own component.
// Module B additionally re-exports module A. Also declare two mock components which can be
// used in tests to verify that overrides within this hierarchy are working correctly.
TestBed.configureTestingModule({ ... });
...
});
This is actually @alxhub's idea that he mentioned during the code review for one of my PRs in
#36649 (comment).
This is great! So excited to see how it comes together. I peeked out of curiosity and just noticed a few little things. I'm happy to do a more thorough docs review down the road too, if you'd like. |
|
||
## Code practices | ||
|
||
#### Write useful comments |
This comment has been minimized.
This comment has been minimized.
##### General | ||
|
||
* Prefer writing out words instead of using abbreviations. | ||
* Prefer *exact* names over short names (within reason). E.g., `labelPosition` is better than |
This comment has been minimized.
This comment has been minimized.
kapunahelewong
Jun 25, 2020
Contributor
To avoid Latin abbreviations (and confusion), we've been changing them to their English equivalents: e.g. --> for example
* Prefer writing out words instead of using abbreviations. | ||
* Prefer *exact* names over short names (within reason). E.g., `labelPosition` is better than | ||
`align` because the former much more exactly communicates what the property means. | ||
* Except for `@Input` properties, use `is` and `has` prefixes for boolean properties / methods. |
This comment has been minimized.
This comment has been minimized.
kapunahelewong
Jun 25, 2020
Contributor
* Except for `@Input` properties, use `is` and `has` prefixes for boolean properties / methods. | |
* Except for `@Input()` properties, use `is` and `has` prefixes for boolean properties / methods. |
jelbourn commentedJun 24, 2020
Create initial document for Angular framework coding standards. This
document will evolve over time. This version contains all
non-controversial rules as discussed in a recent team meeting. Some text
and examples were copied from angular/components.