Giter Site home page Giter Site logo

angular-signal-generators's Introduction

Angular Signal Generators Logo Angular Signal Generators

Angular Signal Generators are purpose built signals meant to simplify common tasks encountered in Components. Check out the demos for a better idea on how they can be used.

Statements Branches Functions Lines
Statements Branches Functions Lines

Installation

npm install @ddtmm/angular-signal-generators

Usage

You can import the signals from '@ddtmm/angular-signal-generators. The signals are used just like ordinary functions.

import { debounceSignal, liftSignal, timerSignal } from '@ddtmm/angular-signal-generators';

@Component({
  selector: 'app-signal-demo',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
<div>{{$secondsSinceStart()}}</div>
<div>
  <input type="text" [(ngModel)]="$debounced" />
  {{debounced()}}
</div>
<div>
  <button type="button" (click)="$liftedArray.push(secondsSinceStart())">
    Add Element
  </button> 
  {{$liftedArray() | json}}
</div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SignalDemoComponent {
  readonly $debounced = debounceSignal('type in me', 1000);
  readonly $liftedArray = liftSignal([0], null, ['push']);
  readonly $secondsSinceStart = timerSignal(1000, 1000);
}

Signals

asyncSignal

Takes an async source (Promise, Observable) or signal/function that returns an async source and returns that source's values as part of a signal. Kind of like an rxjs flattening operator.

debounceSignal

This is very similar to rxjs's debounce operator. This has two overloads - one where it accepts a signal and the value is debounced in a readonly signal, and one where it has a set and update method and the change of the value occurs after debounce time elapses.

DOM Observer Signals - intersectionSignal / mutationSignal / resizeSignal

These signals wrap the DOM observers IntersectionObserver, MutationObserver and ResizeObserver to output the last observation of changes to a target element passed to the signal.

extendSignal

Adds new methods to a signal - even hiding the existing methods if desired. It does this by passing the original signal or a "proxy" as the first parameter of the new method. This first parameter is obscured from the consumer so that it appears to be a normal method.

filterSignal

Filters values set to a signal to prevent the value from changing:
If the filter assigned at creation does not pass then the signal does not change. Can be used with guard functions.

liftSignal

"Lifts" methods from a signal's value to the signal itself just by passing a tuple of method names. The lifted methods should be those appropriate for mutating or updating the value. For example, lifting Array.push will add a method called push to the signal. Calling the push method will internally call signal.mutate() with a function that executes the push.

mapSignal

Creates a signal whose input value is immediately mapped to a different value based on a selector. Either a value or multiple signals can be passed and used in the selector function.

reduceSignal

Creates a signal similar to Array.reduce or Rxjs's scan operator, using a reducer function to create a new value from the current and prior values.

sequenceSignal

The Sequence Signal is useful for situations where you want to easily cycle between options. For example, if you want to toggle between true/false or a list of sizes. These are still writable signals so you can manually override the current value.

There is also a special option to pass a cursor, which is similar to an iterator, but can be reset. There will probably be more functionality added later.

Storage Signals - storageSignal / localStorageSignal / sessionStorageSignal

Signals that uses a secondary storage system to store values, ideally beyond the lifetime of the application. The next time the signal is initialized the initial value will come from this secondary storage. Implementations using localStorage and sessionStorage exist for your convenience.

timerSignal

This is very similar to rxjs's timer operator. It will be have like setTimeout or interval depending on the parameters passed. The value of the timer is incremented after every "tick".

tweenSignal

This was directly inspired by Svelte's tweened function. When the signal value is change, the observed value slowly morphs over time. So if the original value was 1 and the next value was set to 5, then the observed value will be something like 1, 1.512, 2.12, 2.6553, 3 over a set duration.

Utilities

signalToIterator

Converts a signal to an AsyncIterator. Once created, changes are retained until elements are looped through at a later time.

Conventions

SignalInput and ValueSource

As much as possible signals the functions provided try to create signals from either values or other signals. To accommodate this, many arguments are of type SignalInput<T> or ValueSource<T>.

SignalInput can be either something that can be either converted to a signal with toSignal, a function that can be passed to computed or a regular old signal. The purpose of this is to make things just a bit more convenient.

ValueSource

A ValueSource is a SignalSource or a value. The limiting factor here is that if you wanted to use a SignalSource as a value, then you'd have to wrap that in a signal.

const timerFromValue = timerSignal(1000);

const timeSourceAsSignal = signal(1000);
const timerFromSignal = timer(timeSourceAsSignal);

const timerFromComputedFn = timer(() => timeSourceAsSignal() * 2);

const timerSource$ = new BehaviorSubject(1000);
const timerFromObservable = timer(timerSource$);

Overloads

Several generators that accept a traditional value and a SignalInput will have different return types. Those that accept a SignalInput will return a read only signal, whereas those with a traditional value will have methods to update the signal, though not necessarily the same as a WritableSignal.

Injector

All signal generators have an options parameter that accept injector. This is either because effect is needed sometimes or if you toSignal is used.

Issues or Ideas?

I'm just adding signals as I run into real life problems. Please add an issue if you have an idea or run into a technical difficulty.

angular-signal-generators's People

Contributors

ddtmm avatar

Stargazers

François C avatar  avatar

Watchers

Lucian avatar  avatar  avatar

angular-signal-generators's Issues

Demo Site Improvements

* [ ] Make demos all a single component instead of two:
* [ ] Have a short description that will be used in both scenarios.
* [ ] Have a short demo that's optional in the long demo.

  • Enhance Documentation Quality.
  • Have side nav go all the way to bottom of the page
  • Make side nav scrollable.
  • Have footer to left of side nav.
  • Remove extra code from async signal.
  • Explore making demo nav dynamic while retaining the ability to pre render pages.
    • Routes file would have to be created.

Demo fix ups

  • Styles don't load in StackBlitz.
  • TweenSignal demos missing easing selector.
  • Document demo conventions.
  • async signal not responsive.
  • TweenSignal demos don't work in StackBlitz (For some reason adding effect(() => console.log(tweenSignalSimple()) in the constructor seems to fix the problem.)

Use untracked in signal factory functions

Because it is possible that a signal can be created inside another signal any time a signal is called inside a factory it could be come tracked. Therefore the factory functions should call untracked whenever a signal value is referred to directly but tracking isn't necessary.

  • Make any instances of *signal*() in signal functions into untracked(*signal*).
  • Add a global test that ensures a dependency is NOT created if a factory runs inside a computed signal.

AsyncSignal

Accepts a ValueSource that returns a promise like or subscribable object. Returns a signal that emits those values.

If a value doesn't emit before the signal changes then that value is ignored.

Could an AsyncIterable also be accepted? Should be easy.

PipeSignal

A signal that chains writable signals.

The last parameter can be an array which will multicast the previous value. The array can have writable signals or an array of writable signals or an array of writable signals or an array of writable signals, etc...

triggerSignal

Sometimes you just want an effect to fire because of some sort of trigger without having a value associated with it. The trigger signal will produce a unique value every time.

Maybe an alternative will be uniqueValueSIgnal or guidSignal.

The issue is that an effect will fire initially so it would need a triggerEffect, or an effect that skips the first emission.

Maybe a good idea? typeGuardSignal

A signal that only returns a signal if it matches a particular type.

type Cat = {
  readonly kind: 'cat';
  readonly name: string;
};

type Dog = {
  readonly kind: 'dog';
  readonly name: string;
};
type Animal = Cat | Dog;

const animal = signal<Animal>({ kind: 'cat', name: 'fluffy' });
const cat = typeGuardSignal(animal, (x): x is Cat => x.kind === 'cat', undefined); // last param is value if false

Revisit sequenceSignal

Right now it returns a writable signal, which makes no sense because it doesn't update the sequence. An appropriate signal type needs to be returned based on the capabilities of the cursor.

  • For arrays, make set find an index where the element is present, preferring a value forward first, and then starting from the begging.
  • Add capabilities to cursor that affect the signal type returned:
    • findIndex(value)? - necessary for setting. Searches forward, then from beginning. Returns -1 if nothing is found.
    • moveTo(index)? - necessary for setting.
    • reset()? - Make optional.
    • canMoveBackwards - causes next to throw if a negative is passed to next().
  • Add an option for behavior if set tries to set a value not present.
  • Return appropriate signal type based on capabilities:
    • If all a present then return a writable signal.
    • If canMoveBackwards and reset is not present the return a signal that can be set forward.
    • If no reset(), only a signal with next.
  • Add support for Iterables and Async Iterables.

Fix TimerInternal catchup issue

If TimerInternal setTimeouts aren't executing because the browser lost focus then it will catch up by calling setTimeout over and over again.

Possible solutions:

  • Use setInterval instead of setTimeout and let the browser behavior handle it.
  • Somehow execute the catch up's before the setTImeout
  • Calculate the index from elapsed time and resume at the next step.

Make mapSignal async

Have an overload that accepts a promise as a selector. Probably can't do this, because computed's are syncronous. Would have to be an effect.

nestedSignal utility

A function used in a computed that allows a signal to be created inside of it, but not recreated after it the first time so it's state isn't lost.

Essentially it will create a scope that checks if the signal was created and creates the signal. The first param would be the signal and the other params will be the signal's params.

Angular 18

Migrate to Angular 18. This should begin a 2.0 or 18 version that will only be compatible with 17 and 18.

*[ ] Replace code that copied functions from signal and use undocumented functions like nodeSetFn.

eventSignal

Subscribe to browser events similar to rxjs fromEvent.

Idea: MappedSignal

A mapped signal would be a writable Signal that would automatically transform itself after and update.

It would be created like this:

const myVar = mappedSignal(55, x => x + 1);

How is this better than computed? It eliminates the need for two signals, as under the hood that is what it'll be. Is there a real use case for this?

Issues

  • This just seems like rxjs's map operator. It doesn't seem like enough.
  • Should there be an option for if it's computed or untracked? If untracked once the value is calculated then there is no need for wrapping in a computed signal.

Browser api signal observers

Intersection, mutation and resize observer. Can't really think why this would be wanted, but it's something to react to

Filter and Pairwise signal

  • Make filter and pairwise signals just writable signals.
  • Change pairwise to history and allow depth to be specified

DeferSignal

Similar to rxjs's deferred. Basically will allow the ability to reference a signal declared later.

  1. It will create the signal only when it's first read.
  2. How is the better than computed.
  3. The following should work:
class Whatever {
  $deferred = deferSignal(() => mapSignal(this.$source, (x) => x + 1));
  $source = signal(5);
}

Can async signal be delayed a beat in case there is a signal used that is created after

The issue here is that if there is an async signal using a signal created after then it throws an error saying "this.{{signalName}} is not a function"

class SomeComponent() {
   const $a = asyncSignal(() => b$);

   const b$ = new BehaviorSubject(5)
}

This is likely because of this line:: let currentSource = untracked($input); If currentSource could be undefined initially and then set at the first read then that might fix the issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.