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-signal-buckets

v2.0.1

Published

A lightweight implementation for Signal persistance in [Angular](https://angular.dev/).

Downloads

6

Readme

ngx-signal-buckets

A lightweight implementation for Signal persistance in Angular.

This library may be useful if you want to persist Signal values between sessions, but don't want the complexity of a store like ngrx.

It allows you to write your own persistence providers with a minimum amount of code.

It defaults to persisting Signals to localStorage and has built-in support for sessionStorage. You can also write your own persistence provider in a few lines of code by implementing the SignalProvider interface. It supports both synchronous and asynchronous persistence.

For the latest changes and the current version see the Change log.

Just create a service that extends the SignalBucket class and add your own properties and initialize them with the persistedSignal() method like in the example below. You need to call the initialize() method once to retrieve previously persisted values, e.g. after a successful login.

example

// Example of a signal bucket that persists to localStorage unless specified otherwise
@Injectable({ providedIn: 'root' })
class MySignals extends SignalBucket {
  // list of persisted signals is defined and initialized here
  property1 = this.persistedSignal('initialValue', 'id');
  property2 = this.persistedSignal(new SerializableClass(), {
    id: 'id2',
    persistence: SessionStoragePersistence
  });
  property3 = this.persistedSignal(new SerializableClass(), { id: 'id3' });
  // etc...
  propertyN = this.persistedSignal([] as PropertyArray, 'idN')
}


// Example of how to make sure all persisted signal values in the bucket are restored before proceding
// e.g. after the user has logged in, we want to retrieve the persisted state before we continue
class LoginPageComponent {
  userSignals = inject(UserSignalBucket);

  passwordSubmit() {
    if(password.isCorrect()) {
      userSignals.initialize(() => navigateToMainPage());
    }
  }
}

SignalBucket

The SignalBucket class is the parent class for the service(s) with Signals that need to be persisted.

Create a service that extends the SignalBucket class and add your own properties and initialize them with the persistedSignal() method like in the example below. You need to call the initialize() method once to retrieve previously persisted values, e.g. after a successful login.

The persistedSignal() method takes 2 parameters:

  • persistedSignal(initialValue: any, id: string)
    • initialValue: the initial value of the Signal
    • id: the id that identifies the value of the persisted signal for the persistence provider
  • persistedSignal(initialValue: any, options: PersistedSignalOptions)
    • initialValue: the initial value of the Signal
    • options: { id: string, persistence?: Type<PersistenceProvider> }
      • id: the id that identifies the value of the persisted signal for the persistence provider
      • persistence: the persistence provider class used for this persistent signal
@Injectable({ providedIn: 'root' })
class MySignals extends SignalBucket {
  // list of persisted signals is defined and initialized here
  property1 = this.persistedSignal('initialValue', 'id');
  property2 = this.persistedSignal(new SerializableClass(), {
    id: 'id2',
    persistence: SessionStoragePersistence
  });
  property3 = this.persistedSignal(new SerializableClass(), { id: 'id3' });
  // etc...
  propertyN = this.persistedSignal([] as PropertyArray, 'idN')
}

By default the persistence provider for the signals inside the Service that are defined with the persistedSignal method is LocalStoragePersistence. You can override the default persistence as follows:

class MySignals extends SignalBucket {
  override defaultPersistence = MyCustomPersistence;

  //... define public signal properties
}

You need to call the initialize() method once to retrieve previously persisted values, e.g. after a successful login.

// Example of how to make sure all persisted signal values in the bucket are restored before proceding
// e.g. after the user has logged in, we want to retrieve the persisted state before we continue
class LoginPageComponent {
  userSignals = inject(UserSignalBucket);

  passwordSubmit() {
    if(password.isCorrect()) {
      userSignals.initialize(() => navigateToMainPage());
    }
  }
}

PersistenceProvider

The PersistenceProvider interface defines a persistence provider. Several types of persistence providers are supported:

  • update signal value directly with set(value) or update(value => newValue), it will not wait until persistence has completed
  • update the signal value after persistence has completed
  • do not (directly) update the signal value with set(value) or update(value => newValue), but use sendSignal$ Subject and receiveSignal$ Observable in the PersistenceProvider for that (e.g. when using a webSocket to update and receive new Signal values).

The supplied LocalStoragePersistence and SessionStoragePersistence use the ngx-simple-serializer library to persist and restore values. You can use this in your own custom persistence provider like in the examples below or use your own method.

Example that persists to and from a server API

The server api call returns the stored value (which can differ from the posted value), and returns it in an Observable so the SignalBucket can update the Signal to that value.


// Small example of how to write a custom PersistenceProvider for your persistedSignals
// so you can persist values to a server
@Injectable({ providedIn: 'root' })
export class ServerPersistence implements PersistenceProvider {
  constructor(
    protected httpClient: HttpClient
  ) { }

  initialize(ids: Iterable<string>): Observable<SignalIdValue> {
    return this.httpClient.post<string[]>('retrieve.initial.persisted.id.values.url', [...ids]).pipe(
      switchMap(results => from(results)),
      map(serializedIdValue => deserialize(serializedIdValue))
      // TODO: handle errors
    );
  }

  persistValue(idValue: SignalIdValue) {
    return this.httpClient.post<string>('persist.id.value.url', serialize(idValue)).pipe(
      // TODO: handle errors
    );
  }
}

Example that persists to and from a websocket

The persistence provider passes updated values on to the websocket and receives updated values from the websocket.

// example for a persistence provider using a websocket to store and retrieve updates
@Injectable({ providedIn: 'root' })
export class WebSocketPersistence implements PersistenceProvider, OnDestroy {
  private subscription: Subscription;

  private webSocket = webSocket<WebSocketResult>({
    url: 'wss://websocket.url',
    serializer: serialize,
    deserializer: deserialize as () => WebSocketResult,
    openObserver: { next: () => this.authenticate() }
  });

  private authenticate() {
    this.webSocket.next({ 
      type: 'authentication', 
      content: 'authentication credentials (bearer token, username/password etc.)' 
    });
  }

  constructor() {
    this.subscription = this.sendSignal$.subscribe(idValue => this.webSocket.next({
      type: 'persistedSignal', 
      content: idValue
    }));
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }

  initialize(ids: Iterable<string>): Observable<any> {
    return this.webSocket.multiplex(
      () => ({ call: 'initializePersistedSignal', ids }),
      () => undefined,
      (message: any) => message.type === 'initializePersistedSignalResult'
    ).pipe(
      first(),
      mergeMap(message => from(message.content as SignalIdValue[])),
    );
  }

  sendSignal$ = new Subject<SignalIdValue>;

  receiveSignal$ = this.webSocket.multiplex(
    () => ({ subscribe: 'persistedSignal' }),
    () => ({ unsubscribe: 'persistedSignal' }),
    (message: any) => message.type === 'persistedSignal'
  ).pipe(
    map(message => message.content as SignalIdValue)
  );
}