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

@tinystate/core

v0.3.0

Published

A tiny state management library for Angular

Downloads

2

Readme


Build Status codecov npm version Supported Angular versions: 6+ License: MIT


Introduction

State management in every component-based application is hard. TinyState tries to solve the following problem:

Sharing state between components as simple as possible and leverage the good parts of component state and Angular`s dependency injection system.

Demo

Demo on Stackblitz.io

Installation

yarn add @tinystate/core
# or
npm install @tinystate/core

Loading the module in the app/root module

import { TinyStateModule } from '@tinystate/core';

@NgModule({
  imports: [
    CommonModule,
    TinyStateModule.forRoot()
  ]
})
class AppModule {}

Example

import { Container } from '@tinystate/core';

export interface CounterState {
  count: number;
}

/**
 * A Container is a very simple class that holds your state and some logic for updating it.
 * The shape of the state is described via an interface (in this example: CounterState).
 */
export class CounterContainer extends Container<CounterState> {
  getInitialState(): CounterState {
    return {
      count: 0
    };
  }

  increment(increment: number) {
    this.setState(state => ({ count: state.count + increment }));
  }

  decrement(decrement: number = 1) {
    this.setState(state => ({ count: state.count - decrement }));
  }
}

@Component({
  selector: 'my-component',
  template: `
    <h1>
      Counter: {{ counter$ | async }}
    </h1>
    <button (click)="increment()">Increment</button>
    <button (click)="decrement()">Decrement</button>
  `,
  providers: [
    CounterContainer
  ]
})
export class MyComponent {
  counter$: Observable<number> = this.counterContainer.select(state => state.count);

  constructor(private counterContainer: CounterContainer) {}

  increment() {
    this.counterContainer.increment(1);
  }

  decrement() {
    this.counterContainer.decrement();
  }
}

Global state

The example shown above creates a CounterContainer instance for the MyComponent and is also injectable for all child components of the MyComponent.

If you have global state that should be injectable in all your components, add the providedIn: 'root' option to the @Injectable decorator of the Container:

@Injectable({
  providedIn: 'root'
})
class CounterContainer {}

With the configuration show above, you can inject the CounterContainer container in every component of your application.

Testing containers

Testing containers is really easy. Let's say we want to write a test for the following container:

import { Container } from '@tinystate/core';

export interface CounterState {
  count: number;
}

export class CounterContainer extends Container<CounterState> {
  getInitialState(): CounterState {
    return {
      count: 0
    };
  }

  increment() {
    this.setState(state => ({ count: state.count + 1 }));
  }
}

Here's an example of a possible test with Jasmine:

import { CounterContainer } from './counter.container';
import { TestBed, inject } from '@angular/core/testing';

describe('CounterContainer', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [CounterContainer]
    });
  });

  it(
    'should have an initial count state of 0',
    inject([CounterContainer], (container: CounterContainer) => {
      let count: number | undefined;
      container.select(s => s.count).subscribe(s => (count = s));
      expect(count).toEqual(0);
    })
  );

  it(
    'should increment the count by one when calling increment',
    inject([CounterContainer], (container: CounterContainer) => {
      let count: number | undefined;
      container.select(s => s.count).subscribe(s => (count = s));
      container.increment();
      expect(count).toEqual(1);
    })
  );
});

Testing components that use containers

Let's write a test for the following component:

@Component({
  selector: 'my-component',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="count">{{ count$ | async }}</div>
    <button class="inc-count" (click)="increment()">inc</button>
  `
})
class MyComponent {
  count$: Observable<number> = this.counterContainer.select(s => s.count);

  constructor(private counterContainer: CounterContainer) {}

  increment() {
    this.counterContainer.increment();
  }
}
describe('MyComponent', () => {
  let counterContainer: CounterContainer;

  beforeEach(
    async(() => {
      counterContainer = jasmine.createSpyObj<CounterContainer>('CounterContainer', [
        'increment',
        'select'
      ]);
      return TestBed.configureTestingModule({
        declarations: [MyComponent],
        providers: [{ provide: CounterContainer, useValue: counterContainer }]
      }).compileComponents();
    })
  );

  it('should increment via the counter', () => {
    const fixture = TestBed.createComponent(MyComponent);
    fixture.debugElement.query(By.css('.inc-count')).triggerEventHandler('click', null);
    expect(counterContainer.increment).toHaveBeenCalledTimes(1);
  });
});

Redux Devtools Support

To enable support for the Redux Devtools Extension, add the following module to your root NgModule:

import { TinyStateModule, ReduxDevtoolsPluginModule } from '@tinystate/core';

@NgModule({
  imports: [
    CommonModule,
    TinyStateModule.forRoot(),
    ReduxDevtoolsPluginModule.forRoot()
  ],
  providers: [
    CounterContainer
  ]
})
class AppModule {}

TinyState hasn't the concept of Actions. So the action name will always be NO_NAME. But you will see how the state of all your containers change, which is even without action names useful for debugging:

Redux Devtools Demo Gif

FAQ

When should I use TinyState?

Do you have a global state or a state that is needed in several components that you want to share between them and think that solutions like NGRX or Redux are a way too heavy for your simple use case? - then TinyState could be for you.

TinyState is not a solution that should be seen as an alternative to NGRX or Redux because these projects are trying to solve different problems than TinyState wants to solve.

IMO local component state is totally fine as long as it works for you. So choose the right tool for the right job.

Can I use the action/reducer pattern with TinyState?

Nope. The goal of this project is to keep sharing state between components simple. If you think your state is too complex/big or you want a replayable, fully predictable state container, you should consider using NGRX, NGXS or Redux.

What are the differences between TinyState and Unstated?

  • Unstated supports React - TinyState supports Angular.
  • TinyState uses RxJS as the base for all the state handling whereas Unstated uses plain objects. RxJS plays very well together with Angular and allows powerful streaming transformations.
  • Unstated uses the React Context API and a self-implemented Injection pattern whereas TinyState uses Angular's built-in Hierarchical Dependency Injectors to create/assign Container instances to component hierarchies.
  • TinyState supports Redux Devtools and has a plugin API.