The WorksAudit Book
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode

TypeScript

Development Conventions

  • 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() or Array.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'
}; 

Naming Conventions

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 of res) 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.

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

Reactive Programming

  • When you subscribe to a stream always unsubscribe. In Angular components unsubscribe from streams using ngOnDestroy or better yet, use the async 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 or switchMap

On Writing Functions

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

Angular

  • 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 (an Observable and an Observer at the same time) only when necessary, do not make all Observables a Subject if the communication is not bidirectional.

Writing Tests

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