npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rxreact/signal

v1.0.3

Published

[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![Greenkeeper badge](https://badges.greenkeeper.io/rxreact/signal.svg)](https://greenkeeper.io/) [![Build Status](https://travis

Downloads

16

Readme

styled with prettier Greenkeeper badge Build Status Coverage Status

Development Sponsored By:
Carbon Five

Signal

This package answers to one of the most difficult questions when writing applications with RxJS: how to I build a data model for my application with observables?

Existing data modeling solutions use RxJS to mimic other more well-known solutions for state management -- i.e. how would I build Redux with RxJS? This approach often sacrifices the power and potential of observables without providing much benefit. At the same time, working with raw observables without any framework brings up a million pitfalls -- hot vs cold, when to subscribe, how to manage dependencies and test, etc.

Signal is drawn from our experience as professional programmers at Carbon Five who use RxJS on a number of production projects, and is essentially captures our "best practices" we've developed over time for modelling data with Observables.

Installation

In your project:

npm install @rxreact/signal --save

or

yarn add @rxreact/signal

RxJS and React are peer dependencies and need to be installed seperately

What is a Signal Graph?

This tutorial assumes a basic knowledge of RxJS and functional reactive programming. You can start with:

The introduction to Reactive Programming you've been missing

Egghead Courses

Let's explore the idea of a Signal Graph by building something we use all the time: a login form.

Signal Graph

A login form will need an entry for your credentials and a way to submit. You probably want to display feedback from the server about incorrect logins, and maybe you also want to disable clicking submit while a login is in progress.

So how do we model this with observables? We'll let's start with what we have - three streams of data from user inputs. We can track every thing the user types in each text field, and we can track clicking on a submit button.

From there, we can probably come up with a stream of attempted logins by combining the username and password every time the user clicks the submit button.

We can use those to kick off API calls to a server, which will eventually result a stream of responses.

From there we can derive more things -- we can seperate successes and failures from the response stream, and we can derive whether a login is progress from the time between a login attempt and a login response.

We can extract useful data from our success and failure streams -- the error messages returned from the server and maybe an auth token that comes back in a success login response.

And finally later on we can use the auth token stream to trigger a fetch of a protected resource since the next screen after a login is usually to display some personalized data.

We've built up a series of streams, starting from our primary signals which are our user inputs. The network of these connected observables is called a signal graph. The signals are the emissions from observables, and the graph is how they're all tied together. This is how we can architect programs with Observables. Our programs become a series of reactive data streams, starting with primary signals and extending to all the derivations based on those primary signals. Taken together, those form a signal graph.

Signal graphs are a concept. Your observables together form a signal graph whether or not you use this library. However, using this library to directly define your signal graph will help you think about your design, and solve a number of potential pitfalls you're likely to encounter writing production code with observables.

Usage

In traditional RxJs, we'd define a series of signals manually:

const username$ = new Subject();
const password$ = new Subject();
const submitButton$ = new Subject();

const loginAttempts$ = submitButton$.pipe(withLatestFrom(username$, password$));

const loginResponses$ = loginAttempts$.pipe(
  mergeMap(([_, username, password]) => api.login(
    username,
    password
  ))
);

The first issue this presents is testing -- loginResponse$ is dependent on several of the other signals and difficult to test. We can solve this by switch to factory functions:

const makeLoginAttempts = (
    submitButton$: Observable<void>,
    username$: Observable<string>,
    password$: Observable<string>
  ) => submitButton$.pipe(withLatestFrom(username$, password$))
  
const makeLoginResponses = (loginAttempts$: Observable<[void, string, string]>, api: API) =>
    loginAttempts$.pipe(flatMap(([_, username, password]) => api.login({ username, password })))

But now have to wire up all that DI manually in our regular code.

This library provides a DSL for succinctly defining graphs:


  type SignalsType = {
    username$: string
    password$: string
    submitButton$: void
    loginAttempts$: [void, string, string]
    loginResponses$: LoginResponse
    loginInProgress$: boolean
    loginSuccesses$: LoginSuccess
    loginFailures$: LoginFailure
    loginFailureMessage$: string
    authStatus$: AuthStatus
  }

  type Dependencies = {
    api: API
  }

  const signalGraph = new SignalGraphBuilder<SignalsType, Dependencies>()
    .define(addPrimary('username$'))
    .define(
      addPrimary('password$'),
      addPrimary('submitButton$'),
      addDependency('api', api),
      addDerived('loginAttempts$', makeLoginAttempts, 'submitButton$', 'username$', 'password$'),
      addDerived('loginResponses$', makeLoginResponses, 'loginAttempts$', 'api'),
      addDerived('loginInProgress$', makeLoginInProgress, 'loginAttempts$', 'loginResponses$'),
      addDerived('loginSuccesses$', makeLoginSuccesses, 'loginResponses$'),
      addDerived('loginFailures$', makeLoginFailures, 'loginResponses$'),
      addDerived(
        'loginFailureMessage$',
        makeLoginFailureMessage,
        'loginAttempts$',
        'loginFailures$'
      ),
      addDerived('authStatus$', makeAuthStatus, 'loginSuccesses$')
    )
    .initializeWith({
      loginInProgress$: false,
      loginFailureMessage$: '',
      username$: '',
      password$: '',
      authStatus$: { status: 'unauthorized' }
    })
    .build()

We describe dependencies, provide factory functions, and the whole graph is built for us when we call build. We don't even have to define Subjects for our primary signals. It detects cyclic dependencies and missing signals. We can hydrate the graph with an initial state. For those following the subtleties of hot/cold observables, all observables in this graph have shareReplay(1) called on them, because this makes our graph behave like a predicatable state machine. In fact, other than by injecting outside dependencies, the graph is purely functional.

Getting signals in an out of the graph

Once your graph is built, you can extract any signal by calling:

signalGraph.output(`loginAttempts$`)

This will give you an observable that emits values for that node on the graph.

If you want to put new data into one of the primary signals, call:

signalGraph.input(`username$`)

This will give you a Subject that you can call next() on to cause that signal to emit new values.

If you use React for your UI, make sure to check out @rxreact/signal-connect which makes connecting signal graphs to React components super easy!

Connecting multiple graphs

While it's possible to model an entire application as one signal graph, in practice that graph will get huge in a production application. So it's nice often to seperate graphs by different major domains or feature areas of your application. However, in this case you need to connect the graphs, and @rxreact/signal lets you do that.

Let's say you had a second graph for a protected area of the application:

const protectedAreaGraph = new SignalGraphBuilder<
  {
    userToken$: string
    authStatus$: AuthStatus
    protected$: string
  },
  { api: API }
>()
  .define(
    addPrimary('authStatus$'),
    addDerived('userToken$', makeUserToken, 'authStatus$'),
    addDependency('api', api),
    addDerived('protected$', makeProtected, 'userToken$', 'api')
  )
  .initializeWith({
    protected$: ''
  })
  .build()

Now you want to connect outputs of authStatus$ from your authentication graph to inputs of authStatus$ in your protected area graph. You can do this easily:

protectedAreaGraph.connect(
  'authStatus$',
  signalGraph,
  'authStatus$'
)

Typescript FTW

While @rxreact/signal can be used with just javascript, if you use Typescript you get a number of added bonuses, in the form of super strong type checking. You can see from the examples above that SignalGraphBuilder takes two generic parameters that specify the types of your signals and the types of your external dependencies. Once you've done that, all of your definition code has very strong type checking. If you type a signal name wrong, you'll get an error. If the signal you reference as a dependency doesn't match the type expected by the factory function, you'll get an error. It becomes quite difficult to write a graph incorrectly!

Caveat Emptor

This libraries are still in very in development and the typings require Typescript 3.1. Feel free to experiment but beware production usage!

Expect though that development will continue and this will be a production-grade library in the future!

Enjoy!