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

@mondosha1/feature-store-kit

v0.0.1-beta-032

Published

# Intro

Downloads

7

Readme

FSK: Feature Store Kit

Intro

What is the FSK?

FSK is a set of helpers and tools to help building NgRx feature stores for lazy-loaded libraries.

Features:

  • Allows synchronization between NgRx stores, Angular forms and Angular route parameters (cf Managing State in Angular Applications by Victor Savkin).
  • Avoid boilerplate: write actions, reducer and effects only if you need them!
  • Not a replacement of NgRx but built on top of it. No inheritance, only composition: easily add or remove FSK from a feature store without impacting its behavior.

What is it based on?

FSK takes benefits of standard technologies used in an Nx application:

How to use it?

Today you can choose how you want to integrate the FSK in your own code.

The traditional way: by your own bare hands.

This means you have to add all configuration needed inside your module, and implement all you need (state, reducer, structure, effects, etc…)

This will be necessary if you have to implement it inside an old module.

But if you create a new fresh module, we recommend you to use our schematic !

This will create you the entire module customized for your needs.

Let’s see that more in details

With the Mondosha1 library schematic

We recommend you to use Nx Console for VSCode or for Webstorm when you want to use our schematic. This tool provide you a nice UI and let you check a dry run version of what you will generate with the schematic.

Let’s see what you have to to generate your FSK library.

As described by the gif below, first go to generate → library .

The only mandatory information is the name of your library.

image

Now let’s see in details what options you can use in order to customize your new library.

image

With your little hands

  1. Declare the state, initial state and structure
  2. Inject the FeatureStoreModule
  3. Add FSK onto route definition
    1. Angular Routes
    2. Dialog paths
  4. Generate the form with the form helper
  5. Use effect helpers
  6. Create your selectors
    1. The standard NgRx way (with createFeatureSelector)
    2. Reselect on FSK (selector based on getStateWithoutMetaData w/ props)
  7. Create a facade (optional but advised)

How does it work?

Update flow

image

Source: https://www.lucidchart.com/documents/edit/c71861f2-377f-4a4a-89b3-661e45090461/0?beaconFlowId=CCE36D22DB246700#

  1. An effect on ROUTER_NAVIGATION (see ngrx/router for more information) parses router parameters (see difference with router params and query params) from the URL, formats them depending on the defined structure and triggers the updateStoreFromParams action to update the store. The effect and its mechanic is only triggered if the router segment which parameters are attached on correspond to the feature store key. Then, the updateStoreFromParams action is handled by the feature store meta-reducer and updates the store for the matching feature store key. \
  2. In the feature store form helper, the form subscribes the stateWithoutMetaData$ method from the facade (listening to the feature store stateWithoutMetaData selector). When a new state is emitted by the selector, the form will patch its value and possibly make some additional controls changes (for form arrays for example). \
  3. Still in the feature store form helper, a subscription on form value changes

Reset

Submit

Documentation

Module

FSK provides an Angular Module named FeatureStoreModule which is given a configuration object.

Below is the list of available options:

Structure

Intro

The FSK structure file is a serializable representation of the state and its typing. It should make the state and its possible sub-parts easily understandable.

Why?

The structure is used for form groups, arrays and controls generation on the one hand. And it’s used for router params parsing and formatting in the other hand.

Structure format

The format of the structure is opinionated and is presented as a TypeScript interface.

Why don’t we use the format of controls configs from the Angular Form Builder?

Because, we want the structure to be serializable in order to possibly retrieve structures dynamically, from HTTP requests for example.

Controls configs are not serializable as they may contain Angular validators which are functions.

Why don’t we use a JSON file following the JSON Schema specification?

The main benefit of a plain object is that it’s strongly typed. Interfaces and types describe how should be declared the structure. Same types are used by the structure helper in order to generate the reactive forms. But we could handle a JSON schema format additionally for dynamic structures retrieval.

Form controls generation

Depending on the given structure, a reactive form will be generated with form controls, form arrays or child form groups.

Default values of form controls are handled by the NgRx initialState and are not duplicated in the structure to keep DRY.

See the part To complete

Effects

Intro

To complete

updateStoreFromParams

To complete

navigateToStore

To complete

updateParamsFromForm

To complete

initStoreFromParent

To complete

submitIfValid

To complete

resetStoreOnLeave

To complete

Form

Form groups, controls and arrays

Form controls are declared in the structure

Form group

The root level of the structure must be a plain object and will inevitably generate a form group. It’s the obvious behavior as the root level of an Angular Form is a form group.

It is also possible to generate child form groups, for example for structuring the store into sub-parts or into split smart components. To achieve this, the structure part corresponding to the child form group should also be an object where each key is associated a form control to.

In the example below, engine is a child form group of the structure where name and cylinders are controls of the child form.

{
  brand: 'Peugeot',
  engine: {
    name: 'string',
    cylinders: 'number'
  }
}

Form control

Controls are always leaves of a form group. In this way, it can be declared:

  • In the simplest way, as a simple field type: 'string' **| **'number' **| **'boolean' **| **'object' **| **'date'

    {
      brand: 'Peugeot'
    }
  • With the combination of a type and validators:

    {
      brand: {
        type: 'Peugeot',
        validators: ValidatorName.Required
      }
    }
  • As an array of simple fied types:

    {
      valves: {
        items: 'number',
        type: 'array'
      }
    }

For array of complex types, a form array will be generated instead of a form control.

Form array

Array of complex items will generate Angular Form Arrays where each item will be represented as a form group with associated controls, following the item structure.

// structure of an array of complex field types
{
  wheels: {
    type: 'array',
    items: {
      width: 'number',
      height: 'number',
      diameter: 'number'
    }
  }
}

// will generate the Angular Form Array
new FormArray([
  new FormGroup({
    width: new FormControl(),
    height: new FormControl(),
    diameter: new FormControl()
  }),
  …
])

Array of objects do not always need to be represented as an array of complex types. A form control of type object may be sufficient.

To conclude, it really depends on the need to edit or not values of the array items themselves.

Validators

Structure validators are serializable and used for Angular Forms generation only.

There are two kinds of validators:

  • Angular validators
  • Formulas

Angular validators

To remain serializable, the angular validators are declared as string in the structure.

{
  name: {
    type: 'string',
    validators: 'required'
  }
}

A string enum helps retrieving them.

{
  name: {
    type: 'string',
    validators: ValidatorName.Required
  }
}

Validators requiring parameters (like maxLength) can be declared using a plain object:

{
  name: {
    type: 'string',
    validators: {
      name: ValidatorName.MaxLength
      params: { maxLength: 4 }
    }
  }
}

Like standard Angular Form controls declaration, several validators can be given.

{
  url: {
    type: 'string',
    validators: [ValidatorName.Required,ValidatorName.Url]
  }
}

Sometimes, you want to apply your validator only for some use cases. For that, you can use the condition property for a validator definition. This one accept formulas.

{
  id: 'number',
  url: {
    type: 'string',
    validators: [{
      name: ValidatorName.Required,
      condition: 'ISEMPTY(id)'
    }]
  }
}

Formulas

Formulas are a way to validate a form control using an expression as a string (still serializable) in the same manner as we write formulas in Excel.

It uses the amazing Javascript Expression Evaluator (or expr-eval) library for formulas parsing and evaluation. Formula expressions accept either values, arithmetic or logical operators, and functions. Each property of the feature store state is available as a value and the list of available functions can be read below.

An should evaluate to a boolean value and is accompanied by an error message which will be displayed if the expression evaluates to TRUE. In other words, the expression can be read as “What should throw an error?”.

{
  url: {
    type: 'string',
    validators: {
      formula: 'NOT(REGEX(url, "^http[s]?://datastudio\\\\.google\\\\.com/embed(/[a-z]/[0-9])?/reporting/[^/]/page/[a-zA-Z]{4}$"))',
      message: 'The entered URL is not a valid Google Data Studio dashboard URL'
    }
  }
}

Below are the set of functions are available in formulas.

Standard

Constants

Helpers

Logical functions

Advanced functions

Store binding

On value changes

The Angular form is tightly coupled to the store. Every change in any field of the form will trigger an update of the corresponding field in the store.

The form updates the store in two different ways depending on the chosen FormUpdateStrategy.

  • ToStore: form values are directly sent to the store which is updated thanks to an action and a reducer case.
  • ToParams: form values are sent to as segment parameters of the current route before updating the store. The route parameters are updated from an action and an effect, then the store is updated thanks to an effect listening to the router actions (see https://ngrx.io/guide/router-store) and a reducer.
this.formGroup = this.featureStoreFormFactory.getFormBuilder(MY_FEATURE).create({
  takeUntil$: this.destroy$,
  updateStrategy: FormUpdateStrategy.ToParams
})

To trigger params or store update, a subscription to the valueChanges observable provided by the form is used.

To avoid leaks, the subscription should be cancel when the component handling the form creation is destroyed, that’s why a takeUntil$ prop should be fed and correspond to an observable which completes at the component destroy.

While the form values are updated in the store, the form validity is also synchronized in the metadata of the store.

Formatter

It is possible to give a custom formatter function to store values which differ (a bit) from the values returned from the form.

The example below store the foo property as a number while the form field returned it as a string.

FeatureStoreModule.forFeature<MY_FEATURE>({
  featureStoreKey,
  initialState,
  structure,
  formatter: state => ({ foo: parseInt(state.foo, 10), ...state })
})

It is also possible to get the previous form value in order to compare them and by the way, format the data differently.

FeatureStoreModule.forFeature<MY_FEATURE>({
  featureStoreKey,
  initialState,
  structure,
  formatter: (newState, oldState) =>
    newState.color !== oldState.color ? { colorUpdated: true, ...newState } : newState
})

Ask for validation

The feature store form subscribes to the askForValidation$ selector which returns true when the askForValidation action is called (manually or via the submitBeforeLeaving guard).

Once validation asked, the form marks all its controls as dirty (which usually shows form fields errors if they have) and call the submit action. This action is responsible for checking the validity of the form (thank to the corresponding metadata) before triggering any user-defined side-effect stream.

Patch value

The last responsibility of the form is to listen to the store changes and update the corresponding form fields. Only the fields specified in the structurePathsForForm property are updated (an empty array or no property given will whitelist all the structure).

Such updates also work for Angular FormArrays and will create or remove items depending on the changes made in the store. This is a tricky part of the form update as Angular does not help us a lot (mutability, event emitting, etc).

Meta-reducer & emptyReducer

Meta-reducer

To complete

emptyReducer

To complete

Selectors and facade

Facade

To complete

Selectors

To complete

Child store

To complete

FAQ

Why is a form array created while I expect a simple form control?

You probably declared your array entry in the structure using a flatten object as items instead of the simple type object. As explained above, a flatten object will create a form group for each object and by the way will create a form array as parent.

Glossary