Negotiable Paleobotanist Missions

    rx-component-combinators

    0.3.1 • Public • Published

    The case for a component combinator library

    This component combinator library comes from some pain points I experimented while dealing with implementing interactive applications, which make heavy use of streams and try to use componentization :

    • the stream plumbery between components is hard to read and make sense of
      • this is specially true with deep and large reactive graphs
      • and when you have to process mentally miscellaneous level of flatMap
    • achieving reusability through parameterization leads to more in-your-face stream gymnastic
      • using prop$ to pass properties can become unwieldy when there are a lot of properties involved

    The application under development should be expressed as simply or as complex as it is per its design, while keeping the stream complexity at a lower layer.

    Let's see some examples.

    Login gateway

    For instance, the structure behind a login section of an application goes as such:

    export const App = Switch({
      on: convertAuthToIsLoggedIn,
      sinkNames: ['auth$', DOM_SINK, 'router'],
      as : 'switchedOn',
      trace: 'Switch'
    }, [
      Case({ when: IS_NOT_LOGGED_IN, trace: 'LoginPage Case' }, [
        LoginPage({ redirect: '/component-combinators/examples/SwitchLogin/index.html?_ijt=7a193qn02ufeu5it8ofa231v7e' })
      ]),
      Case({ when: IS_LOGGED_IN, trace: 'MainPage Case' }, [
        MainPage
      ]),
    ]);
     

    and translates the simple design :

    • Functional specifications
      • if user is logged, show the main page
      • if user is not logged, show the login page, and redirect to index route when login is performed
    • Technical specifications
      • MainPage takes the concern of implementing the main page logic.
      • LoginPage is parameterized by a redirect route, and is in charge of logging in the user
      • convertAuthToIsLoggedIn emits IS_NOT_LOGGED_IN or IS_LOGGED_IN according to whether the user is logged or not

    The stream switching logic is hidden behind the Switch combinator.

    Nested routing

    The following implementation corresponds to :

    • Functional specifications
      • user visits '/' -> display home page
        • home page allows to navigate to different sections of the application
      • when the user visit a given section of the application
        • a breadcrumb shows the user where he stands in the sitemap
        • a series of clickable cards is displayed
          • when the user clicks on a given card, details about that card are displayed, and corresponding to a specific route for possible bookmarking
    • Technical specifications
      • HomePage takes the concern of implementing the home page logic.
      • Card is parameterized by its card content, and is in charge of implementing the card logic
      • CardDetail is parameterized by its card content, and is in charge of displaying the extra details of the card
    export const App = InjectSourcesAndSettings({
      sourceFactory: injectRouteSource,
      settings: {
        sinkNames: [DOM_SINK, 'router'],
        routeSource: ROUTE_SOURCE,
        trace: 'App'
      }
    }, [
      OnRoute({ route: '', trace: 'OnRoute (/)' }, [
        HomePage
      ]),
      OnRoute({ route: 'aspirational', trace: 'OnRoute  (/aspirational)' }, [
        InjectSourcesAndSettings({ settings: { breadcrumbs: ['aspirational'] } }, [
          AspirationalPageHeader, [
            Card(BLACBIRD_CARD_INFO),
            OnRoute({ route: BLACK_BIRD_DETAIL_ROUTE, trace: `OnRoute (${BLACK_BIRD_DETAIL_ROUTE})` }, [
              CardDetail(BLACBIRD_CARD_INFO)
            ]),
            Card(TECHX_CARD_INFO),
            OnRoute({ route: TECHX_CARD_DETAIL_ROUTE, trace: `OnRoute (${TECHX_CARD_DETAIL_ROUTE})` }, [
              CardDetail(TECHX_CARD_INFO)
            ]),
            Card(TYPOGRAPHICS_CARD_INFO),
            OnRoute({
              route: TYPOGRAPHICS_CARD_DETAIL_ROUTE,
              trace: `OnRoute (${TYPOGRAPHICS_CARD_DETAIL_ROUTE})`
            }, [
              CardDetail(TYPOGRAPHICS_CARD_INFO)
            ]),
          ]])
      ]),
    ]);

    The nested routing switching logic is hidden behind the OnRoute combinator.

    Dynamically changing list of items

    The following implementation corresponds to :

    • Functional specifications
      • display a list of cards reflecting input information from a card database
      • a pagination section allows to display X cards at a time
    • Technical specifications
      • Card is parameterized by its card content, and is in charge of implementing the card logic
      • Pagination is in charge of the page number change logic
    export const App = InjectSources({
      fetchedCardsInfo$: fetchCardsInfo,
      fetchedPageNumber$: fetchPageNumber
    }, [
      ForEach({
          from: 'fetchedCardsInfo$',
          as: 'items',
          sinkNames: [DOM_SINK],
          trace: 'ForEach card'
        }, [AspirationalPageHeader, [
          ListOf({ list: 'items', as: 'cardInfo', trace: 'ForEach card > ListOf' }, [
            EmptyComponent,
            Card,
          ])
        ]]
      ),
      ForEach({
        from: 'fetchedPageNumber$',
        as: 'pageNumber',
        sinkNames: [DOM_SINK, 'domainAction$']
      }, [
        Pagination
      ])
    ]);
     

    The iteration logic are taken care of with the ForEach and the ListOf combinators.

    Combinators

    Syntax

    In general combinators follow a common syntax :

    • Combinator :: Settings -> ComponentTree -> Component
      • Component :: Sources -> Settings -> Sinks
      • ComponentTree :: ChildrenComponents | [ParentComponent, ChildrenComponents]
      • ParentComponent:: Component
      • ChildrenComponents :: Array<Component>

    Combinator list

    The proposed library has the following combinators :

    Combinator Description
    FSM Activate components based on inputs, and current state of a state machine. Allows to implement a flow of screens and actions according to complex control flow rules.
    OnRoute Activate a component based on the route changes. Allows nested routing.
    Switch Activate component(s) depending on the incoming value of a source
    ForEach Activate component for each incoming value of a source
    ListOf Activate a list of a given component based on an array of items
    Pipe Sequentially compose components
    InjectSources Activate a component which will be injected extra sources
    InjectSourcesAndSettings Activate a component which will receive extra sources and extra settings
    InSlot Assign DOM content to a slot
    m The core combinator from which all other combinators are derived. m basically traverses a component tree, applying reducing functions along the way.

    Documentation, demo and tests for each combinator can be found in its respective repository.

    Background

    The theoretical underpinnings can be found as a series of articles on my blog :

    Documentation

    Documentation can be found in the projects portion of my blog.

    Roadmaps

    Roadmap v0.5

    The core target of this release will be to prepare the architecture for visual tracing, and specify the (visual) shape that this should take. A small proof of concept should be produced. A secondary target is to start a very basic UI component library, not going over the proof of concept level.

    The current roadmap for the v0.5 stands as :

    • Core
      • see what can be done to have a better concurrency model (i.e. beyond FSM)
      • type contracts error handling for component's settings (which types of component combinator expects, types of settings, etc.)
      • error management : error boundaries?
      • logging and visualization (!)
      • conversion to web components
    • Component library
    • Demo
    • Testing
      • Model-based testing for FSM, i.e. automatic test cases generation
      • study testing with pupeeteer.js (chrome headless browser)
    • Combinators
      • Portal combinator (render DOM in a specific location)
      • Catch combinator? cf. Core -- error management
      • Switch combinator
        • cover the default: part of switch statement
      • State machine combinator FSM
        • convert FSM structure to graphml or dot or tgf format
        • automatic generation of graphical representation of the FSM
        • refactor the asynchronous FSM into synchronous EHFSM + async module
          • this adds the hierarchical part, improvement in core library are automatically translated in improvement to this library, and closed/open principle advantages
        • investigate prior art
      • Event combinator WithEvents
      • State combinator WithState
      • Action combinator ComputeActions
    • Distribution
      • monorepo?
      • individual combinator packages?

    Roadmap v0.4

    Please note that library is still wildly under development :

    • APIs might will go through breaking changes
    • you might encounter problems in production
    • performance has not been investigated as of yet

    The current roadmap for the v0.4 stands as :

    • Core
      • component model
      • DOM merge with slot assignment (a la web component)
      • documentation slot
      • documentation combinators
      • nice blog site : github pages?
        • select static site generator (Jekyll, Hexo, Hugo)
        • blog site architecture
        • theoretical underpinnings
      • demo from Angular2 book
    • Testing
      • Testing library runTestScenario
      • Mocks for DOM and document driver
      • Mock for domain query driver
    • Combinators
      • Generic combinator m
      • Routing combinator onRoute
      • Switch combinator
        • Switch
        • Case
      • State machine combinator FSM
      • ForEach combinator ForEach
      • List combinator ListOf
      • Injection combinator
        • InjectSources
        • InjectSourcesAndSettings
      • Query driver
      • Action driver
      • sequential composition combinator (Pipe)

    Installation

    Running tests

    • npm install
    • npm run node-build-test
    • npm run test
    • then open with a local webserver the index.html in test directory

    Demos

    Example application

    The example application is taken from the book Mastering Angular2 components. Cf. screenshot here.

    • sits in examples/AllInDemo directory
    • npm install
    • npm run wbuild
    • then open with a local webserver the index.html in $HOMEDIR/examples/AllInDemo directory

    State Machine

    • go to $HOMEDIR/examples/volunteerApplication
    • npm install
    • npm run wbuild
    • then open with a local webserver the index.html in $HOMEDIR/examples/volunteerApplication directory

    Switch

    • go to $HOMEDIR/examples/SwitchLogin
    • npm install
    • npm run wbuild
    • then open with a local webserver the index.html in $HOMEDIR/examples/SwitchLogin directory

    OnRoute

    • go to $HOMEDIR/examples/NestedRoutingDemo
    • npm install
    • npm run wbuild
    • then open with a local webserver the index.html in $HOMEDIR/examples/NestedRoutingDemo directory

    ForEach and List

    • go to $HOMEDIR/examples/ForEachListDemo
    • npm install
    • npm run wbuild
    • then open with a local webserver the index.html in $HOMEDIR/examples/ForEachListDemo directory

    Contribute

    Contribution is welcome in the following areas :

    • devops
      • monorepos
      • whatever makes sense to make the repository more manageable
    • reducing build size

    Known issues

    TODO

    Keywords

    none

    Install

    npm i rx-component-combinators

    DownloadsWeekly Downloads

    13

    Version

    0.3.1

    License

    Apache-2.0

    Last publish

    Collaborators

    • brucou