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

ngx-stateful

v1.1.2

Published

Be obnoxiously clear about which of your Angular components own state

Downloads

6

Readme

ngx-stateful

npm version Coverage Status Build Status Bundle Phobia

Sample App

Simple Stackblitz App (it's not a "ToDo") to show you how ngx-stateful works.

Why Stateful?

First of all, you probably don't need this package, it's completely trivial to implement yourself using a StateSubject. It looks like ngrx has tried to do this but they've failed to KISS. Plus (unpopular opinion ahead) dependency injection in Angular is not the way to go. Not because DI is inherently bad, but because most developers just don't use it for it's intended purpose. Instead of relying on abstract dependencies and "programming to the interface", it's used simply to share state around various services in the application in weird ways that are hard to debug.

Secondly, just because your application uses state (spoiler alert: every application has state), doesn't mean it needs all the boilerplate of a redux implementation.

After working on an (unncessary) large scale angular application with way too many components with devs who had a variety of skill levels, I realized how unclear the concept of State is to a lot of developers, even though they may be using redux to manage their state.

I blame OOP principles (yeah, I said it). When components are backed by instances of classes, often developers tend to put instance variables on their components without fully realizing that, by doing so, they are causing that component to become a StatefulComponent (see StatefulWidget if you know anything about Flutter).

Aside - this is also the reason why I'm such a big fan of React moving to functional components - not because react is better, but just because it tries to force developers to critically think about state before using it (typing useState comes with more forethought than this.someString = 'fred', IMO.)

Back to Angular components with instance variables - these components then own something, and when state is scattered up and down your component tree (without it being clearly identifiable - does this component class have instance variables? How are they used?) then it just becomes a nightmare to onboard yourself into a large codebase.

So let's start making it obnoxiusly clear to all of your team members when a component in Angular owns state - it must extend StatefulComponent<T>, and whenever something about that state changes, it must call setState.

It provides some neat helper methods that make it easy to discern when and why state is being updated without looking through your component file for this.someProp = X and then checking your template file to see if someProp is being bound to something in the template.

Aren't you just trying to make Angular like React?

YES... kind of, but that's because it is like React. React, Vue, Angular, and Flutter are all effectively the same - they render a tree of components, some of which own state (instance members? computed properties?) and the tree needs to update (render) when things change. All of these frameworks can be written in the exact same style, and they probably should be, it makes moving between applications written in in various frameworks tremendously easy. Unidirectional data flow and the composable component pattern of building user interface applications have easily won the day, so we, as developers, should embrace it fully rather than trying to invent new state management patterns each time a new framework shows up - looking at you BLOC.

How to use

import { StatefulComponent } from 'ngx-stateful';

interface MyState {
    fetching: boolean;
    data?: string[];
    error: false;
}

@Component({
    selector:'app',
    template:`
        <ng-container *ngIf="fetching$ | async; then spinner; else result"></ng-container>

        <ng-template #result>
            <ng-container *ngIf="error$ | async; then error; else data"></ng-container>
        </ng-template>

        <ng-template #spinner>
            <mat-progress-spinner></mat-progress-spinner>
        </ng-template>

        <ng-template #error>
            <app-error-component [error]="error$ | async"></app-error-component>
        </ng-template>

        <ng-template #data>
            <app-my-data-displayer [data]="data$ | async"></app-my-data-displayer>
        </ng-template>    
    `
})
export class MyComponent extends StatefulComponent<MyState> implements ngOnInit {

    public fetching$ = this.selectKey<MyState['fetching']>('fetching');
    public error$ = this.selectKey<MyState['error']>('error');
    public data$ = this.selectKey<MyState['data']>('data');

    constructor() {
        super({
            fetching:false,
            error:false
        })
    }


    async ngOnInit() {
        this.setState({fetching:true});

        try {
            this.setState({data:await fetchSomeData()});
        } catch(err) {
            this.stateState({error:true});
        }

        this.setState({fetching:false});
    }

}

API

Any component class that extends this must call super() with the state:

class MyComponent extends StatefulComponent<MyState> {
    constructor() {
        super({...your state})
    }
}

  • public state: T

The current IMMUTABLE state of the component.

  • public state$: Observable<T>

An observable of your components state, think async pipe in your template.

  • public setState(state: StateSetter<T>)

Sets the state of the component by either patching it or using the function signature.

private startFetching() {
    // patch function signature
    this.setState({fetching:true});
}

private stopFetching() {
    // callback function signature
    this.setState(state => ({...state,fetching:false}));
}

  • public select<R>(selector: (state: T) => R): Observable<R>

Retrieve state with a selector (see reselect).

public fetching$ = this.select(state => state,({fetching}) => fetching);

  • public selectKey<R>(key: string): Observable<R>

Retrieve state by string key (when your state is an object).

public fetching$ = this.selectKey<boolean>('fetching');

  • *public onChange<R>(selector: (state: T) => R): Observable<[R,R]>

Returns on observable that emits only when a certain piece of the state changes.

  • *public onKeyChange<R>(key: string): Observable<[R,R]>

Same deal as selectKey

*Note: onChange and onKeyChange DO NOT emit if the piece of state being selected has never changed. See pairwise.