Library

Creating Custom RxJS Operators

 Published: Jan 25, 2021  (last updated: Jan 25, 2021)~ 1500 words~ 7 minutes reading time

RxJS is a popular library available for TypeScript and JavaScript.

It provides APIs for the creation of applications and libraries using asynchronous streams of data and reactive methods. It’s one of the foundation libraries of Angular.

Included in it are over 100 operators - functions that take an Observable stream of data and return values for use in chains of operators.

Many of the operators are low level, and combining them through the pipe method they create a powerful way to work with data.

Creating custom operators for a domain

The good news is it’s also very easy to create new higher-level operators for our domain code - these can be used where you find duplicate or complicated operations.

Creating operators we can also ensure well-tested code using marble testing and they can be shared among your team to make your code more readable and stable.

There are two types of operators that can be created - a MonoTypeOperatorFunction and OperatorFunction and all operators must do two things:

  • Return a function which accepts as its parameter a source from the previous Observable value in the stream
  • Return a value of the same type for MonoTypeOperatorFunction or different type for an OperatorFunction by using the source value with pipe

Below we’ll have an example of each, but first, to support creating the operators we need some code to simplify:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { from } from 'rxjs';
import { map, tap } from 'rxjs/operators';

// Create a cold source that will emit each number
const source$ = from([1, 2, 3, 4, 5]);

// Create a cold source that multiplies each number by `5`
const multiplyByFive$ = source$.pipe(map(value => value * 5));
// Create a cold source that multiplies each number by `10`
const multiplyByTen$ = source$.pipe(map(value => value * 10));

// Subscribe to the sources and console.log the output
multiplyByFive$.pipe(tap(console.log)).subscribe();
// Output: `5, 10, 15, 20, 25`

multiplyByTen$.pipe(tap(console.log)).subscribe();
// Output: `10, 20, 30, 40, 50`

Creating MonoTypeOperatorFunction for single types

As the name suggests a MonoTypeOperatorFunction is a function that works with a single type of data - the input and output value must be of the same type.

Looking at our code we can identify two multiplication operations in our code that are the same. To turn this into an operator the function will look like this:

1
2
3
4
5
6
import { MonoTypeOperatorFunction } from 'rxjs';
import { map } from 'rxjs/operators';

export function multiply(factor: number): MonoTypeOperatorFunction<number> {
  return (source) => source.pipe(map(value => value * factor))
}

Here, we are returning an arrow function that takes the previous source - which must be an Observable<number>. The source is piped to map which allows the source value to be converted to a new value, in our case we multiply by the factor

TypeScript understands that the output must also be a number - and if you try to return another value type it will throw a compile error.

Writing a marble test

Marble testing is a way to write tests for RxJS operators that deal with data over time - data is not static due to its asynchronous nature and cannot always be guaranteed in a specific order. Luckily the test for this operator is simple.

My personal preference has been to write these test in Jest using rxjs-marbles and jest-marbles, but there are other libraries to write these test available.

Using marbles, we can set up a mock source that will emit 5 numbers at the specified frames.

The test result contains two things:

  • A subscriptions string which is used to check that the operator handle subscription ending properly using toHaveSubscriptions
  • An output Observable that will contain the results of the operator and compared against the expectations using toBeObservable

In this test, we’ll pass a source of numbers and multiply by 10

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { marbles } from "rxjs-marbles/jest";
import { map } from "rxjs/operators";
import { multiply } from './multiply'

describe("multiply", () => {
  it("should multiply by 10", marbles(m => {
    const input = m.hot('-a-b-c-d-e-|', {a: 2, b: 3, c: 4, d: 5, e: 6});
    const subs = '^----------!';
    const expected = m.cold('-a-b-c-d-e-|', {a: 20, b: 30, c: 40, d: 50, e: 60});
    m.expect(input.pipe(mul(10))).toBeObservable(expected);
    m.expect(input).toHaveSubscriptions(subs);
  }));
});

Update Code

Now the operator is created it can be used in the existing code from above - ideally the operator should be part of a shared library of code:

1
2
3
4
5
6
7
import { from } from 'rxjs';
import { multiply } from '@myorg/rxjs-library'

const source$ = from([1, 2, 3, 4, 5]);

const multiplyByFive$ = source$.pipe(multiply(5));
const multiplyByTen$ = source$.pipe(multiply(10));

Already much more readable! Our code explains our intent, but we haven’t really reduced the duplication of our sources.

Changing the API with OperatorFunction

In our domain, we know we always want more than one value from a source and using the OperatorFunction we can use that to reduce our duplicate code even more.

This would introduce an API change, but with proper tests, we should be able to migrate our code easily.

For our source value, it is still a single number value, but in the API we’ve changed:

  • The input factor can be a single value or an array of values
  • The return value is now an array of values, regardless of the input.

Instead of forcing the users to check the type of response, this single API can be well documented and expected when we use it in our code:

1
2
3
4
5
6
import { OperatorFunction } from 'rxjs';
import { map } from 'rxjs/operators';

export function multiply(factor: number | number[]): OperatorFunction<number, number[]> {
  return source => source.pipe(map(value => (Array.isArray(factor) ? factor : [factor]).map(f => value * f)))
}

Updating the tests

First, we need to update the existing test - here we only have to change the values in our expected Observable - we now expect an array of numbers regardless of the input - but with a single value our array length will be 1

1
2
3
4
5
6
7
it("should multiply by 10", marbles(m => {
  const input = m.hot('-a-b-c-d-e-|', {a: 2, b: 3, c: 4, d: 5, e: 6});
  const subs = '^----------!';
  const expected = m.cold('-a-b-c-d-e-|', {a: [20], b: [30], c: [40], d: [50], e: [60]});
  m.expect(input.pipe(mul(10))).toBeObservable(expected);
  m.expect(input).toHaveSubscriptions(subs);
}));

To ensure full coverage, we should also test for the case where were have an array input for the multiplication factor:

1
2
3
4
5
6
7
it("should multiply by by 5 and 10", marbles(m => {
  const input = m.hot('-a-b-c-d-e-|', {a: 2, b: 3, c: 4, d: 5, e: 6});
  const subs = '^----------!';
  const expected = m.cold('-a-b-c-d-e-|', {a: [10, 20], b: [15, 30], c: [20, 40], d: [25, 50], e: [30, 60]});
  m.expect(input.pipe(mul([5, 10]))).toBeObservable(expected);
  m.expect(input).toHaveSubscriptions(subs);
}));

Update Code

We can now update the code further - here we can now remove the two additional cold Observables and create a single one using our new multiply operator, passing it an array containing out factors:

1
2
3
4
5
6
import { from } from 'rxjs';
import { multiply } from '@myorg/rxjs-library'

const source$ = from([1, 2, 3, 4, 5]);

const multiplyValues$ = source$.pipe(multiply([5, 10]));

Now we can subscribe to the multiplyValues$ source and get both our new result which contains the multiplication of both numbers

1
2
multiplyValues$.pipe(tap(console.log)).subscribe();
// Output: `[5, 10], [10, 20], [15, 30], [20, 40], [25, 50]`

Next Steps

You can see a working version of this operator on StackBlitz by opening the console to see the result.

This operator is just a taste of what’s possible with RxJS - diving into the API you’ll find many more operators to help work with data in other synchronous and asynchronous operations.

A collection of pre-built operators for your projects

The RxJS Logo, a Ninja jumping over a moon

Now for a shameless plug - my own library - RxJS Ninja - is a collection of over 130 operators for working with various types of data (such as arrays or numbers) and streams allowing for modifying, filtering and querying the data.

Still in active development, you might find useful operators that provide clearer intent for your RxJS code.

You can check out the source code on GitHub. There you can also find a starter project for creating your own TypeScript libraries like this.

RxJS Ninja Updates - New operators for math and working with streams

 Published: Jan 14, 2021  (last updated: Jan 14, 2021)~ 300 words~ 2 minutes reading time

Since the last update on RxJS Ninja there have been a few new operators added, below are some details and links to StackBlitz demos showing them in action.

Numbers and Math operators

In @rxjs-ninja/rxjs-number the missing toFixed operator has been added, alongside a new custom toHex operator and the corresponding parseHex one allowing hex numbers to be worked with ( such as converting colours)

There are also new operators for some basic math - add , sub, div, mul, mod and pow all allowing you to modify source numbers, all accept a number, or an Observable number source.

Working with Browser Streams

Some new operators have been added to @rxjs-ninja/rxjs-utility that allow interoperability between RxJS and the StreamsAPI. These APIs are not in all browsers but there is an available polyfill.

fromReadableStream

This operator accepts a ReadableStream and provides the emitted values as an Observable, allowing you to use RxJS operators to work with the data.

  • Demo - Using fetch body with fromReadableStream to show partial images
  • Demo - A basic infinite number stream that ticks per second

toWritableStream

This operator accepts a WritableStream. The operator emits the source value and also writes to the stream. The operator takes care of closing the writer when the Observable subscription is closed.

  • Demo - Streams an Observable interval value into a WritableStream with optional ability to stop the writer without ending the Observable subscription.

fromFetchWithProgress

This operator uses fetch to do a HTTP request, instead of the body response it emits either a number which is the current progress, or a Uint8Array containing the final response from the body.

  • Demo Fetches an image and shows a progress bar with the current percentage and once complete shows the image.

RxJS Primitives is now RxJS Ninja

 Published: Nov 23, 2020  (last updated: Nov 23, 2020)~ 100 words~ 1 minutes reading time

RxJS Ninja Logo is a Ninja jumping over a crescent moon

Today I’ve re-released RxJS Primitives as RxJS Ninja. The new libraries have been published as the same last versions under their old name, so it’s easy to migrate to the new version - all now published under @rxjs-ninja instead of @tinynodes (a deprecation notice has also been left the old packages).

New RxJS Primitives release, new operators + Typescript 4

 Published: Nov 18, 2020  (last updated: Nov 18, 2020)~ 900 words~ 5 minutes reading time

This week I released new versions of my RxJS libraries in rxjs-primitives. Since it’s released I’ve added a few new utility operators. Some of these have been out for a while since I originally wrote about the release, so I’ve highlighted them here as they may be useful to some developers.

You can check out the full docs here.

Typescript 4

Upgrading to Typescript 4 has allowed the removal of polymorphic functions in place of Vardic Tuple Types and is why there is a major bump on all packages.

This can be seen in the old and new concat operator in the rxjs-string package.

Most of the tests have also been converted to rxjs-marbles allowing for more robust Observable testing (if you are working with RxJS I highly recommend checking it out, it integrates well with runners like Jest).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
it(
    'should return string value of string ending with passed character',
    marbles((m) => {
      const input = m.hot('-a-b-c-|', { a: 'test', b: 'testing', c: 'gone' });
      const subs = '^------!';
      const expected = m.cold('---y---|', { y: 'testing' });
      m.expect(input.pipe(filterEndsWith('g'))).toBeObservable(expected);
      m.expect(input).toHaveSubscriptions(subs);
    }),
  );

rxjs-array

npm install @tinynodes/rxjs-array

In the array module there are some operators to use with finding the difference or intersection between a source and a passed array, for example:

1
2
3
4
5
6
7
of(['a', 'b', 'd'])
 .pipe(difference(['a', 'c']))
 .subscribe(console.log) // ['b', 'd']

of(['a', 'b', 'd'])
 .pipe(intersects(['a', 'c']))
 .subscribe(console.log) // ['a']

These methods accept an array, or an Observable<Array> of items to compare against.

The module also included a binarySearch operator which returns a custom BinarySearchResult tuple.

rxjs-boolean

npm install @tinynodes/rxjs-boolean

A new Luhn algorithm operator luhnCheck is provided that does validation on numbers such as credit cards, ID cards and other value schemes that use the check.

1
2
3
fromString('4485275742308327')
    .pipe(luhnCheck())
    .subscribe(console.log) // true, this is a valid credit card

rxjs-number

npm install @tinynodes/rxjs-number

inRange / outOfRange and filterInRange / filterOutOfRange both all two numbers, the filter methods return the value from the source observable within the range of those values, while the other methods return a boolean value if in range. An optional third value will include/exclude the range value based on the method

1
2
3
4
5
6
7
8
fromNumber([-1, 0, 1, 2, 10, 11])
 .pipe(filterInRange(0, 10))
 .subscribe(console.log) // [0, 1, 2, 10]

// Passing true as the third parameter, the range numbers will also be excluded
fromNumber([-1, 0, 1, 2, 10, 11])
 .pipe(filterInRange(0, 10, true))
 .subscribe(console.log) // [1, 2]

rxjs-string

npm install @tinynodes/rxjs-string

New operators such as titleize, repeat and match add new utility features for strings. Where they can they also support localisation:

1
2
3
4
5
6
7
fromString('Mary had a little lamb')
 .pipe(titleize())
 .subscribe(console.log) // 'Mary Had A Little Lamb'

fromString('Mary had ä little lamb')
 .pipe(titleize('de-DE'))
 .subscribe(console.log) // 'Mary Had Ä Little Lamb'

rxjs-utility

npm install @tinynodes/rxjs-utility

The utility module contains some specialised tap operators such as tapIf, startWithTap and tapOnSubscribe. These provide a way to do side effects. With startWithTap it can be used with Angular to do a form touch, also tapOnSubscribe will fire when there is a subscription to the Observable:

1
2
3
4
5
6
7
8
9
// Only touch on first value change
form.valueChange.pipe(
 startWithTap(() => this.onTouch())
).subscribe()

// Fire when a component subscribes to the service bus
this.serviceBus.pipe(
  tapOnSubscribe((name: string) => console.log(`New Subscription to ${name}`))
).subscribe()

The tapIf will only fire if a passed method result is truthy:

1
2
3
fromNumber([1, 2, 3, 4, 5, 6]).pipe(
  tapIf((val) => val % 2 === 0), (val) => console.log(val)
).subscribe() // 2, 4, 6

The last operator is mapIfSource which might be a bit of a weird one but I hope might become useful.

The operator takes the value from the source and passes to a predicate method, and depending on the result will map the result of a passed method. A simple example would be:

1
2
3
4
5
6
7
fromNumber([1, 2, 3, 4, 5, 6]).pipe(
  mapIfSource(
    (value) => val % 2 === 0,
    (value) => val * 10,
    (value) => val * 20
  )
).subscribe() // 20, 20, 60 40, 100, 60

Here, if the result of the predicate is true multiply by 10, otherwise by 20. The method is typed to allow different return values based on the result (so you will have to handle the type later). For example we could even turn it into a FizzBuzz operator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
export function fizzbuzz(): OperatorFunction<number, string | number> {
  return (source: Observable<number>) =>
    source.pipe(
      mapIfSource<number, string, number>(
        (value) => value % 15 == 0 || value % 3 == 0 || value % 5 == 0,
        (value) => (value % 15 == 0 ? `FizzBuzz` : value % 3 === 0 ? 'Fizz' : 'Buzz'),
        (value) => value
     )
  );
}

// And now we use it in our code
fromNumber([1, 3, 5, 15, 16]).pipe(
  fizzbuzz(),
).subscribe() // 1, 'Fizz', 'Buzz', 'FizzBuzz', 16

Hopefully you’ll find these operators useful and feel free to leave feedback and suggestions.

Publishing NPM Libraries using NX and Github Actions

 Published: May 7, 2020  (last updated: May 7, 2020)~ 2300 words~ 11 minutes reading time

This week I released version 1.0 of RxJS Primitives to NPM - but the journey to get there was not as easy as I hoped.

In the past I’ve used CircleCI and at work I use Jenkins but with this project I wanted to try out Github Actions.

After some trial an error (and many failed builds) I managed to get the workflow working, I’ve decided to share here the steps taken to hopefully save you from the same pain.

Setting up your monorepo

The NX CLI is a set of tools that make managing your repository of JavaScript and TypeScript code easy. Originally built on top of the Angular CLI the tooling now supports more project types including Node, React and Web Components.

In my day-job most of my work is building Angular Enterprise applications, but I also work on my own open source software and in both cases I use NX, including using it to publish Angular and TypeScript libraries to NPM.

Each way of setting this up is subtly different. In this example I’ll show the steps of how I built a pipeline for releasing my TypeScript RxJS library.

The easiest way to start is to create your repo using create-nx-workspace

npx create-nx-workspace <project-name>

When running this, you will be asked a few questions depending on your setup. For RxJS Primitives I used a plain workspace, and then once created I added @nrwl/node to the project. The default @nrwl/workspace plugin allows the creation of libraries, but does not provide a publishable output (adding package.json) as an option.

Once set up you can now create libraries that can be built and published as NPM modules, using the @nrwl/node plugin type to create them. For rxjs-string I used:

1
> nx g @nrwl/node:library --name=string --directory=rxjs --publishable

If it’s an Angular library use:

1
> nx g @nrwl/angular:library --name=my-component --directory=ngx --publishable

After a few seconds, inside the libs folder will be a default output of a TypeScript library, with various tsconfig.json files for testing and building, a Jest config for unit testing, a index.ts file as the entry point and package.json for publishing.

One issue with nx is that with this configuration in the package.json you’ll find a 2-level name for your library (e.g. @tinynodes/rxjs-string) however in the root tsconfig.json file you’ll see the following in the paths property

1
2
3
4
5
{
  "paths": {
    "@tinynodes/rxjs/string": ["libs/rxjs/string/src/index.ts"]
  }
}

If you are only using this library internally it’s not really an issue, but when you intend to publish the library I recommend changing the path to @tinynodes/rxjs-string to match the NPM import path.

Preparing for publishing

Once you have developed your library, it’s time to publish to NPM! First of all, make sure your public API (Functions, Classes, and Types) are exported in the library index.ts:

1
2
3
4
// index.ts
export { myFactoryFunction } from './libs/factory';
export { MyThing } from './classes/my-thing'
export { MyInterface, SOME_CONSTANT } from './types/thing-types' 

To see the output of this library you can run nx build library-name, this will output a NPM module to the dist folder.

Setting up Github Actions for Pull Requests

For an open-source library on GitHub, it’s good practice for you to get pull requests from other developers, and to have confidence in those PRs having a pull request checker is ideal.

First create a folder .github at the root of your workspace, and inside the workflows folder. This is the folder that GitHub checks to see if there are any YAML files containing action flows.

Create a file pr_on_master.yml and inside this file set up the following steps:

1
2
3
4
5
6
7
8
9
# File for Pull Request on master branch
name: PR on master

# When a PR is opened to master
on:
  pull_request:
    branches:
      - master
    types: [opened, reopened, synchronize]

This first section provides the name and triggers for when the action runs: when a pull request opened or re-opened against master.

Next the steps need set up:

1
2
3
4
5
6
7
8
jobs:
  build:
    # Setup OS and Node Version
    runs-on: ubuntu-latest
    strategy:
      matrix:
        # Latest nodes only
        node-version: [13.x]

This sets up a Github Action runner and makes sure NodeJS 13 is installed. Here you can add more versions and run parallel tests against different versions if you plan to support more than one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Define Steps
steps:
  # Checkout code
  - uses: actions/[email protected]
    with:
      ref: ${{ github.event.pull_request.head.ref }}
      fetch-depth: 0

  - name: Use Node.js ${{ matrix.node-version }}
    uses: actions/[email protected]
    with:
      node-version: ${{ matrix.node-version }}

The first two steps first check out the code from the pull request branch, and then sets up NodeJS to run.

The next piece of step was the part of setting up the pipeline that took the longest to fix.

To use the nx affected commands it needs a base branch to compare changes against - by default Github checkout does not pull all the branches, only the current one - and this includes the master branch. This command tells git to pull the master branch from the origin:

1
2
3
  # Make sure we have all branches
  - name: Fetch other branches
    run: git fetch --no-tags --prune --depth=5 origin master

Finally, we run some NPM command for installing the dependencies, running linting and test coverage.

1
2
3
4
5
6
7
8
  - name: Install environment
    run: npm ci

  - name: Run lint
    run: npm run affected:lint -- --base="origin/master"

  - name: Tests coverage
    run: npm run affected:test -- --base="origin/master" --codeCoverage

Using the affected commands, the pipeline will only run linting and testing against libraries that have changed against the master branch. More steps such as SonarQube or other webhooks can be performed here.

Publishing to NPM

When publishing libraries, there’s a few additional steps needed before we write the pipeline. First of all we need two access tokens, one for NPM and one for GitHub.

NPM Token

The NPM token will be used to publish the library to the public NPM registry. Log into NPM and under your profile go to the “Auth Tokens” section and create a new token with “Publish” access. Next go to your Github repository and under “Settings -> Secrets” add a new token called NPM_AUTH_TOKEN and paste in the value.

If publishing to a private registry follow it’s instructions on generating an API token.

Github Token

To publish changes back to GitHub from the pipeline you also need a personal access token - this can be created under your account settings in “Developer Settings -> Personal Access Token”. The only permissions you need to give this token are the repo permissions.

Add this under “Settings -> Secrets” as ACTION_AUTH_TOKEN (it seems you cannot name them with GITHUB_ in the name at all) 🤷‍♂️

Setting up the action

Like before we are going to add a YAML file to the .github/workflows folder - this time called publish.yml

First set the action up to trigger only when a PR is closed on master:

1
2
3
4
5
6
name: Merge on master
on:
  pull_request:
    branches:
      - master
    types: [closed]

The job section is the same as above, but for the steps there is a slight change - to allow merges to master that don’t trigger a release, each command will be wrapped in a if block this block checks the commit message for the string [skip-ci] to avoid running these tasks (unfortunatly it seems you can’t just put a block around the entire set of steps so has to be added to all of them)

1
2
3
4
5
steps:
  # Checkout code
  - name: Checkout Code
    if: github.event.pull_request.merged == true && contains(github.event.commits[0].message, '[skip-ci]') == false
    uses: actions/[email protected]

Instead of running lint and test, we’ll now run a set of different tasks - hold on tight because we’re about to dive into some bash code!

Deployment Step

In our publish.yaml add the following step which first exports our registry publishing setting with the auth token we set up earlier, then we call our publish bash script

1
2
3
4
5
6
7
- name: Deploy
    if: github.event.pull_request.merged == true && contains(github.event.commits[0].message, '[skip-ci]') == false
    env:
      NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
    run: |
      npm config set //registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN
      ./.github/scripts/publish-libraries.sh      

Create the .github/scripts/publish-libraries.sh file and make sure it’s set to executable by typing chmod +x .github/scripts/publish-libraries.sh before committing the script.

The first part of the script is setting up our variables and getBuildType function to check what type of release we are doing. It’s good to use SemVer to publish based on changes that happen in the library and with this function using the following words in the MERGE COMMIT message will determine the release type.

For example: (fix): Fixed that annoying bug in issue #123 will set the release type to patch. The default is minor. Using (feat) will make a major change.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash
set -o errexit -o noclobber -o nounset -o pipefail

# This script uses the parent version as the version to publish a library with

getBuildType() {
  local release_type="minor"
  if [[ "$1" == *"feat"* ]]; then
    release_type="major"
  elif [[ "$1" == *"fix"* || "$1" == *"docs"* || "$1" == *"chore"* ]]; then
    release_type="patch"
  fi
  echo "$release_type"
}

PARENT_DIR="$PWD"
ROOT_DIR="."
echo "Removing Dist"
rm -rf "${ROOT_DIR:?}/dist"
COMMIT_MESSAGE="$(git log -1 --pretty=format:"%s")"
RELEASE_TYPE=${1:-$(getBuildType "$COMMIT_MESSAGE")}
DRY_RUN=${DRY_RUN:-"False"}

The script also cleans up the dist folder, and has an optional DRY_RUN to set if you want to test the pipeline before release.

After this add the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
AFFECTED=$(node node_modules/.bin/nx affected:libs --plain --base=origin/master~1)
if [ "$AFFECTED" != "" ]; then
  cd "$PARENT_DIR"
  echo "Copy Environment Files"

  while IFS= read -r -d $' ' lib; do
    echo "Setting version for $lib"
    cd "$PARENT_DIR"
    cd "$ROOT_DIR/libs/${lib/-//}"
    npm version "$RELEASE_TYPE" -f -m "RxJS Primitives $RELEASE_TYPE"
    echo "Building $lib"
    cd "$PARENT_DIR"
    npm run build "$lib" -- --prod --with-deps
    wait
  done <<<"$AFFECTED " # leave space on end to generate correct output

  cd "$PARENT_DIR"
  while IFS= read -r -d $' ' lib; do
    if [ "$DRY_RUN" == "False" ]; then
      echo "Publishing $lib"
      npm publish "$ROOT_DIR/dist/libs/${lib/-//}" --access=public
    else
      echo "Dry Run, not publishing $lib"
    fi
    wait
  done <<<"$AFFECTED " # leave space on end to generate correct output
else
  echo "No Libraries to publish"
fi

This part of the script allows us to control the build and release - the first line gets a single line list of affected libraries from the current master to the last HEAD in master - if you use only PRs to make changes into master this is very effective - but can break if you make changes directly to master.

This is done instead of using nx affected as there is currently no task for publishing, so this gives a way to provide a loop for both building and publishing.

Both while loops parses this string and splits on the space to allow a loop to be run.

In each loop there is a string replacement ${lib/-//} - this changes the library name (e.g. rxjs-string) into a path (rxjs/string)

The first while loop ensures we are in the correct directory and does npm version - bumping the package.json for release - it then runs the build task for production, and ensures any dependencies are also build.

The second while loop iterates over the same list but runs the npm publish command in each directory, setting the access to public.

Congratulations

If you’ve made it this far, well done - this is quite a long post! At this point your pipeline should have successfully published your NPM module for other developers to use! We’re not done yet though!

Before running this we are going to add some additional steps so that we can publish documentation and changes back to Github:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  - name: Build Docs
    if: github.event.pull_request.merged == true && contains(github.event.commits[0].message, '[skip-ci]') == false
    run: npm run docs

  - name: Commit files
    if: github.event.pull_request.merged == true && contains(github.event.commits[0].message, '[skip-ci]') == false
    run: |
      git config --local user.email "[email protected]"
      git config --local user.name "GitHub Action"
      git add .
      git commit -m "Release [skip-ci]" -a || true      

  - name: Push changes
    if: github.event.pull_request.merged == true && contains(github.event.commits[0].message, '[skip-ci]') == false
    uses: ad-m/[email protected]
    with:
      github_token: ${{ secrets.ACTION_AUTH_TOKEN }}
      tags: true
      force: true

For RxJS Primitives I used TypeDoc to generate static content to the docs folder, which is then used by GitHub Pages, but here you can set up whatever documentation system you prefer.

Once the docs have been generated we then commit all changes including the package.json version bumps back to git and then finally push it back to master using the GitHub token generated earlier.

Within a few seconds your GitHub Page should update with the latest content, the master branch reflect all changes made.

That’s a wrap! You’ve now successfully published your TypeScript library for other developers to use, along with documentation.

If you’ve found this article useful, let me know - and if you find any issues or improvements please get in touch!

RxJS Primitives - Operators for mutating and filtering primitives

 Published: Apr 23, 2020  (last updated: Apr 23, 2020)~ 400 words~ 2 minutes reading time

Today I’ve published a new set of libraries to NPM - RxJS Primitives.

These are based on some operators I’ve collected over the last year, and some additional ones I’ve started adding. Most are based around ECMASCript objects such as String, Number and Boolean but also includes some useful utility operators.

Over the coming weeks I’ll add more operators, both based on ECMAScript methods and custom functions that I have found useful.

The following modules are on NPM:

rxjs-string

@tinynodes/rxjs-string operators that are built around the String object in ECMAScript, for example with toUpperCase:

1
2
3
from(['hello', 'world']).pipe(
  toUpperCase()
).subscribe(value => console.log(value)) // ['HELLO', 'WORLD']

There are also some boolean value operators such as endsWith and extraction operators like charAt, with plans to add more useful utilities. For example endWith returns a boolean value, but I also want to include an endsWith that returns the original value instead (like filter).

rxjs-number

@tinynodes/rxjs-number operators that are built around the Number object in ECMAScript, for example parseFloat/parseInt and isNaN.

1
from(['1', '1.2', '3.14']).pipe(parseInt()).subscribe(value => console.log(value)) // [1, 2, 3]

This also includes toString which uses Number.prototype.toLocaleString supports formatting such as currency.

rxjs-boolean

@tinynodes/rxjs-boolean operators that are built around the Boolean object in ECMAScript, and are designed to help with filtering content from observables. Currently, there are two operators firstTruthy and filterTruthy.

In both cases these return the underlying value only if it’s a truthy value in JavaScript, in the case of firstTruthy it only returns the first value, while filterTruthy returns all truthy values.

rxjs-utility

@tinynodes/rxjs-utility is a custom module that provides some additional operators that don’t fit into the other packages but still have some usefulness.

Currently, there are two operators:

  • startWithTap - Will fire a callback method only on the first emission from an Observable
1
2
3
form.valueChanges.pipe(
  startWithTap(() => form.touch()),
).subscribe()
  • debounceWithQuery - Debounces an input such as a text input and passes it to a method that returns a value from a query (such as a search)
1
2
3
searchField.valueChange.pipe(
  debounceWithQuery(1000, (search) => http.get(`/search?query=${search}`))
).subscribe()

Ngx-EditorJS library for Angular

 Published: May 10, 2019  (last updated: May 10, 2019)~ 300 words~ 2 minutes reading time

Today I have published my first full Angular module - @tinynodes/ngx-editorjs on NPM.

The module is a set of features for Angular (7+) to create and control EditorJS instances.

A demo application is available to see the editor in action, and the source is also available in the @tinynodes monorepo.

Included Features

The library exports several features once the NgxEditorJSModule has been included in your project.

NgxEditorJSDirective

This is the main directive which can be used on any element with the [ngxEditorJS] selector and and id attribute.

1
<div id="my-editor" ngxEditorJS></div>

This will handle creating the editor instance via the NgxEditorJSService

NgxEditorJSComponent

This component can be used in any Angular component using the <ngx-editorjs> tag. Again this component can take a set of blocks, it also provides a holder input for overriding the ID.

1
<ngx-editorjs [holder]="holderProperty"></ngx-editorjs>

NgxEditorJSService

This service provides handling the life-cycle of the EditorJS instances, and exposes the underlying EditorJS API - in future releases more of the API will be exposed via service methods to make controlling the container easier.

The module is configurable and allows EditorJS plugins to be injected into the library - (see README.md) on how to do this in a AOT-friendly way.

Next Steps

Before release I had to resolve an issue with AOT compiling and injecting EditorJS plugins - this means version 1.1.0 has already been released - now this is resolved I want support additional feature in the editor, as well as provide unit test coverage and better documentation and demo.

If you find any issues or have feature requests these can be left on the GitHub Issues page.