- add
SignalGroup#hasSignal(name)
helper - refactor naming of internal constants
- rename
SignalGroup#getSignal(name)
helper toSignalGroup#signal(name)
- remove obsolete type SignalFuncs
- improve README and CHANGELOG → Migration Guide to v0.17.0
- minor maintenance release
- exclude unused images from npm package output
❗BREAKING CHANGES❗
- refactor
createSignal()
andcreateEffect()
api calls- introduce the
Signal
class (formerlySignalObject
)- as return result of
createSignal(): Signal
- rename previous
Signal
type →ISignalImpl
- as return result of
- introduce a new
Effect
class- as return result of
createEffect(): Effect
- rename previous
Effect
class →EffectImpl
- as return result of
- rename some
createSignal()
options- rename
compareFn
→compare
- rename
beforeReadFn
→beforeRead
- rename
- introduce the
- introduce the new
SignalGroup
API - remove some awkward and mistakable decorators
- remove
@signalReader()
- remove
@effect()
- remove
- refactor public api exports
- rename
queryObjectSignal()
→findObjectSignalByName()
- rename
getObjectSignalKeys()
→findObjectSignalKeys()
- rename
getObjectSignals()
→findObjectSignals()
- rename
destroySignals()
→destroyObjectSignals()
- rename
- cleanup types
- remove
connect()
,unconnect()
andclass Connection
- introduce
link()
,unlink()
andclass SignalGroup
- as a more general approach and replacement of the previous connection api
The signature of the call to createSignal()
has changed; a signal object is now returned.
The previous calls in the form const [val, setVal] = createSignal()
can be transformed into the form const {get: val, set: setVal} = createSignal()
. Alternatively, you can now simply call const val = createSignal()
and read the signal using val.get()
or val.value
and write it using val.set()
.
Similarly, the createEffect()
function now also returns an effect object.
The previous call const [run, destroy] = createEffect()
should be rewritten as follows: const {run, destroy} = createEffect()
. Alternatively, simply use the effect object:
const effect = createEffect(...)
...
effect.destroy()
The SignalGroup
API now replaces the awkward @signalReader
decorator.
For each object that uses the @signal()
decorator, a SignalGroup
is automatically created, in which the signals are stored according to their name.
It is therefore possible to retrieve the signal api object via group.getSignal(name)
.
Before:
class Foo {
@signal() accessor bar = 123;
@signalReader() accessor bar$;
}
const f = new Foo();
f.bar$((val) => {
console.log('bar changed to', val);
});
After:
class Foo {
@signal() accessor bar = 123;
}
const f = new Foo();
const bar = findObjectSignalByName(f, 'bar');
bar.onChange((val) => {
console.log('bar changed to', val);
});
The SignalGroup
API now replaces the mistakable @effect
decorator.
The necessity to call the methods annotated as @effect()
in the constructor once has led to misunderstandings and ambiguities, especially when it was an effect with static dependencies. With the new attach
option for effects, the behavior is now explicit and clear.
Before:
class Foo {
@signal() accessor bar = 123;
@signal() accessor plah = 'abc';
constructor() {
this.staticEffect();
this.dynamicEffect();
}
@effect(['bar', 'plah'])
staticEffect() {
console.log('bar, plah :=', this.bar, this.plah);
}
@effect() dynamicEffect() {
console.log('plah, bar :=', this.plah, this.bar);
}
destroy() {
destroySignalsAndEffects(this);
}
}
After:
class Foo {
@signal() accessor bar = 123;
@signal() accessor plah = 'abc';
constructor() {
createEffect(() => this.dynamicEffect(), { attach: this });
createEffect(
() => this.staticEffect(),
[ 'bar', 'plah' ],
{ attach: this },
).run();
}
staticEffect() {
console.log('bar, plah :=', this.bar, this.plah);
}
dynamicEffect() {
console.log('plah, bar :=', this.plah, this.bar);
}
destroy() {
destroyObjectSignals(this);
}
}
Replace all occurrences of SignalObject
(which was introduced in version v0.14.0) with Signal
. The methods have not changed.
The legacy connection api is now replaced by the signal group feature and the link()
and unlink()
utility functions:
In most cases, it should be sufficient to simply replace the connect()
calls with link()
calls. Similarly, unlink()
replaces the function unconnect()
, although unlink()
is often not necessary at all; links between signals are automatically cleaned up when one of the signals is destroyed.
Links to object signals must be adapted, e.g. with:
link(sigFoo, findObjectSignalByName('bar'))
.. or by using the new group api:
link(groupA.getSignal('foo'), groupB.getSignal('bar'))
- update to
@spearwolf/eventize@4.0.1
- use
Symbol.for
for constants
maintenance update
- no new feature inside!
- just updated most build dependencies
- BUT also updated the (only) runtime dependency @spearwolf/eventize to v4.x: and this is a ❗BREAKING CHANGE❗ since the new eventize api switches to the functional api by default
- so you may need to make adjustments to your codebase if you use the eventize api directly (independently of signalize)
createSignal()
now returns a polymorphic api- a new object-based api is returned, see the SignalObject class for details
- but the returned api can still be used as an array of [reader, writer] functions
- so you don't need to change existing code that uses the reader and writer function syntax
- but you can use the new object-based api, which may be more convenient (depending on your coding style and context)
- more docs will follow later ;)
- upgrade build dependencies
maintenance release
- upgrade build dependencies
- remove unnecessary optional dependencies
createEffect()
now also supports async callbacks. if an async effect callback creates a cleanup callback as return value, it will be executed like a normal cleanup callback when the effect is re-executed
- add the
beQuiet()
helper for dynamic effects. within the beQuiet callback, an active dynamic effect will not be noticed when a signal is read. - add another test to demonstrate the dynamic nature of effects
- fix
@effect
decorator types
- the
@effect
decorator now supports the specification of static signal dependencies (viasignal
ordeps
options)- in this case, you can use the
autostart: false
option to control whether the effect is executed immediately when the effect method is called for the first time - or only later when one of the static signal dependencies changes - by default (if it is not specified), then
autostart
is activated
- in this case, you can use the
- if no name is specified in the
@signalReader
decorator, then the name is automatically determined from the accessor field name. with the special feature that the field name is cut off at the end if the field has a$
in the name. for example, the signal namefoo
is extracted from the field namefoo$
- ensure that each object has its own signal instance when using the
@signal
decorator - add
name
andreadAsValue: true
options to@signal
decorator - introduce
@signalReader({name: 'foo'})
class accessor decorator - export
getObjectSignalKeys(obj)
helper
- the createEffect api was enhanced
createEffect(callback, [sigA, sigB, ..])
- similar to react's createEffect hook, you can now (optionally) specify a dependency array. in the dependency array, you specify the signals that will execute the effect on change. the signals do not have to match the signals used in the effect callback. if such static dependencies are specified, the effect callback will no longer be executed automatically when you create the effect. it will only be executed later if at least one signal changes.
- a signal reader callback is no longer called immediately ..
- only when the signal changes
- the callback is no longer called as a dynamic effect
- it only uses the original signal as a static effect dependency
- introduce the type helper
SignalFuncs<Type>
— the return value type ofcreateSignal()
- the pre-compile step for jest is omitted, now ts-jest is used and jest can be called directly without any indirection 🥳
- the decorators are no longer included in the default export (index.js)
- to use the decorators, the user must import them from `@spearwolf/signalize/decorators'
- fix package type definitions
- no commonjs format is delivered anymore
- the esm format is no longer bundled
- use
import type ..
syntax
- switch package to
type: module
- this hopefully solves the problem that typescript cannot resolve the types correctly when
signalize.mjs
is loaded 😵 - the final package output will now completely omit
.mjs
file endings
- this hopefully solves the problem that typescript cannot resolve the types correctly when
- mark package as side effects free
- update (mainly dev) dependencies
- upgrade dev depenedencies
- this includes an upgrade from typescript 5.1 to 5.2, which brings with it new build artefacts
- upgrade dependency
@spearwolf/eventize
tov3.0.0
- remove
type=module
from package.json- instead, use
*.mjs
file extension for esm output
- instead, use
- introduce CHANGELOG 😉
- upgrade to typescript@5
- refactor build pipeline
- mute, unmute and destroy signals
muteSignal(get)
unmuteSignal(get)
destroySignal(get)
- fix effect cleanup callback
- if an effect is executed again, the cleanup callback from the last effect is called first (the behavior is similar to the react.useEffect() cleanup function)
- add
getEffectsCount()
andonDestroyEffect()
helpers - auto cleanup/unsubscription of effects and memos when all their signals are destroyed
- change signature of the
createEffect()
helper: an array with a run and unsubscribe function is now returned - refactor child effects
- typescript: export all types