TypeScript
- Dont describe types inline, declare an interface or type and append it to the end of the file if it is used only in this file, make a file for that if it is used by several files.
// Please no, this could get really complex to read and maintain
function mirror(object: string | string[] | number | number[] ) {
return object;
}
// Yes, please
function coolMirror(object: StringsOrNumbers ) {
return object;
}
type StringsOrNumbers = string | string[] | number | number[];
- Don’t leave unnecessary code even if commented (specially big blocks). Dead code should be deleted.
- Avoid iterating over big objects, if you need to perform a search on Arrays use
Array.prototype.some()
orArray.prototype.find()
specially in views, or else UI will feel laggy. - Write declarative over procedural code
// Procedural:
const obj1 = {};
obj1['foo'] = 'my foo';
obj1['bar'] = 'my bar';
// Declarative
const obj2 = {
foo: 'my foo',
bar: 'my bar'
};
When building an application you should try to maintain good, meaningful naming conventions for variables, functions, classes, data models/interfaces, libraries, and any other things that you give a name
For code[^3]:
- Use GTS, it has styling and linting tools
- Some gray area cleared on naming:
- Always use descriptive names, avoid generics (
myVar
,temp
,foo
, etc) - In variable names don’t use abbreviations, write the full word (i.e.
httpResponse
instead ofres
) that will be easier to understand when reading code. - Use
camelCase
for variables and local constants (const
),PascalCase
for classes and namespaces. In interfaces, enums and types use `PascalCa - While historically it’s been usual that observable variable names are trailed with a currency symbol (
$
) in javascript for identifying its type at first sight, in Typescript makes little sense since it has variable typing, so it’s not encouraged to do it.
- Always use descriptive names, avoid generics (
For files:
- Use angular naming conventions. Some highlights:
- Filenames use
kebab-case
- The recommended pattern is
feature.type.extension
- For types use angular’s type names
.service
,.component
,.pipe
, etc. Additionally use generics like.interface
,.lib
,.spec
,.const
etc. Try to use as few as possible
- Filenames use
- When you subscribe to a stream always unsubscribe. In Angular components unsubscribe from streams using
ngOnDestroy
or better yet, use theasync
pipe. These will prevent memory leaks. - Use marble[^5] ASCII diagram if you need to document an observable.
import { interval } from 'rxjs'
import { skip, take, filter, map } from 'rxjs/operators';
const interval = interval(1000); // 0--1--2--3--4--5--6...
const myObservable = interval
.pipe(
skip(1), // ---1--2--3--4--5--6...
take(5), // ---1--2--3--4--5|
filter(v => v % 2 === 0), // ------2-----4---|
map(v => v + 1) // ------3-----5---|
);
- Avoiding nested subscribes as much as possible. It’s recommended to use higher-order streams like
mergeMap
orswitchMap
Basically you need to write pure functions, that is:
- Functions that do not produce side effects (A side effect is any change in the system that is observable to the outside world, specially modifying global variables or input values).
- Functions that given the same input, will always produce the same output.
This is how it should look like a pure function[^2]:
import { range } from 'rxjs';
const fizzBuzz = (max: number) =>
range(max)
.map((x: number) => cond([
[!(x % 3 || x % 5), () => 'fizzBuzz'],
[!(x % 3), () => 'fizz'],
[!(x % 5), () => 'buzz'],
[true, () => `${x}`]
]));
For achieving this try using chainable functions as much as possible. To achieve this
- Big functions are more prone to be impure, divide big functions in concrete, small & if possible reusable functions.
- Use more streams and composition to achieve logic flow.[^6]
- Use lodash’s wrapper (
_(obj)
) and_.chain
to make objects conveyable through chainable functions and_.value()
to unwrap the result - Use
Array.prototype.map()
for transforming the chained output - Use global utilities for using flow-control chainable functions and types
- Divide modules properly and make them lazy loaded[^4]
- Components
- A component application logic supports the view operation, other kind of logic must be avoided
- Instead of subscribing to a stream to feed a component’s view, use the async
pipe
- For writing or styling a component’s view check first the components demo and the mixin & util catalog
- TODO: Describe use case of angular Module, Feature, Directive
- Since most streams are cold by default, every subscription will trigger the producer of these streams, be careful of
not accidentally triggering streams when sharing them among system elements, this will slow down the app specially if
the stream triggers a heavy web request or other expensive events. Use
.pipe(share())
to share a subscription[^1] - Use
Subjects
(anObservable
and anObserver
at the same time) only when necessary, do not make allObservables
aSubject
if the communication is not bidirectional.
In principle Angular Components must have tests. Ideally all other functions written must be tested, but as development cycle might be not long enough for delivering features + tests, it is left to the developer’s judgment whether to test other functions extensible or only the fundamental, however it is highly recommendable to write tests for core features like data providing elements.
When writing tests for web API consumer libraries instead of testing the data that it retrieves test that the query building is done correctly, if the data retrieved receives some treatment before handling it to the next system element, you should test also the code that does that treatment.
It is recommendable to use the Typescript tspec.ts
files to describe the desired behavior of a function before writing
the actual function, then you can develop it and see if it fulfills the spec