Code Monkey home page Code Monkey logo

observer-util's People

Contributors

benoitzugmeyer avatar devgrok avatar lemming avatar moritzuehling avatar mseddon avatar solkimicreb avatar yuler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

observer-util's Issues

should observe explicit array length changes

The following scenario doesn't work (explicitly deleting items by modifying array.length)

  it("should observe explicit array length changes", () => {
    let dummy;
    const list = observable(["Hello", "World"]);
    observe(() => {
      dummy = list[0];
    });

    expect(dummy).to.equal("Hello");
    list.length = 0;
    expect(dummy).to.equal(undefined);
  });

Shallow observing

I came across a problem using react-easy-state that feels like a limitation in observer-util.

To make sure that React re-renders when an object assignment changes, the object needs to be observed. But observing properties inside the object can have negative effects. Observing the object "pollutes" everything inside it by wrapping properties in proxies and propagating the proxies deeper and deeper into the hierarchy of nested objects as the object mutates over time.

Imagine the object comes from an unrelated Javascript library and cannot tolerate the performance impact of proxy observers on every nested property or object. In addition adding proxies prevents object equality checks from working when a proxied version of the object is compared with a non-proxied version. Again, because this is in a third-party library it is not possible to use raw to fix the equality.

It seems like the only solution would be to have a kind of "shallow" observer that only observes the object itself (or perhaps the container of the object), and none of the object's properties. It is not completely clear to me how this could work in a way that was not too burdensome on the programmer. I am wondering if you have thought about this problem before and have any ideas for solutions.

Observing only specific properties

What can be done if observable is applied to 3 different vars Nd need to provide observer to target variables instead of observing all 3

A discussion about MobX, NX and transparent reactivity

This issue is a response to this article by Michel Weststrate, which is a response to this article by me. I decided to not write another one, but try to spark a conversation about reactive programming here. I hope that this discussion might produce useful and new reactive programming ideas.

NX goals

This is a list of what NX tries to achieve and hopefully does currently achieve.

  • No reactions run more than once in a single stack
  • Reactions are deferred, but they run in the order in which they were first triggered by state modifications
  • Reactions are allowed to contain further state modifications
  • Reactions never cause infinite loops (consequence of first one)
  • No data is saved or modified by NX, so no staleness can be caused
  • Observables are transparent layers, that do not modify the underlying object but seamlessly forward all operations to it
  • All get (reaction registration) and set (reaction triggering) like language operations are picked up.

A kind request to transparent reactivity experts: if you think, that any of the above points are not working correctly in NX please provide a small example snippet.

About stale values

There is no computed in NX, you should use getters/setters instead. Check the following example.

const nx = require('@nx-js/observer-util')

const person = nx.observable({
  firstName: 'Dwane',
  lastName: 'Johnson',
  get fullName () { return `${this.firstName} ${this.lastName}` }
})

// outputs 'Dwane Johnson'  to the console after the current stack empties
nx.observe(() => console.log(person.nickName || person.fullName))

// outputs 'Dwane Bob' to the console
setTimeout(() => person.lastName = 'Bob')

If person.fullName is used anywhere in the code (in normal code or reaction), the getter function executes and produces a value. Value is never cached by NX and NX never ever stores data, it only pairs reactions with state property keys. As data is never stored or modified by NX, I don't think that it could cause stale data.

About the derivation dependency graph

I am still very uncertain about this, so if I miss something important please enlighten me. The following section explains how I currently think about this problem.

I don't see the reason for keeping track of a whole dependency graph. Keeping track of the state property - used by a reaction - and the top level reaction (wrapped in observe or autorun) seems enough to me. NX simply keeps track of these pairs. If the observed state properties change later, the belonging top level reactions are triggered by NX and they call the getters (computed values) and other functions in correct order.

image

The graph feels like a reconstruction of a stack to me, which is already defined by the current app state and the first function of the stack (saved top level reaction) anyways.

For a more detailed explanation and a working sub 100 lines prototype see this article. The actual NX logic is also around 100-200 lines and it can be found here. The rest are just platform and Proxy specific bug fixes and workarounds.

About $raw

The $raw property in NX does actually work like untracked if it is used for get operations in reactions. If it is used for set operations it can work the other way too.

const nx = require('@nx-js/observer-util')

const person = nx.observable({
  name: 'John',
  age: 25
})

// outputs 'name: John, age: 25' to the console after the current stack empties
nx.observe(() => console.log(`name: ${person.name}, age: ${person.$raw.age}`))

// will not cause a rerun, since the observer only uses person.$raw.age
// outputs nothing to the console, similar to untracked in MobX
setTimeout(() => person.age = 30)

// outputs 'name: Bill, age: 30' to the console
setTimeout(() => person.name = 'Bill', 100)

// will not cause a rerun, since it only modifies person.$raw
// outputs nothing to the console
setTimeout(() => person.$raw.name = 'Anne', 200)

Even though age is used inside a reaction, changing person.age won't trigger the reaction because it used the $raw (untracked) version of age.

The reaction used the tracked version of name, but setting the $raw version of later won't trigger the reaction.

Please note that the observed and the $raw version are the same object so these two are always 'in sync'. Observables are transparent layers, that forward every operation to the raw object.

Questions

  • Do you think that manually keeping a dependency graph is necessary?
  • What are the main benefits of sync/async reactions in your opinion?
  • Do you think that mutating state in reactions is bad, even if it can be handled internally? Why?

rawToProxy WeakMap.set is never called

was reading through the code to see how observable is implemented with proxies. I saw this line https://github.com/nx-js/observer-util/blob/master/src/handlers.js#L28 and thought rawToProxy should work as a cache to avoid recreating proxy if some nested property is accessed multiple times. but after search by rawToProxy I could not find any set call.

I understand that it only affects perfomance but not affect the correcntess of behavior

edit: seems same is for proxyToRaw

Object.defineProperty should trigger reactions

Setting a new value or a new get function using Object.defineProperty should trigger reactions on the relevant key. This should support reacting to both modifying existing keys of and adding new keys to an object.

This would require adding a new Proxy trap and writing tests for the new behavior.

I'm willing and able to help put together a pull request.

Any thoughts?

Is it possible to receive the type of change when observing a value?

I'm trying to observe an array of objects and see what type of change was made.

If the array had stuff removed, I wanna call a specific function to respond to that.

If the array had stuff added, I wanna call a different function.

In mobX this was rather straight forward...observe would just return an object that could tell u a list of if things changed, added, or removed...

I was curious if you had an easy way to observe a type of change.

Edit*( Not only just observe the type of change, but maybe return the values changed?)

Like if 2 objects were pushed into the array, it return added: [object1, object 2]

I think mobx had something where it give u a list of objects that changed...

So if I added 2 objects, I'd get an array of added with both objects

If I had removed 2 objects, I'd get an array of removed of both objects)

Worst case scenerio... I maybe try to cache a second copy of the array, and compare a changed version to the old version using a function to determine the type of change...

Maybe thats the intended way to do it.

Again, this library is amazing. Thank you so much for how easy it is to use!

Understanding reactions

Why this behavior?

import { observable, observe } from "@nx-js/observer-util";

// This WORK
const counter = observable({ num: 0 });
const countLogger = observe(() => console.log("a-", counter.num));

// this calls countLogger and logs 1
counter.num++;
setTimeout(() => {
  // this calls countLogger and logs 4
  counter.num = 4;
}, 2 * 1000);

// This NOT WORK
const counter2 = observable({ num: 0 });
const countLogger2 = observe(() => console.log("b-", counter));

// this calls countLogger and logs 1
counter2.num++;
setTimeout(() => {
  // this calls countLogger and logs 4
  counter2.num = 4;
}, 2 * 1000);

// Output
//  a- 0
// a- 1
// b- Object {num: 1}
// a- 4

Why does observing counter instead of counter.num break the behavior?

Here is a sandbox

Observables arrays don't appear to observe correctly

I have an observables array that I observe to listen to changes.

On size change I update the information based on if it changed size.

Increasing the array with push seems to work fine. It reacts properly.

However splicing or poping it (reducing it's size) calls the reaction twice... This second reaction returns the same length array but with the value set to null in the position of where the pop happened (and I assume where any element removed)

This is causing me a lot of issues in being able to observe arrays changes....Ive had to resort to manually doing reactions...tho I'd prefer if it was automatic based on observe.

I read somewhere in another issue that arrays do weird things with proxy and observables...so I was curious if this could be fixed.

Question about scheduler batching and observe optimizations.

First of all I wanna say sorry for asking so many questions. I'm sure I've overwhelmed you and I apologize for it, it won't happen again. This mostly should be all the remaining questions I have.

I'm trying to speed up my observes by batching stuff together in a more optimal way.

I experimented with queue and set and found them both really useful.

I noticed that if I setInterval Set it always runs once the first reaction is fired. And doesn't stop either (i guess unless I manually stop it)

I was curious if their was a way to combine the automatic process of priorities, with the interval from set. I wanted to try making the priority low, and when it does fire it can only if the interval allows it.

Lastly I have each instance placed in the map set too observe its own observable self from the store to update properties. (this was carried from my mobx way of doing it)

I noticed that this way is very slow in this library compared to mobx so I assume its not batching in the same way and I'm doing something wrong here(I may have been doing wrong all along anyway)

Is there an easy way to batch the process of observing itself with other instances doing the same type of observing?

Or should I have this type of observable process done in a loop through all the instances outside the instance, rather then in the instance itself.

Thank you for taking the time to explain this stuff for me this is a really incredible library.

Mutating observables in reactions is forbidden

I don't understand why I am getting this error. The code where I am mutating the observable is not in a reaction? Here is a sample of what is happening in a custom element.

// create observable store
this.store = observable({
  clickCount: 0,
  clickTimestamp: null
});

// re-render component with async/dedupe Scheduler
observe(() => {
  this.renderCallback();
}, {scheduler: new Scheduler()});

// event handler mutating props
this.addEventListener('click', (e) => {
  this.store.clickCount = this.store.clickCount + 1;
  this.store.clickTimestamp = Date.now(); // <== I GET THE ERROR HERE
})

I am getting the error trying to set the second property on the observable.

Observing through a Proxy

Thanks for a fantastic library. So elegant!

I am trying to use it in a scenario where I have another proxy in order to make the object more elegant to use. I want to create an object which acts like an array, but also stores other data. However observe() doesn't flow through the proxy. I am not surprised it doesn't work, but I am looking for ideas on how to make it work.

import { observable, observe } from '@nx-js/observer-util';

class Foo {
  constructor() {
    this._collection = [111]

    return new Proxy(this, {
      get(target, prop, receiver) {
        if (prop in target._collection)
          return target._collection[prop]
        else {
          return Reflect.get(target, prop, receiver);
        }
      },
      has(target, prop) {
        if (prop in target._collection) {
          return true
        } else {
          return Reflect.has(target, prop)
        }
      }
    })
  }

  get data() {
    return this._collection
  }
}

const counter = observable(new Foo);
const workingObserve = observe(() => console.log(`Working: ${counter.data[0]}`));
const notWorkingObserve = observe(() => console.log(`Not working: ${counter[0]}`));

counter._collection = [1,2,3]
counter._collection = [7,8]

Note that the notWorkingObserve function is not executed when the collection is updated.

I am testing by just running this code in https://repl.it/languages/babel

[Optimization] Don't trigger observers if prev value and new value are same

For this snippet -

const observer  = require("@risingstack/nx-observe");

let context = observer.observable({"name" : "a"});

const signal = observer.observe(()=>console.log(context.name));

let name = context.name;

context.name = "a";

The observer is triggered twice. Wouldn't it help to not trigger observers if the value hasn't changed?

Documentation suggestion

I think it should be mentioned that observe(...) call is side-effectful. It runs the function first time immediately (to evaluate those proxies) and the reaction is almost always a side-effect like logging, mutating, with probably the only exception of doing nothing...

import { observable, observe } from '@nx-js/observer-util';

const counter = observable({ num: 0 });

+ // this calls countLogger and logs 0
const countLogger = observe(() => console.log(counter.num));

// this calls countLogger and logs 1
counter.num++;

There's a section of Reaction scheduling but it describes another aspect (of scheduling, while the first time it's always a sync run, no matter which scheduler is applied, right?).

Problem when changing whole objects

I am using a library that basically thin wrap this utility: react-easy-state.
It seems that re-assigning a new object to an observable property do not trigger the change.

Is there any special case?? Here is a simplification of what is happening to me.. I would like to have some suggestion of what can be wrong or I can debug this. Can't wrap my head around this problem:

// Component A
const A => view(() => <div>{store.nestedObject.title}</div>)

// Component B = this way DOES NOT WORK! Component A does not update
class A extends React.Component {

  updateNested = () => {
    store.nestedObject = object // => this is a big object I receive from server
  }

  render() {
    return <button onClick={this.updateNested}>click me</button>
  }
}


// Component B = this way WORK! Component A does UPDATE!!
class A extends React.Component {

  updateNested = () => {
    store.nestedObject.title = object.title // different approach that works
  }

  render() {
    return <button onClick={this.updateNested}>click me</button>
  }
}

It seems to not detect when I change the whole object

ignore rather than expose raw object

@solkimicreb, would it be difficult to expose an ignore function that operates identically to raw within an observer but without exposing the actual raw object?

this would allow me to avoid the unwrapping problem mentioned in #18 and the vulnerabilities that come with unwrapping. see lukeburns/immutator#2 for some more details.

in short, it would nice to prevent reactions from triggering while still preserving the properties of proxies that wrap observables:

let { observable, ignore } = require('@nx-js/observer-util')
let state = anotherProxy(observable({ time: 0 })
observe(() => console.log(ignore(state.time))) // triggers only once on initial run
setInterval(() => state.time++, 1000)

TypeError: Illegal invocation

Hi. I'm experiencing an issue when using react-easy-state, however the TypeError is coming from this lib so I thought it best to report issue here. This may not be a bug but rather a lack in my understanding of how proxies work or what limitations they may have, or perhaps the issue lies in the Tone.js library classes I'm trying to proxy, in any case here is an example that I think will illustrate the issue better than I can explain:

import React from "react";
import { render } from "react-dom";
import { store, view } from "react-easy-state";
import Tone from "tone";

const osc1 = new Tone.Oscillator();
const osc2 = new Tone.Oscillator();

const proxy = new Proxy(osc1, {
  get(...args) {
    return Reflect.get(...args);
  }
});

// works
console.log(proxy.frequency.value); // => 440

const oscStore = store(osc2);

// throws TypeError: Illegal invovation
const App = view(() => <div>{oscStore.frequency.value}</div>);

render(<App />, document.getElementById("root"));

codesandbox

In the example would expect oscStore to behave exactly like osc2 object or even the proxy object.

`construct` trap fails for observed constructors

Hi! Thanks so much for the library, it's been great to use.

We've been trying 4.3.0-alpha.2 to get the react-easy-state fix RisingStack/react-easy-state#184, and ran into a problem using constructors that have been observed -- at least with @babel/plugin-transform-classes. I haven't been able to test without it, but either way the behavior doesn't seem right.

An example might be easiest:

class Thing {}

const thing = new Thing(); // => Thing instance
const observableThing = observable(thing); // => Proxy wrapping Thing instance
const Constructor = observableThing.constructor; // => Proxy wrapping Thing()
const newThing = new Constructor(); // => Fails with Uncaught TypeError: Cannot call a class as a function

The failure happens because the construct function here is called as

construct(Thing, [], Proxy { Thing() })

When constructing the object, Thing() is called with this set to Proxy { Thing() }.prototype, which is also a Proxy. This means that this instanceof Thing will be false, which seems incorrect.

I think using the raw constructor as newTarget would be the easiest fix, but I don't know whether that's right.

4.2.0 broke something

I have been using version 4.1.3 without any problems, but when I tried 4.2.0 my model updates were not propagated downwards anymore. Is there something that changed when render() is called? reverting back to 4.1.3 fixed the issue.

I have code like this

 <InputPassword changes={v => loginModel.password = v}
   name="password"
   value={loginModel.password}
   big={true}/>

and my model is imported from

const loginModel = observable(new LoginForm())
export {loginModel}

I receive the new v values but when the component re-renders value hasn't changed.
I use it with preact but it's basically almost the same as react-easy-state

Any idea what is breaking it?

Bug or user error? Copying observable to other position in array converts it to the raw item

In this code, "this.items" is an observable array, that contains items that are observables themselves.
When I run this code:

 moveItem(from, to) {
    if (from === to) return;

    // this algorithm keeps the array length constant
    const itemToMove = this.items[from];
    if (to > from) {
      this.items.copyWithin(from, from + 1, to + 1);
    } else if (to < from) {
      this.items.copyWithin(to + 1, to, from);
    }
    this.items[to] = itemToMove;
  }

then all the items that are affected by the copy operations, that is, items affected by copyWithin() and also items affected by plain assignments, like in this.items[to] = itemToMove where itemToMove is a proxy, get replaced by their raw items inside the array. So what started oput as an array of proxies ends up as a mixed array of proxies and raw items.

I found one workaround by operating on the raw array first, but it is not elegant:

moveItem(from, to) {
    if (from === to) return;

    const items = raw(this.items);

    // this algorithm keeps the array length constant
    const itemToMove = items[from];
    if (to > from) {
      items.copyWithin(from, from + 1, to + 1);
    } else if (to < from) {
      items.copyWithin(to + 1, to, from);
    }
    items[to] = itemToMove;

    // we made changes on the raw array, because copyWithin and assignments applied to the proxy
    // 'this.items' strip the copied/assigned array elements of any proxies that might wrap them.
    // So we need to trigger a reaction explicity on this.items by clearing and re-assigning to it.
    this.items = [];
    this.items = items;
  }

Am I missing something? Is there a better way to do this?

Not sure I'm understanding this at all

In my class constructor I'm trying to observe an object that's passed in, like this:

observable(this.form)

observe(() => {
    console.log(this.form)
})

But when I change the form properties nothing is logged.

I'm also not understanding how the observe callback even runs - how does it know we're listening to the this.form observable? Surely it would be simpler to do it this way:

observe(this.form, ({ value, path }) => {
    // this.form has changed
})

Thanks

Instances of global classes not observable

Test case:

https://runkit.com/scriptspry/observer-utils-global-definitions-not-observable


const {observable, observe, isObservable} = require('@nx-js/observer-util');

class Counter {
    constructor() { this._count = 0 }
    increment() { this._count += 1 }
    decrement() { this._count -= 1 }
}

const c = observable(new Counter());
console.log('c', isObservable(c));

global.Counter = Counter;
const c2 = observable(new Counter());
console.log('c2', isObservable(c2));

2 modules, 1 observable

If 2 different modules require this lib like so

const observer = require("@nx-js/observer-util"); //module-a

const observer = require("@nx-js/observer-util"); //module-b

can they both observe the same observable? It doesn't seem like it, since currentObserver would be different in both scenarios. I was forced to pass a singe observer object around. This seems like a big limitation, am I missing something obvious, or is there an elegant way you handle this inside nx-framework?

maximal feature set compatible with proxy-polyfill

i'm exploring the possibility of implementing the maximum feature set of observer-util compatible with https://github.com/GoogleChrome/proxy-polyfill, which only works for get, set, apply, construct traps.

it's severely limited. it would restrict use to observing predefined properties (as the polyfill is unable to intercept new properties), and handling arrays would be slow (it would require replacing the entire array every change), but i think there are use cases where developers can work under reasonable constraints that would allow at least for a working implementation in e.g. IE11, even if slow.

i understand this library has opinion and likely no interest in supporting IE, but if i find myself interested in such an implementation, i wonder what challenges you would anticipate.

limitation on mutating observables in reaction

Is there a reason for why this limitation exists? I can see how this may cause loops, but otherwise there's nothing wrong with having reaction triggered in reaction (not in, after).

How are observables used in observed function detected?

Hi,

this is not an issue, but a question. In the documentation, you are saying

Reactions are functions, which use observables. They can be created with the observe function and they are automatically executed whenever the observables - used by them - change.

I read this post and looked into the code, but was not able to find out how the observables used in observed functions are detected. When I have used a similar approach in C# it involved AOP tool and analyzing the code of the method.

Are all observers executed whenever any observable changes? Or you trigger the function and detect which observables are read?

Thanks for clarification,

Karel

modularity and middleware with proxies

i've been wondering about what decisions can and should be made by proxy-based libraries for playing nice with other proxy-based libraries. proxies are pretty well suited for modularity, as you can extend the proxy you're working with by wrapping it with another proxy (though, i am curious how performant this is for many layers of proxies).

for instance, a logger proxy could easily be implemented separately from observer-util that lives below observables: observable(logger(state)). this is the first problem solved in redux by middleware. with proxies, there may be useful patterns to help with interoperability that don't require commitment to a particular library.

in playing with immutator and morphable, for example, i found that i needed to unwrap the immutator and observable proxy layers (i ended up choosing a different interface for accessing the underlying target than you did in observer-util). line 4 in the example for immutator is residual of this.

do you have thoughts on this broader question of the interfacing between libraries?

Scheduler with Task

I am building native web components with Lit-Html and want to minimize unnecessary re-rendering calls. I am not sure I quite understand how the scheduler feature works. I have created a Scheduler that uses a Set, as per the example to eliminate duplicate reactions, and setTimeout to create a task that defers processing.

class Scheduler {
  constructor() {
    this.timer = null;
    this.reactions = new Set();
  }
  add(value) {
    this.reactions.add(value);
    if (this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(() => {
      this.reactions.forEach(reaction => reaction());
    });
  }
  delete(value) {
    this.reactions.delete(value);
  }
}

This seems to work, and results in batching reactions and generating a single render. Is this a suitable way to accomplish this, or is there a simpler way?

FYI - the delete method never seems to get called.

Make documentation more accessible

It would be nice to make sure the documentation is usable by everyone:

  • Words like "stack" or "call stack" are not known by everyone please include the documentation or include links to explanation about using them.
  • Make sure the reader knows why they need the latest browser.
  • Provide real life examples for documentation (function noop does not describe behavior)

Angular 7 Integration

Hi, I'm just wondering on how one can integrate this into Angular 7. Like a simple component where I just a field in a text field and it updates the value in the other text field.

Thank you in advance!

Integrating NX-observe with React

Hi!

Would anyone be interested in doing this. I would put it into a separate repo (like mobx and react-mobx) to keep nx-observe framework independent. If nobody is interested I will try to do it, but I am pretty much a React newbie.

Unidirectional Data Flow

I am having a difficult time reasoning how using this library will work when I am passing data down a stack of web components.

Let's say I have an array of items at a top-level component that I have wrapped as observable. I need to pass that array down to child components (through a JavaScript property). The child component also wraps the property as observable.

a) Since I want to keep the web component interface agnostic of using the proxy for reactivity, should I be passing the raw object down to the child components rather than the proxied object?
b) Will duplicate proxies be created for each web component as the array is passed down, or does the library detect its the same object and reuse a single proxy?
c) If I change the array at the high-level component, will it only trigger the observe() within that web component, or will the observe() methods fire at every web component I passed the array down to automatically as well? (I am concerned that it fires at every level, but then also fires again as the stack is re-rendered down from the top-level component).

I am hoping I am explaining this correctly - this is a difficult scenario to describe but unidirectional data flow is a proven architecture and something I really need to work with native web components.

Observe size on Map and Set

Currently, it is not possible to observe the size property on Maps and Sets:

const map = observable(new Map())

observe(() => console.log(map.size)) //0

setTimeout(() => map.set('key', 'value'), 200)

// crickets...

How can I plug into the observation process?

I want to take a bunch of observable/streams (like RxJS, I don't know the exact name of those) and pass them to a function that will return an observable that will have all their last emitted values stored, and every time one of those observable/streams emit something that will update the observable and trigger updates accordingly to observers.

This seemed like a simple thing to do, but I'm somehow seeing a lot of unexpected errors.

import { observable as nxobservable } from '@nx-js/observer-util'

export function observable (def = {}) {
  var values = nxobservable({})

  for (let property in def) {
    let value = def[property]
    setnew(values, property, value)
  }

  return new Proxy(def, {
    set (target, property, value) {
      setnew(values, property, value)
    },

    get (target, property) {
      return values[property]
    }
  })
}

function setnew (target, property, value) {
  var stream = value
  var initialValue
  if (Array.isArray(value) && value.length === 2 && value[0].subscribe) {
    stream = value[0]
    initialValue = value[1]
  }

  if (!initialValue && stream && stream._prod) {
    // hackshly inspect xstream streams to get initialValues
    switch (stream._prod.type) {
      case 'fromArray':
        initialValue = stream._prod.a[0]
        break
      case 'startWith':
        initialValue = stream._prod.val
        break
      case 'mapTo':
        initialValue = stream._prod.f()
        break
      case 'debug':
        setnew(target, property, stream._prod.ins)
    }
  }

  if (!value || typeof value.subscribe !== 'function') {
    initialValue = typeof value === 'object' && !Array.isArray(value)
      ? observable(value)
      : value
    stream = noopStream
  }
  
  target[property] = initialValue
  stream.subscribe({
    next (v) {
      target[property] = v && typeof v === 'object' ? observable(v) : v
    },
    error (e) {
      console.error(`error on stream ${property}:`, e)
      target[property] = e
    } 
  })
} 

const noopStream = {subscribe: () => {}}

There's a lot of complication here because I had to introduce initialValue, which wasn't needed when I was trying to build this from scratch without using Proxy or @nx-js/observer-util (in my previous implementation the observers were only triggered when the streams emitted their first values, but now they're triggered before that.)

I'm just looking for some help, but if there was a way to "intercept" values before get I think it would solve it to me.

Not accessing the changed property doesn't call observe second time

.observe() callback is not called if the changed property getter is not executed in the .observe() callback. Take a look at the example below. Execute it then try uncommenting the line and commenting the one above.

const observer = require('@risingstack/nx-observe');

const observable = observer.observable();

// This way doesn't call the callback twice
observer.observe(() => console.log(observable));

// This way works
// observer.observe(() => console.log(observable.value));

observable.value = 3;

setTimeout(() => observable.value = 4);

Trap "toJSON" to allow serialization of proxies

Hi,
I just found an issue while trying to store objects on IDB. It seems that IDB can't serialize proxied objects, and throws an error (Serialization error: Can't serialize [object object] on xxx...).

So, in order for me to be able to store these objects, I have to serialize/deserialize the object manually (so effectively removing the proxy).

I was thinking that maybe you can trap the checks for "has toJSON" to return true and then when that method is invoked, return the raw object. That would solve the serialization issue.

If the object already has a toJSON method, you should not have to do anything.

What do you think?

Redundant reactions?

I'm confused about the behavior of the following code:

const list = observable(['Hello'])
observe(() => console.log(list))
list.push('World!')

This logs ['Hello', 'World!'] twice. I expect it to log once. I get expected behavior from:

const list = observable(['Hello'])
observe(() => console.log(list.concat()))
list.push('World!')

Is this the correct behavior?

Pass observables across components?

I am trying to pass an observable between two web components (e.g. a parent and a child). I was hoping to put a reaction in the render method of both components based on the same observable (I am passing the observable from the parent to the child on the HTMLElement.data property). Then if the observable was modified by either component, it would re-render either/both as appropriate.

What I am seeing however is that the reactions only fire in the element where the observable is created. Is this the intended behavior?

Should Symbols be observable?

Should properties with Symbol keys trigger observed functions, when they change value?

Currently Symbols are observable with the exception of well-known Symbols. The reason is that well- known symbols are frequently used internally by JS, which would lead to unexpected observed function triggers and performance issues.

The more I use the observer-util the more I feel like this should be extended to all Symbols. Symbols are generally used to store internal low-level data, which should not be observed.

What's your opinion? Is there anyone who relies on observing Symbol keyed properties?

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.