255 lines
7.5 KiB
Markdown
255 lines
7.5 KiB
Markdown
|
# Angular Framework Coding Standards
|
||
|
|
||
|
The coding practices in this doc apply only to development on Angular itself, not applications
|
||
|
built _with_ Angular. (Though you can follow them too if you really want).
|
||
|
|
||
|
## Code style
|
||
|
|
||
|
The [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) is the
|
||
|
basis for Angular's coding style, with additional guidance here pertaining to TypeScript. The team
|
||
|
uses `clang-format` to automatically format code; automatic formatting is enforced by CI.
|
||
|
|
||
|
## Code practices
|
||
|
|
||
|
### Write useful comments
|
||
|
|
||
|
Comments that explain what some block of code does are nice; they can tell you something in less
|
||
|
time than it would take to follow through the code itself.
|
||
|
|
||
|
Comments that explain why some block of code exists at all, or does something the way it does,
|
||
|
are _invaluable_. The "why" is difficult, or sometimes impossible, to track down without seeking out
|
||
|
the original author. When collaborators are in the same room, this hurts productivity.
|
||
|
When collaborators are in different timezones, this can be devastating to productivity.
|
||
|
|
||
|
For example, this is a not-very-useful comment:
|
||
|
```typescript
|
||
|
// Set default tabindex.
|
||
|
if (!attributes['tabindex']) {
|
||
|
element.setAttribute('tabindex', '-1');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
While this is much more useful:
|
||
|
```typescript
|
||
|
// Unless the user specifies otherwise, the calendar should not be a tab stop.
|
||
|
// This prevents ngAria from overzealously adding a tabindex to anything with an ng-model.
|
||
|
if (!attributes['tabindex']) {
|
||
|
element.setAttribute('tabindex', '-1');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
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
|
||
|
|
||
|
#### Boolean arguments
|
||
|
|
||
|
Generally 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.
|
||
|
|
||
|
```typescript
|
||
|
// AVOID
|
||
|
function getTargetElement(createIfNotFound = false) {
|
||
|
// ...
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```typescript
|
||
|
// PREFER
|
||
|
function getExistingTargetElement() {
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
function createTargetElement() {
|
||
|
// ...
|
||
|
}
|
||
|
```
|
||
|
|
||
|
You can ignore this guidance when necessary for performance reasons in framework code.
|
||
|
|
||
|
#### Optional arguments
|
||
|
|
||
|
Use optional function arguments only when such an argument makes sense for an API or when required
|
||
|
for performance. Don't use optional arguments merely for convenience in implementation.
|
||
|
|
||
|
### TypeScript
|
||
|
|
||
|
#### Typing
|
||
|
|
||
|
Avoid `any` where possible. If you find yourself using `any`, consider whether a generic or
|
||
|
`unknown` may be appropriate in your case.
|
||
|
|
||
|
#### Getters and Setters
|
||
|
|
||
|
Getters and setters introduce openings for side-effects, add more complexity for code readers,
|
||
|
and generate additional code when targeting older browsers.
|
||
|
|
||
|
* Only use getters and setters for `@Input` properties or when otherwise required for API
|
||
|
compatibility.
|
||
|
* Avoid long or complex getters and setters. If the logic of an accessor would take more than
|
||
|
three lines, introduce a new method to contain the logic.
|
||
|
* A getter should immediately precede its corresponding setter.
|
||
|
* Decorators such as `@Input` should be applied to the getter and not the setter.
|
||
|
* Always use a `readonly` property instead of a getter (with no setter) when possible.
|
||
|
|
||
|
```typescript
|
||
|
/** YES */
|
||
|
readonly active: boolean;
|
||
|
|
||
|
/** NO */
|
||
|
get active(): boolean {
|
||
|
// Using a getter solely to make the property read-only.
|
||
|
return this._active;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Iteration
|
||
|
|
||
|
Prefer `for` or `for of` to `Array.prototype.forEach`. The `forEach` API makes debugging harder
|
||
|
and may increase overhead from unnecessary function invocations (though modern browsers do usually
|
||
|
optimize this well).
|
||
|
|
||
|
#### JsDoc comments
|
||
|
|
||
|
All public APIs must have user-facing comments. These are extracted for API documentation and shown
|
||
|
in IDEs.
|
||
|
|
||
|
Private and internal APIs should have JsDoc when they are not obvious. Ultimately it is the purview
|
||
|
of the code reviewer as to what is "obvious", but the rule of thumb is that *most* classes,
|
||
|
properties, and methods should have a JsDoc description.
|
||
|
|
||
|
Properties should have a concise description of what the property means:
|
||
|
```typescript
|
||
|
/** The label position relative to the checkbox. Defaults to 'after' */
|
||
|
@Input() labelPosition: 'before' | 'after' = 'after';
|
||
|
```
|
||
|
|
||
|
Methods blocks should describe what the function does and provide a description for each parameter
|
||
|
and the return value:
|
||
|
```typescript
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
open<T>(component: ComponentType<T>, config?: MatDialogConfig): MatDialogRef<T> { ... }
|
||
|
```
|
||
|
|
||
|
Boolean properties and return values should use "Whether..." as opposed to "True if...":
|
||
|
```ts
|
||
|
/** Whether the button is disabled. */
|
||
|
disabled: boolean = false;
|
||
|
```
|
||
|
|
||
|
#### Try-Catch
|
||
|
|
||
|
Only use `try-catch` blocks when dealing with legitimately unexpected errors. Don't use `try` to
|
||
|
avoid checking for expected error conditions such as null dereference or out-of-bound array access.
|
||
|
|
||
|
Each `try-catch` block **must** include a comment that explains the
|
||
|
specific error being caught and why it cannot be prevented.
|
||
|
|
||
|
##### Variable declarations
|
||
|
|
||
|
Prefer `const` wherever possible, only using `let` when a value must change. Avoid `var` unless
|
||
|
absolutely necessary.
|
||
|
|
||
|
##### `readonly`
|
||
|
|
||
|
Use `readonly` members wherever possible.
|
||
|
|
||
|
#### Naming
|
||
|
|
||
|
##### General
|
||
|
|
||
|
* Prefer writing out words instead of using abbreviations.
|
||
|
* Prefer *exact* names over short names (within reason). For example, `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.
|
||
|
|
||
|
##### Observables
|
||
|
|
||
|
Don't suffix observables with `$`.
|
||
|
|
||
|
##### Classes
|
||
|
|
||
|
Name classes based on their responsibility. Names should capture what the code *does*,
|
||
|
not how it is used:
|
||
|
```typescript
|
||
|
/** NO: */
|
||
|
class DefaultRouteReuseStrategy { }
|
||
|
|
||
|
/** YES: */
|
||
|
class NonStoringRouteReuseStrategy { }
|
||
|
```
|
||
|
|
||
|
##### Methods
|
||
|
|
||
|
The name of a method should capture the action performed *by* that method rather than
|
||
|
describing when the method will be called. For example:
|
||
|
|
||
|
```typescript
|
||
|
/** AVOID: does not describe what the function does. */
|
||
|
handleClick() {
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
/** PREFER: describes the action performed by the function. */
|
||
|
activateRipple() {
|
||
|
// ...
|
||
|
}
|
||
|
```
|
||
|
|
||
|
##### Test classes and examples
|
||
|
|
||
|
Give test classes and examples meaningful, descriptive names.
|
||
|
|
||
|
```ts
|
||
|
/** PREFER: describes the scenario under test. */
|
||
|
class FormGroupWithCheckboxAndRadios { /* ... */ }
|
||
|
class InputWithNgModel { /* ... */ }
|
||
|
|
||
|
|
||
|
/** AVOID: does not fully describe the scenario under test. */
|
||
|
class Comp { /* ... */ }
|
||
|
class InputComp { /* ... */ }
|
||
|
```
|
||
|
|
||
|
#### RxJS
|
||
|
|
||
|
When importing the `of` function to create an `Observable` from a value, alias the imported
|
||
|
function as `observableOf`.
|
||
|
|
||
|
```typescript
|
||
|
import {of as observableOf} from 'rxjs';
|
||
|
```
|
||
|
|
||
|
#### Testing
|
||
|
|
||
|
##### Test names
|
||
|
|
||
|
Use descriptive names for jasmine tests. Ideally, test names should read as a sentence, often of
|
||
|
the form "it should...".
|
||
|
|
||
|
```typescript
|
||
|
/** PREFER: describes the scenario under test. */
|
||
|
describe('Router', () => {
|
||
|
describe('with the default route reuse strategy', () => {
|
||
|
it('should not reuse routes upon location change', () => {
|
||
|
// ...
|
||
|
});
|
||
|
})
|
||
|
});
|
||
|
|
||
|
/** AVOID: does not fully describe the scenario under test. */
|
||
|
describe('Router', () => {
|
||
|
describe('default strategy', () => {
|
||
|
it('should work', () => {
|
||
|
// ...
|
||
|
});
|
||
|
})
|
||
|
});
|
||
|
```
|