# Null, nullish, nullable

In most programming languages there is only one way of defining whether the value is set or not. That value is generally considered `null` but named differently. But the Javascript went one step further and introduced two of them: `null` and `undefined`. Usually, they are considered in the same fashion - both are treated as **nullable** values which means there is no difference (for the developer) which of them is assigned. In this article, I'll show how to make your life easier by introducing functions, Angular pipes and RxJS operator to handle them.

# Setup

Requirements: Node, npm, npx

Install dependencies

```shell
npm i jest@29 @types/jest@29 ts-jest rxjs typescript
```

# Basic null checks

The most basic null check can be done in a few ways.

The first one is a loose comparison. Comparing a nullable value directly to the `null` or `undefined` results in `true`.

```typescript
if (value == null) {
  // true if value is null or undefined
}

if (value == undefined) {
  // true if value is null or undefined
}
```

When enforcing strict equality the code becomes more tedious.

```typescript
if (value === null || value === undefined) {
  // true if value is null or undefined
}
```

# Types and helpers

Let's create the types and helper functions.

`Nullable<T>` - a union of `current variable type`, `null` and `undefined`.

```typescript
export type Nullable<T> = T | null | undefined;
```

`Nullish` - a union of `null` and `undefined` types.

```typescript
export type Nullish = null | undefined;
```

`NonNullish<T>` - a `current variable type` with `null` and `undefined` types excluded. The `Exclude` comes from standard Typescript's utility types. You can also use the Typescript's `NonNullable<T>` type but I prefer my definition for more readability.

```typescript
export type NonNullish<T> = Exclude<T, null | undefined>;
```

`isNullish` function - checks whether the value is `null` or `undefined`.

```typescript
export const isNullish = (value: unknown): value is Nullish => value === null || value === undefined;
```

`isNonNullish` function - checks whether the value is different from `null` and `undefined`.

```typescript
export const isNonNullish = (value: unknown): value is NonNullish<unknown> => value !== null && value !== undefined;
```

The file with definitions might look like this

```typescript
// nullable.ts

export type Nullable<T> = T | null | undefined;
export type Nullish = null | undefined;
export type NonNullish<T> = Exclude<T, null | undefined>;

export const isNullish = (value: unknown): value is Nullish => value === null || value === undefined;
export const isNonNullish = (value: unknown): value is NonNullish<unknown> => value !== null && value !== undefined;
```

## Test it

Prepare the datasets, pass the values to the functions and check the result.

```typescript
// nullable.spec.ts
import { isNonNullish, isNullish } from './nullable';

const isNullishDataset: { key: string; value: any; expectedResult: boolean }[] = [
  { key: 'null', value: null, expectedResult: true },
  { key: 'undefined', value: undefined, expectedResult: true },
  { key: 'string290', value: 'string290', expectedResult: false },
  { key: '""', value: '', expectedResult: false },
  { key: 'false', value: false, expectedResult: false },
  { key: 'true', value: true, expectedResult: false },
  { key: '1', value: 1, expectedResult: false },
  { key: '-1', value: -1, expectedResult: false },
  { key: '0', value: 0, expectedResult: false },
  { key: 'Infinity', value: Infinity, expectedResult: false },
  { key: '-Infinity', value: -Infinity, expectedResult: false },
  { key: '[]', value: [], expectedResult: false },
  { key: '["e1", 23]', value: ['e1', 23], expectedResult: false },
  { key: '{}', value: {}, expectedResult: false },
  { key: '{prop1: false, prop2: 90}', value: { prop1: false, prop2: 90 }, expectedResult: false },
  {
    key: '() => {}', value: () => {
    }, expectedResult: false
  },
  { key: 'NaN', value: NaN, expectedResult: false },
  { key: 'new Error()', value: new Error(), expectedResult: false },
];


describe('Test isNullish', () => {
  it.each(isNullishDataset)('value: $key', ({ value, expectedResult }) => {
    expect(isNullish(value)).toEqual(expectedResult);
  });
});

const isNonNullishDataset: { key: string; value: any; expectedResult: boolean }[] = [
  { key: 'null', value: null, expectedResult: false },
  { key: 'undefined', value: undefined, expectedResult: false },
  { key: 'string290', value: 'string290', expectedResult: true },
  { key: '""', value: '', expectedResult: true },
  { key: 'false', value: false, expectedResult: true },
  { key: 'true', value: true, expectedResult: true },
  { key: '1', value: 1, expectedResult: true },
  { key: '-1', value: -1, expectedResult: true },
  { key: '0', value: 0, expectedResult: true },
  { key: 'Infinity', value: Infinity, expectedResult: true },
  { key: '-Infinity', value: -Infinity, expectedResult: true },
  { key: '[]', value: [], expectedResult: true },
  { key: '["e1", 23]', value: ['e1', 23], expectedResult: true },
  { key: '{}', value: {}, expectedResult: true },
  { key: '{prop1: false, prop2: 90}', value: { prop1: false, prop2: 90 }, expectedResult: true },
  {
    key: '() => {}', value: () => {
    }, expectedResult: true
  },
  { key: 'NaN', value: NaN, expectedResult: true },
  { key: 'new Error()', value: new Error(), expectedResult: true },
];

describe('Test isNonNullish', () => {
  it.each(isNonNullishDataset)('value: $key', ({ value, expectedResult }) => {
    expect(isNonNullish(value)).toEqual(expectedResult);
  });
});
```

# RxJS operator to filter nullish values

The operator

* invokes native `filter` operator on the observable

* checks if the value is non-nullish with the previously defined function `isNonNullish`

* returns the source observable


`filterNullish` - a wrapper function that returns `OperatorFunction` which is one of the RxJS's operator interfaces. The filtering:

* uses native `filter` operator

* tells the compiler that the returned type is `NonNullish<T>`

* invokes and returns the result of previously defined `isNonNullish` function


```typescript
import { filter, OperatorFunction } from 'rxjs';
import { isNonNullish, NonNullish } from './nullable';

export function filterNullable<T>(): OperatorFunction<T, NonNullish<T>> {
  return filter((value: T): value is NonNullish<T> => isNonNullish(value));
}
```

## Test it

The test is more complex

* define `inputValues` to test the operator

* use `jest.useFakeTimers()` to "control the time"

* within the test

  * create observable from test values `from(inputValues)`

  * use the operator `filterNullable()` inside the pipe

  * accumulate all the values in the array by using `reduce` operator

  ```typescript
  reduce((acc, val) => {
    acc.push(val);

    return acc;
  }, [] as any[])
  ```
    
  * subscribe to the result and run the `expect`

  ```typescript
  subscribe(result => {
    expect(result).toEqual(expectedResult);
  })
  ```
  
  * advance timers `jest.advanceTimersToNextTimer()` allowing observable to be processed


The whole test

```typescript
// filter-nullish-operator.spec.ts

import { from, reduce } from 'rxjs';
import { filterNullish } from './filter-nullish-operator';

const inputValues = [
  null,
  undefined,
  'string290',
  '',
  false,
  true,
  1,
  -1,
  0,
  Infinity,
  -Infinity,
  [],
  [
    'e1',
    23,
  ],
  {},
  {
    'prop1': false,
    'prop2': 90,
  },
  NaN,
];

const expectedResult = [
  'string290',
  '',
  false,
  true,
  1,
  -1,
  0,
  Infinity,
  -Infinity,
  [],
  [
    'e1',
    23,
  ],
  {},
  {
    'prop1': false,
    'prop2': 90,
  },
  NaN,
];

jest.useFakeTimers();

describe('Test filterNullish operator', () => {
  it('should filter nullish values', () => {
    from(inputValues).pipe(
      filterNullish(),
      reduce((acc, val) => {
        acc.push(val);

        return acc;
      }, [] as any[]),
    ).subscribe(result => {
      expect(result).toEqual(expectedResult);
    })

    jest.advanceTimersToNextTimer();
  });
});
```

# Source code

https://gitlab.com/barcioch-blog-examples/010-null-nullish-nullable

