Mobx observe only specific objects in array - mobx

I have a store with events
export class EventStore {
#observable
events: Event[] = [];
#action
addEvent(event: Event) {
this.events = [...this.events, event]
};
};
My Event look like this :
export class Event{
classType: string;
}
I want to observe change on events properties of store BUT only of a specific classType
For Eg :
I have Event with classType "AddToCart" and "Order", and I want to observe only "Order" added, removed from events
I want to use import { observer } from "mobx-react"
Question :
Is there some magic trick to do in the EventStore or I have to handle it in my component (with some if) ?
My unperfect solution
Meanwhile, I'm doing somehting like this :
reaction(
() => eventStore.lastEvent,
event => {
if (event.classType === "Order")
this.newEvent = { ...event }
}
)

You can add computed property
#computed
get orderEvents() {
// Add whatever filtering you want
return this.events.filter(event => event.classType === 'Order')
};
If you need to pass arguments you could you computedFn from mobx-utils:
filteredEvents = computedFn(function (classType) {
return this.events.filter(event => event.classType === classType)
})
Note: don't use arrow functions as the this would be incorrect.
https://github.com/mobxjs/mobx-utils#computedfn
https://mobx.js.org/refguide/computed-decorator.html#computeds-with-arguments

Related

Aurelia: EventAggregator fires twice

The function called by the subscription function triggers twice.
The publisher is not being used in an activate or attached function, but in an async function of a different class. Both classes recieve the same EventAggregator through binding.
Console.Trace() has the same routes in both cases. The Publish/Subscribe set is unique and not used by any other classes.
async sender(item:any):Promise<void> {
this.dialogService.open({
viewModel: CaModalConfirm,
model: {
color: this.color
}
}).whenClosed(async response => {
if (response.wasCancelled === false) {
this.moduleName = params.params.moduleId;
await this.selectionEventAggregator.publish('requestSelection',{item: item});
this.elementEventAggregator.publish('hideSidebar');
}
});
}
---------------------------------------------
attached() {
this.subscriptions.push(
this.selectionEventAggregator.subscribe(
'requestSelection',
params => this.sendSelection(params)
)
);
}
sendSelection(params):void {
console.trace(params);
this.selectionEventAggregator.publish(
'sendSelected',
{
selection: this.itemSelection,
item: params.item
}
);
}
The Custom Element which contained the custom Element with the Subscription has been used twice, which caused the issue. This was not an EventAggregator issue.

How to make observable from multiple event in Rxjs?

How are you. I am newbie of Rxjs. I am not sure how to merge observable from different event. I integrated Rxjs with Vue.js
export default {
name: 'useraside',
data: function () {
return {
searchKey: '',
isPublic: true
}
},
components: {
User
},
subscriptions () {
return {
// this is the example in RxJS's readme.
raps: this.$watchAsObservable('searchKey')
.pluck('newValue')
// .filter(text => text.length > 1)
.debounceTime(500)
.distinctUntilChanged()
.switchMap(terms => fetchRaps(terms, this.userdata._id, this.isPublic))
.map(formatResult)
}
}
}
Now event comes from searchKey changes, now I would like to subscribe same observable when isPublic value change.
So I would like to get raps whenever searchKey changes or isPublic changes.
Thanks.
You could use the merge operator and keep using the this.isPublic in your switchMap, as Maxime suggested in the comment.
But I'd rather go with a nice a pure dataflow where you listen for the two values and consume them in your handlers. Something like
Rx.Observable.combineLatest(
this.$watchAsObservable('searchKey').pluck('newValue'),
this.$watchAsObservable('isPublic').pluch('newValue'),
([searchKey, isPublic]) => ({ searchKey, isPublic })
)
.dedounceTime(500)
.distinctUntilChanged()
.switchMap(({ searchTerm, isPublic }) => fetchRaps(searchTerm, this.userdata._id, isPublic))
Or event better is you can change the initial data structure to something like :
data: function () {
return {
searchConfig: {
searchKey: '',
isPublic: true
}
}
},
you can then remove the combineLatest and only watch the searchConfig property.
The benefit of this implementation is that your dataflow is pure and doesn't depend on any external context (no need for the this.isPublic). Every dependency is explicitly declared at the beginning of the dataflow.
If you want to go even further, you can also watch the userdata and explicitly pass it down the dataflow :)

Aurelia observer not firing for array

I have a custom data grid element simplified like this:
export class DataGrid {
#bindable data;
dataChanged(newValue, oldValue) {
console.log("Sensing new data...", newValue);
}
}
It's instantiated like this:
<data-grid data.bind="records"></data-grid>
"Sensing new data..." and the array of records is displayed in the console when the data grid appears. However, when I delete a record from the array of objects, the dataChanged() function is not triggered.
let index = this.records.findIndex((r) => { return r.acc_id === this.record.acc_id; });
if (index > -1) {
console.log("Deleting element..." + index, this.records);
this.records.splice(index, 1);
}
I get "Deleting element..." in the console but not "Sensing new data...".
Any ideas why dataChanged() is not firing when I splice out a record?
You can not observe an Array for mutations like that. You have to use a collectionObserver instead. Right now, your dataChanged() would only fire if you overwrite the data value (ie data = [1, 2, 3] which overwrites it with a new array).
Example how to use the collectionObserver from the BindingEngine class, for your usecase:
import { BindingEngine } from 'aurelia-framework';
export class DataGrid {
static inject = [BindingEngine];
#bindable data;
constructor(bindingEngine) {
this._bindingEngine = bindingEngine;
}
attached() {
this._dataObserveSubscription = this._bindingEngine
.collectionObserver(this.data)
.subscribe(splices => this.dataArrayChanged(splices));
}
detached() {
// clean up this observer when the associated view is removed
this._dataObserveSubscription.dispose();
}
dataArrayChanged(splices) {
console.log('Array mutated', splices);
}
}

MobX - Observable value promised in a store constructor using fromPromise stays null when accessed in another store?

So I have 2 stores, an AuthorStore:
class AuthorStore {
constructor() {
// has author.name and is always present in storage
AsyncStorage.getItem('author').then(action((data) => {
this.author = JSON.parse(data);
}));
}
#observable author = null;
}
and a BookStore:
import AuthorStore from 'authorStore';
class BookStore {
#observable book = {
authorName: AuthorStore.author.name,
bookTitle: null
}
}
I keep getting an error in BookStore that it cannot get property of null, as if the AuthorStore.author.name is null. So it's reading the default author value from the AuthorStore without the constructor running first to assign it the value.
I came across the new mobx-utils fromPromise which I think would get the author value if it exists in local storage, and wait for AsyncStorage to assign it to the author observable, so it can be called from another store without being null.
I tried using fromPromise first in the AuthorStore to log the author value, but it shows as Got undefined in console, and the usual null error in the BookStore when it comes to the AuthorStore.author part.
UPDATE:
class AuthorStore {
#observable author = null;
#computed get theAuthor() {
authorPromise = fromPromise(AsyncStorage.getItem('author').then(data => JSON.parse(data)));
// combine with when..
when(
() => authorPromise.state !== "pending",
() => {
console.log("Got Author", authorPromise.reason || authorPromise.value) // This runs, and returns author
console.log("Got Name", authorPromise.reason || authorPromise.value.name) // This runs, and returns name
return authorPromise.value; // This doesn't get returned in BookStore when calling this computed
}
);
}
}
class BookStore {
#observable book = {
authorName: AuthorStore.theAuthor.name, // doesn't get computed returned value from promise
bookTitle: null
}
}
How can I get the fromPromise value assigned by the AuthorStore computed function theAuthor to return the promised authorPromise value into BookStore under authorName?
FromPromise creates a new object wrapping the original promise. So your authorFromStorage is just a normal promise in your example, not having a state property at all. So you should change your code to:
authorPromise = fromPromise(AsyncStorage.getItem('author').then(data => JSON.parse(data)))
And then when(() => authorPromise.state !== "pending") etc..
** UPDATE **
class AuthorStore {
#observable author = null;
constructor() {
AsyncStorage.getItem('author').then(data => { this.author = JSON.parse(data) });
}
}
class BookStore {
#observable book = {
authorName: function() { // a function in an observable creates a computed prop
return AuthorStore.author && AuthorStore.author.name
},
bookTitle: null
}
}

Aurelia `click` attribute that requires event target to be same as element

I'm aware of click.trigger as well as click.delegate which work fine. But what if I want to assign a click event that should only trigger when the exact element that has the attribute gets clicked?
I'd probably do something like this were it "normal" JS:
el.addEventListener('click', function (e) {
if (e.target === el) {
// continue...
}
else {
// el wasn't clicked directly
}
});
Is there already such an attribute, or do I need to create one myself? And if so, I'd like it to be similar to the others, something like click.target="someMethod()". How can I accomplish this?
Edit: I've tried this which doesn't work because the callback function's this points to the custom attribute class - not the element using the attribute's class;
import { inject } from 'aurelia-framework';
#inject(Element)
export class ClickTargetCustomAttribute {
constructor (element) {
this.element = element;
this.handleClick = e => {
console.log('Handling click...');
if (e.target === this.element && typeof this.value === 'function') {
console.log('Target and el are same and value is function :D');
this.value(e);
}
else {
console.log('Target and el are NOT same :/');
}
};
}
attached () {
this.element.addEventListener('click', this.handleClick);
}
detached () {
this.element.removeEventListener('click', this.handleClick);
}
}
And I'm using it like this:
<div click-target.bind="toggleOpen">
....other stuff...
</div>
(Inside this template's viewModel the toggleOpen() method's this is ClickTargetCustomAttribute when invoked from the custom attribute...)
I'd also prefer if click-target.bind="functionName" could instead be click.target="functionName()" just like the native ones are.
Just use smth like click.delegate="toggleOpen($event)".
$event is triggered event, so you can handle it in toggleOpen
toggleOpen(event) {
// check event.target here
}
Also you can pass any other value available in template context to toggleOpen.