I still try to use MobX in an AngularJS 1 application, and I have hit some issues and limitations.
I open this issue to record them: perhaps you can do something for some of them, and at least, users might find them when searching...
First, what I call "observable arrays". That's arrays in observable objects, like:
ctrl.view = mobx.observable({ a: [ someData ] });
To reproduce my issues and experiments / explore more the topic, I made a jsFiddle:
https://jsfiddle.net/nv0525rp/9/
A bit too complex, but that's a lot of copy & paste to explore variations...
First issue, for awareness (I don't think you can / should do anything for that): 3rd party libraries can't clone / copy these structures correctly.
Lodash's _.clone and _.cloneDeep fail and return an empty object.
Angular's angular.copy crashes...
Of course, Array.prototype.slice works, that's just that I like to use _.clone for all my cloning needs, for consistency. Note also that Lodash's _.isArray fails too.
Good point for MobX: if I assign a new array value to such array, MobX does the right thing and the new value is still observable.
Problem, and main issue (main reason for opening this issue): if I have a mobx.observe on such array, we will loose the observe on assignment.
The workaround I used: I splice the array, instead. But the method I used, Array.prototype.splice.apply, adds the elements in the array one by one, so the observe is triggered each time.
...
Aah, while typing this, I realize I can just use obsArr.slice.apply, thus using your implementation, and avoiding the issue above! I did that for the sixth array.
But, well, it would be more "natural" to accept a simple assignment and keep the observe, if possible at all.
I also found out I can an autorun just for this array (wasn't use I could have several autoruns in the same code), in place of mobx.observe, but this has problems of its own (infinite loop, see around line 91) which I will report in a different issue.
First, what I call "observable arrays". That's arrays in observable objects
You don't have to put array in object, there is is ObservableArray class by design:
var o = mobx.observable([1,2,3]);
console.log(o.constructor.name); // ObservableArray
What mobx does is just recursively replacing every object's value with observable(value)
Lodash's _.clone and _.cloneDeep fail and return an empty object
You don’t want to use _.clone / angular.copy / Array.prototype.slice with anything except plain object or plain array, unless you know what you are doing.
Even if it works now, it is bad practice, because you have no guarantee, that it will work after some inner stuff changed. There are plenty of reasons why that can fail. Just don't do it (it relates not only to MobX).
Note also that Lodash's _.isArray fails too
It’s quite logical, what _.isArray is doing is “Checks if value is classified as an Array object”, whereas ObservableArray is not Array. See article Determining with absolute accuracy whether or not a JavaScript object is an array
Problem, and main issue (main reason for opening this issue): if I have a mobx.observe on such array, we will loose the observe on assignment.
Yes, because mobx creates another ObservableArray on which you are not subscribed.
The workaround I used: I splice the array, instead. But the method I used Array.prototype.splice.apply, adds the elements in the array one by one, so the observe is triggered each time
Just use .replace(newValue)
var o = mobx.observable([1,2,3]);
o.observe(() => console.log('~>', o.toJSON()));
o.replace([7, 8, 9]); // [7, 8, 9]
But, well, it would be more "natural" to accept a simple assignment and keep the observe, if possible at all.
@mweststrate, what do you think about this?
Thanks for the detailed answer.
You don't have to put array in object, there is is ObservableArray class by design: [...]
What mobx does is just recursively replacing every object's value withobservable(value)
Well, yes, that's what I expected, although maybe I abuse too much of this shortcut.
Note: I used _.clone not to get a new ObservableArray, but to get a copy of the array (content), precisely to avoid changes to the original one. Mmm, I shouldn't have put the results in ctrl.view, though.
My remark about _.isArray is more for reference (I didn't need to use it, actually), as I don't expect either Lodash or MobX to do anything to "fix" that...
Yes, because mobx creates another ObservableArray on which you are not subscribed.
In my second experiment, I found out I can do an autorun to take care of changes. I didn't know I could have several independent autoruns in the same class.
I didn't know about replace() because i have found no doc about ObservableArray. Instead, in two places, there is a reference to a fastArray, including a dead link... I can fix that as I am revising the doc, but I am not sure how to really fix... :stuck_out_tongue:
Anyway, that's a good answer to my problem, simpler than splice. Thanks.
Thanks again for your answers.
@andykog yup you are right, slices are usually the way to go before passing observable arrays to other libraries. I usually like to even use slice to pass the array to any place where it shouldn't be mutated anymore, since slice is really cheap anyway.
The non-standard support array functions are listed here: http://mobxjs.github.io/mobx/refguide/observable.html under the array section. (ok, I'm gonna drop gitbook at some point, really annoying that individual sections are not linkable).
In general I recommend against using observe, use autorun instead. observe is only interesting if you need specific splices or something. But in your case, when you are mainly computed values, use computed. autorun is really there for side effects. (Somehow I need to stress that more in the docs, suggestions are welcome).
The cool thing about computations is that they 1) memoize, 2) are disposed automatically if you do not use them (indirectly) in some autorun. So if you have two different computations and an autorun that logs only one of them, the other one would never be evaluated as long as you don't inspect it. Lazyness to the max. However they expect pure functions, so if you depend on some side effect in the computation to happen, like logging, you should use autorun.
N.b, if you are using ES6, note that you could have the spread operator to make the splice work for the complete array in one go.
Closing this issue for now, feel free to re-open it on further questions.
The non-standard support array functions are listed here
Ah, I knew I have read it somewhere! Maybe it is worth to have a section of its own, like map, to find it back easily... Note: the link to fastArray here is 404. Is fastArray still a thing? There is also an example using it in another page.
autorunis really there for side effects.
Yes, the doc is pretty clear about this... :smile: That's why I made side effects: assigning values to properties of a non-observable object (the view) and modifying sub-properties (disabled) of an object depending on changes on an observable array. Doing the latter in an autorun allows me to use simple assignments to this array, as I did before, solving the problem above.
I just was carried over and did that for observable objects as well, and that's what tripped me (in my other issue, #179). Removing these, using createTransformer directly made a cleaner code for me.
I know you mean mostly I/O for "side-effects", but you also mention changing the UI (checking a check box, etc.), and that's what I really do with my assignments: I set a value to a bound variable, and Angular updates the UI accordingly. Instead of someBox.setSelected(true), I do someBoxSelected = true;.
Thank you for the advices.
As a heads up: I had an issue because I made an array of objects observable.
I made an array of a subset of these objects, these where still observables (references on the original objects, actually). I gave the subset elsewhere, and a _.cloneDeep was done to avoid changing the original stuff. But this resulted in an empty array!
So, beware of interactions with other parts of code.
In my case, I observed the array only to see if it is not empty, which is a bit overkill...
ctrl._model = mobx.observable(
{
stuffList: [],
});
Easy way out: I tell MobX not to observe the objects themselves.
ctrl._model = mobx.observable(
{
stuffList: mobx.asFlat([]), // Observe array, not objects inside it
});
Assigning later the real array to stuffList preserved the asFlat modifier.
Basically, if you are interested in changes in the array (add / remove items), but not the objects inside them (which might not change at all!), asFlat is good to use (can even improve performance, which doesn't hurt...).
Following up on this issue :
I have a quite massive array with sub object in my application (a list of account with sub resources). I get them all with a one API call and I basically do :
api call -> array.replace(e.response).
Semantically it looks like I'm replacing the whole array at once, but Mobx is effectively going down the whole array to make each property observable (I guess?).
I'd like to know if there was a more efficient way to do this (That doesn't require compromising completely with asFlat() ) because when that happen, I basically see in the Redux remotedev tool a tons of calls to add() with non statuses change, and only one last call to splice() actually mutate my state :

Basically, the question is, what are all those add calls ? I s it simply turning sub values to observable ?
Just to make sure, are you running all the creation logic inside an action?
On Tue, Mar 7, 2017 at 8:36 AM, yann-stepienik-cko <[email protected]
wrote:
Following up on this issue :
I have a quite massive array with sub object in my application (a list of
account with sub resources). I get them all with a one API call and I
basically do :
api call -> array.replace(e.response).Semantically it looks like I'm replacing the whole array at once, but Mobx
is effectively going down the whole array to make each property observable
(I guess?).
I'd like to know if there was a more efficient way to do this (That
doesn't require compromising completely with asFlat() ) because when that
happen, I basically see in the Redux remotedev tool a tons of calls to
add() with non statuses change, and only one last call to splice() actually
mutate my state :[image: screen shot 2017-03-07 at 14 35 53]
https://cloud.githubusercontent.com/assets/17568523/23661078/71d8f450-0343-11e7-97f4-c90b0913e39f.pngBasically, the question is, what are all those add calls ? I s it simply
turning sub values to observable ?—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/mobxjs/mobx/issues/177#issuecomment-284738645, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAIrcjGob1vgn25xjwrcPqpbanadGR-vks5rjWuHgaJpZM4ICOF_
.
--
-Matt Ruby-
[email protected]
This is my current code @mattruby
@remotedev({ global: true })
export default class AppState {
@observable accountList = asFlat([]);
@action updateAccountList(values) {
(this.accountList as any).replace(values);
}
}
@PhiLhoSoft The same question. I am rendering a template using the observable array, and the outer array can work normal after .slice(), but the nested list is still a observable array!
For deep conversion, use mobx.toJS
Op di 15 aug. 2017 om 14:30 schreef finalion notifications@github.com:
@PhiLhoSoft https://github.com/philhosoft The same question. I am
rendering a template using the observable array, and the outer array can
work normal after .slice(), but the nested list is still a observable array!—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/mobxjs/mobx/issues/177#issuecomment-322453552, or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABvGhI4wOiriMQL80yBaqTkDnyxy7RoGks5sYY9lgaJpZM4ICOF_
.
@mweststrate I have found the answer in the document. Thanks for your speedy reply!
Most helpful comment
As a heads up: I had an issue because I made an array of objects observable.
I made an array of a subset of these objects, these where still observables (references on the original objects, actually). I gave the subset elsewhere, and a
_.cloneDeepwas done to avoid changing the original stuff. But this resulted in an empty array!So, beware of interactions with other parts of code.
In my case, I observed the array only to see if it is not empty, which is a bit overkill...
Easy way out: I tell MobX not to observe the objects themselves.
Assigning later the real array to
stuffListpreserved theasFlatmodifier.Basically, if you are interested in changes in the array (add / remove items), but not the objects inside them (which might not change at all!),
asFlatis good to use (can even improve performance, which doesn't hurt...).