Announcing NGXS 3.4 — Immutability helpers, HMR, SSR & much more

Mark Whitfeld
NGXS
Published in
5 min readFeb 28, 2019
Photo by Nicolas Tissot on Unsplash

NGXS v3.4 has been a long time coming. It is the result of months of hard work by the team and a steady focus on what will enhance the core of the library without compromising the simplicity that it offers.

Overview

  • 🚀 State Operators
  • 📶 New Actions Stream Pipe — onActionCompleted
  • ♻️ HMR Support
  • 🎨 SSR Support
  • 💠 New State Lifecycle Event — ngxsAfterBootstrap
  • ✂️ Decoupling from Zones.js
  • ⏫ Inheritance of State Options
  • 🔌 Plugin Improvements
  • 🐛 Bug fixes
  • 📃 Versioned Docs
  • 💥 New Labs Projects

State Operators

State Operators are a mechanism for expressing immutability updates in a declarative, type safe and code completion friendly way. This is a really exciting addition to NGXS. We have enabled a very simple mechanism in `StateContext.setState` to allow for an abstraction of the calculation of the new state to be passed to the `setState` call. This abstraction takes the form of a function that returns the new state given the existing state. This function is referred to as a State Operator and has the following signature:
<T>(existing: Readonly<T>) => T

We have included some common operators in NGXS to get you started but the real power lies in the ability for you to create your own operators to express the updates to your domain cleanly. See this page in our docs for more information: https://ngxs.gitbook.io/ngxs/advanced/operators

After leveraging this approach to make your own State Operators your @Action handler code could look like this:

Here the developer has defined their own addEntity State Operator that handles the updating of the entities and ids parts of their state.

A more in depth look at State Operators can be found in the following medium post:

New Actions Stream Pipe — onActionCompleted

Action Handlers are provided in NGXS to be able to observe the Actions stream and respond to the lifecycle events of actions. Before NGXS v3.4 the following pipes exist for filtering the Actions observable:

  • ofAction any lifecycle event happens for the specified action
  • ofActionDispatched when the specified action has been dispatched
  • ofActionSuccessful when the specified action has completed successfully
  • ofActionCanceled when the specified action has been canceled
  • ofActionErrored when the specified action has errored

Each of these pipes filter the Actions stream and provide the instance of the action as the resultant value in the observable. This is great but there are three pipes that represent the different possible completion outcomes, and the ofActionErrored returns the action and no information about the error (this will be fixed in the next major version).

In v3.4 we introduce a new pipe called ofActionCompleted. This new pipe handles all completion outcomes and returns a summary of the action completion in the following form:

interface ActionCompletion<T = any, E = Error> {
action: T;
result: {
successful: boolean;
canceled: boolean;
error?: E;
};
}

This new pipe is very useful and solves the problem of determining the error that caused an action to fail (see issue #518 ).

HMR Support

We have added a plugin called @ngxs/hmr-plugin which handles the details of getting hot module reloading working with NGXS so that the state is preserved between module reloads.

There is an extensive tutorial available here:

Details of this plugin are found in our docs here:

https://ngxs.gitbook.io/ngxs/plugins/hmr

SSR Support

We have adjusted the way that NGXS interacts with zone.js so that you can now use Server Side Rendering with your NGXS app and everything just works. No special effort required! …other than the usual Angular Universal setup process found here: https://angular.io/guide/universal

New State Lifecycle Event — ngxsAfterBootstrap

There have been some use cases where the NgxsOnInit lifecyle hook has not been sufficient because it runs before the Angular APP_INITIALISER lifecyle event. In v3.4 your NGXS States are now able to respond to an ngxsAfterBootstrap lifecycle event. This event is triggered when the application is fully rendered.

In order to make use of this lifecycle event you need to implement the NgxsAfterBootstrap interface in a State class and provide an implementation for the ngxsAfterBootstrap method exposed by this interface. This method will be invoked after the root component and all its children components are successfully bootstrapped.

See our docs here for more details:
https://ngxs.gitbook.io/ngxs/advanced/life-cycle#ngxsafterbootstrap

Decoupling from Zones.js

As an angular library NGXS has had to do some optimisations regarding Angular’s zone API when it handles actions. By default methods decorated with `@Action` decorator are called outside Angular’s zone which gives some performance benefits for the average usage scenarios. We have moved all logic related to the exection context under which NGXS executes into an execution strategy. This allows for an advanced user to customise the way that code is executed to optimise for their specific use case. For example, an application that uses OnPush change detection everywhere can simply provide the NoopNgxsExecutionStrategy so that there is no interaction with zones at all.

Another benefit of this approach is that zone.js is now isolated into a single point in the codebase which fits quite nicely for applications that do not use zones (when using the new Ivy renderer for example) and therefore would not need to take on this dependency.

More details can be found here: https://ngxs.gitbook.io/ngxs/advanced/options

Inheritance of State Options

Meta properties of a @State decorator can now be inherited from its super class (if present).

In the example above the state “b” will inherit the same default as the state “a”. Note: The only properties that aren’t inherited are the children and name properties.

Plugin Improvements

Our plugins have also recieved some love:

  • Feature: WebSocket Plugin — Add WebSocketDisconnected action to notify of disconnection #825
  • Fix: Websocket Plugin — server/network error triggered close should dispatch WebSocketDisconnected #832
  • Fix: WebSocket Plugin — WebsocketMessageError should notify of errors #825
  • Fix: Logger Plugin — Log group not closed on error #831
  • Fix: Form Plugin — correct state synchronization with dirty flag #862

Bug fixes and other features

  • Feature: Define the default state before module initialization #791
  • Fix: Throw error when duplicate state names are found #791
  • Fix: Bind static context to the selector function #818
  • Fix: Performance improvement reading the name of the state from the property name for parameterless @Select usages #826

Versioned Docs

Our docs have recieved a large number of contributions from our enthusiastic community and they are more thorough than ever before. We have also enabled a mechanism for versioning of our docs so that we can showcase new features in the development version of our docs without confusing users that are still on a stable version of the library. Older version of the library also have versions of the docs that align to their feature sets.

New Labs Projects

Async Storage Plugin

A community member has created the @ngxs-labs/async-storage-plugin which is a variant of the normal storage plugin but expects the storage interface to be asynchronous. This plugin enables the connection of various asynchronous storage implementations, opening up some use cases that were not possible with the standard synchronous plugin intended for in-browser storage.

The primary example of this plugin provides an implementation that makes use of Ionic’s storage service for storage. See the readme for more information: https://github.com/ngxs-labs/async-storage-plugin

--

--

Mark Whitfeld
NGXS
Editor for

A Software Developer passionate about writing clean, maintainable, high quality software that delights the user. #TDD #CleanCode #SoftwareCraftsmanship