Observing immutable object via #observable - aurelia

Let's assume I have a global state class that contains a TimeFrame class which has some useful properties
export class StateContainer {
public timeframe: TimeFrame;
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
}
}
export class TimeFrame {
public readonly startTime: Date;
public readonly endTime: Date;
}
Then, I need to consume this state elsewhere, so I do so via DI and then use the bindingEngine.propertyObserver to get changes on the timeframe object as one would do.
However, I would like be able to do something similar to the following, if it's possible:
#autoinject
export class Display {
#observable
timeFrame: TimeFrame = this.state.timeframe;
constructor(private state: StateContainer) {
}
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) {
...
// everytime this.state.timeFrame is changed via setTimeFrame()
// this change handler should fire, and so should any
// bindings from this component for this.timeFrame
}
}
However, when I do the previous, I only get timeFrameChanged(...) notifications on the inital creation, not whenever I call setTimeFrame(). Am I doing something wrong or is this not possible?

Yes, you are doing something wrong, I think you are misunderstanding how the observable decorator and the corresponding timeFrameChanged method are meant to be used.
Let's take it bit by bit. Take a look at these lines:
#observable
timeFrame: TimeFrame = this.state.timeframe;
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) { ... }
The observable decorator tells Aurelia that whenever the property to which it is applied changes, execute the corresponding method. Unless otherwise configured, the corresponding method is nameOfInvolvedProperty + "Changed" (in this case, as you are correctly doing, timeFrameChanged).
However, you are never actually changing the value of that property! If you actually changed that property, it would work:
<button click.delegate="changeProp()">
Change prop
</button>
and the VM:
changeProp() {
this.timeFrame = null;
this.timeFrame = this.state.timeframe;
}
Now you'd see that the handler correctly fires.
But in your code, this property is only ever assigned once, here:
timeFrame: TimeFrame = this.state.timeframe;
Remember, this is just dereferencing. In other words, this code tells the app to take the value of this.state.timeframe, store the momentary value of it in that variable and forget about this.state.timeframe. So it does not get updated whenever this.state.timeframe is updated.
As described in the documentation, you can configure the observable decorator to some extent, however, to my knowledge, there is no way to configure it in such a way that set it up to observe nested properties (or maybe it's just me not knowing how to do that).
Even if it was possible, I think a cleaner way to deal with such situations would be to use an event aggregator. This enables you to employ a subscription mechanism - whenever the value you are interested in changes, you publish an event and whichever component is interested in that change can subscribe to it and update itself accordingly.
A simple implementation:
import { Router, RouterConfiguration } from 'aurelia-router';
import { autoinject } from 'aurelia-framework';
import { observable } from 'aurelia-binding';
import { StateContainer } from './state-container';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
#autoinject
export class App {
subscription: Subscription = null;
constructor(private state: StateContainer, private eventAggregator: EventAggregator) {
}
activate() {
this.subscription = this.eventAggregator.subscribe('state:timeframe', (e) => {
console.log('Timeframe changed!', e);
});
}
deactivate() {
// Make sure to dispose of the subscription to avoid memory leaks.
this.subscription.dispose();
}
change() {
this.state.setTimeframe(new Date(), new Date());
}
}
And StateContainer:
import { TimeFrame } from "./time-frame";
import { autoinject } from "aurelia-framework";
import { EventAggregator } from "aurelia-event-aggregator";
#autoinject
export class StateContainer {
public timeframe: TimeFrame = null;
constructor(private eventAggregator: EventAggregator) {
}
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
this.eventAggregator.publish('state:timeframe', this.timeframe);
}
}
Hope that helps.

Related

Mobx computed gets warning about changing the observable while it is not

I have the following observable class sample:
class AppState implements IAppState {
isSignedIn: boolean;
private constructor(
isSignedIn: boolean,
) {
this.isSignedIn = isSignedIn;
makeAutoObservable(this);
}
get itIsSafeToFetch(): boolean {
return this.isSignedIn === true;
}
}
The thing is that I have getter itIsSafeToFetch(), which does not change any observable values. But when I run the code I get warning:
[MobX] Since strict-mode is enabled, changing (observed) observable values
without using an action is not allowed.
Tried to modify: AppState#45.isSignedIn
If I comment out that getter - warning disappear. Why could this happen?

TornadoFX: How to preserve `ItemViewModel`'s property?

Good day.
I am trying to preserve a property of ItemViewModel via config helper. I am able to successfully save the property (conf directory with appropriate .properties file is generated), however upon next start, the property does not restore its value, just remains null. Here's a sample code to demonstrate my issue:
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
data class Foo(val doNotPreserveMe: String, val preserveMe: String)
class FooModel : ItemViewModel<Foo>() {
val doNotPreserveMe = bind { item?.doNotPreserveMe?.toProperty() }
val preserveMe = bind { SimpleStringProperty(item?.preserveMe, "pm", config.string("pm")) }
}
class FooApp : App(FooView::class)
class FooView : View() {
private val model = FooModel()
override val root = form {
fieldset {
field("Do not preserve me") { textfield(model.doNotPreserveMe).required() }
field("Preserve me") { textfield(model.preserveMe).required() }
button("Do something") {
enableWhen(model.valid)
action {
model.commit {
// ...
with(config) {
set("pm" to model.preserveMe.value)
save()
}
}
}
}
}
}
}
Any ideas on why the model is not restoring the value?
Each Component has it's own config store, which is backed by a separate file. Either make sure to use the same config file, or the app global config file.
You can refer to other component's config store, so one solution would be to let the View access the ViewModel's config store like this:
button("Do something") {
enableWhen(model.valid)
action {
model.commit {
// ...
with(model.config) {
set("pm" to model.preserveMe.value)
save()
}
}
}
}
However, there is a much simpler and more contained solution, which is simply to handle save in the FooModel's onCommit callback
override fun onCommit() {
with(config) {
set("pm" to preserveMe.value)
save()
}
}
In this case you'd simply call model.commit() in the button callback.
You can also use a common, or even global config object. Either use a Controller's config store, or the global store. To use the global config object, just refer to app.config in both the model and the view.

Is it possible to create inheritance between two mobx stores?

I'm building two widgets with mobx/react, where all the logic sits inside the stores. Both share most of the design rules, so their stores are 95% identical.
Is there a smart way to handle this situation?
For example, is it possible to create inheritance such as this?
class Animal {
#observable name = "";
constructor(name) {
this.name = name;
}
#computed get sentence() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
#observable isBarking = false;
#computed get bark() {
if (this.isBarking){
console.log('The dog is barking');
}
}
#action
setIsBarking(isBarking) {
this.isBarking = isBarking;
}
}
Yes you can, but you have to structure it like this, using the new Mobx pattern which does not use decorators:
(Using Typescript)
import {observable, action, computed, makeObservable} from "mobx";
const animalProps = {
name: observable,
sentence: computed
};
class abstract Animal {
name = "";
constructor(name) {
this.name = name;
}
get sentence() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
isBarking = false;
constructor(){
makeObservable(this, {
...animalProps,
isBarking: observable,
bark: computed,
setIsBarking: action
});
}
get bark() {
if (this.isBarking){
console.log('The dog is barking');
}
}
setIsBarking(isBarking) {
this.isBarking = isBarking;
}
}
If you need an instance of Animal in your app, then Mobx-State-Tree is a better option. Because making a prop observable/actionable/computable twice (the parent class and the subclass) will throw an error.
I know this was asked a long time ago at this point, but per the docs here you can override as you wrote. There are limitations though:
Only action, computed, flow, action.bound defined on prototype can be overriden by subclass.
Field can't be re-annotated in subclass, except with override.
makeAutoObservable does not support subclassing.
Extending builtins (ObservableMap, ObservableArray, etc) is not supported.
You can't provide different options to makeObservable in subclass.
You can't mix annotations/decorators in single inheritance chain.
All their standard limitations apply as well which I won't list here.
This works with the non-annotation syntax as well (e.g., makeObservable).
Have you consider MobX State Tree (https://github.com/mobxjs/mobx-state-tree) for managing your two classes Animal and Dog ?
This will give you the powerfull compose functionality, that could be used instead of inheritance.
Here's the probably most useful part for you: "Simulate inheritance by using type composition" https://github.com/mobxjs/mobx-state-tree#simulate-inheritance-by-using-type-composition

Specialized Singleton implementation

I am looking for specialized singleton implementation, probably I might be using wrong terminology and hence looking for expert suggestion. Here is my scenario:
There is common code which can be called by ComponentA or ComponentB. I need to push telemetry data from the common code. Telemetry needs to have information that whether this common code get called by ComponentA or ComponentB.
So common code will have just this line of code:
telemetry.pushData(this._area, data);
where this._area tells the telemetry data is getting pushed for which component
I need to push telemetry data from multiple places so it would be good if object got created once and used through out the code lifetime
One option I can think of passing component context to the common code which in mind doesn't look right, hence looking for suggestion what kind of pattern one should use in this case?
This is what I am thinking
// Telemetry.ts file present in shared code
export class Telemetry extends Singleton {
public constructor() {
super();
}
public static instance(): Telemetry {
return super.instance<Telemetry>(Telemetry);
}
public publishEvent(data): void {
if (!this.area) {
throw new Error("Error: Initialize telemetry class with right area");
}
pushtelemetryData(this.area, data);
}
public area: string;
}
// Create Telemetry object from component A
Telemetry.instance().area = "ComponentA";
// Shared code will call telemetry publishEvent
Telemetry.instance().publishEvent(data);
Thanks
It's not a good pattern to use in TypeScript where you would generally inject dependencies.
If you must absolutely do it then you can do it by faking it somewhat:
namespace Telemetry {
var instance : SingletonSomething;
export function push(data: Any) : void {
if (instance == null) {
instance = new SingletonSomething();
}
instance.push(data);
}
class SingletonSomething() { ... }
}
and then you could call
Telemetry.push(data);
You can imitate the singleton pattern in typescript easily:
class Telemetry {
private static instance: Telemetry;
public static getInstance(): Telemetry {
if (Telemetry.instance == null) {
Telemetry.instance = new Telemetry();
}
return Telemetry.instance;
}
...
}
If you have your code in some sort of closure (module, namespace, etc) then you can replace the static member with:
let telemetryInstance: Telemetry;
export class Telemetry {
public static getInstance(): Telemetry {
if (telemetryInstance == null) {
telemetryInstance = new Telemetry();
}
return telemetryInstance;
}
...
}
But then you can also replace the static method with:
let telemetryInstance: Telemetry;
export function getTelemetryInstance(): Telemetry {
if (telemetryInstance == null) {
telemetryInstance = new Telemetry();
}
return telemetryInstance;
}
export class Telemetry {
...
}
At this point, in case you are using some sort of closure, you might ask yourself if you really need the class at all?
If you use this as a module:
// telemetry.ts
export interface TelemetryData {
...
}
export function pushData(data: TelemetryData): void {
...
}
Then you get exactly what you're looking for, and this is more of the "javascript way" of doing it.
Edit
In the telemetry module there's no need to know the users of it.
If the Telemetry.pushData function needs to have information about the object that called it then define an interface for it:
// telemetry.ts
export interface TelemetryData {
...
}
export interface TelemetryComponent {
name: string;
...
}
export function pushData(data: TelemetryData, component: TelemetryComponent): void {
...
}
Then in the other modules, where you use it:
// someModule.ts
import * as Telemetry from "./telemetry";
class MyComponent implement Telemetry.TelemetryComponent {
// can also be a simple string property
public get name() {
return "MyComponent";
}
fn() {
...
Telemetry.pushData({ ... }, this);
}
}
2nd Edit
Because you are using a module system, your module files are enough to make singletons, there's no need for a class to achieve that.
You can do this:
// telemetry.ts
let area: string;
export interface TelemetryData {
...
}
export function setArea(usedArea: string) {
area = usedArea;
}
export function pushData(data: TelemetryData): void {
...
}
Then:
Telemetry.setArea("ComponentA");
...
Telemetry.publishEvent(data);
The telemetry module will be created only once per page, so you can treat the entire module as a singleton.
Export only the functions that are needed.

Why does Luxe/Flow quit unexpectedly after building with my PhysicsHandler class?

My PhysicsHandler class seems to be causing Luxe to quit unexpectedly, and I have no idea why.
Everything runs fine until I declare a class-variable, at which point it crashes a couple of seconds after loading. What's weird is that I have another class (InputHandler) that declares class-variables and runs fine. Not sure whether this is a problem with my code (somehow... ), Luxe, or Flow.
Main class:
import luxe.Input;
import luxe.Parcel;
import luxe.ParcelProgress;
import InputHandler;
import PhysicsHandler;
import Player;
enum GAME_STATE
{
play;
pause;
}
class Main extends luxe.Game {
var INPUT_HANDLER: InputHandler;
override function ready() {
var assetsParcel = new Parcel
({
textures:
[
{ id:"assets/block.png" },
{ id:"assets/background.png" }
]
});
new ParcelProgress
({
parcel : assetsParcel,
oncomplete : onAssetsLoaded
});
assetsParcel.load();
INPUT_HANDLER = new InputHandler();
INPUT_HANDLER.GameState = GAME_STATE.play;
}
private function onAssetsLoaded(_)
{
var player = new Player();
INPUT_HANDLER.setPlayerEntity(player);
}
override function update(dt:Float) {
INPUT_HANDLER.update();
}
}
InputHandler class:
import luxe.Input;
import luxe.Entity;
import Main;
class InputHandler
{
public var GameState: EnumValue;
private var player: Entity;
// functions, etc. below here...
}
PhysicsHandler class (the troublemaker... ):
import Main;
class PhysicsHandler
{
public var GameState: EnumValue;
}
This is all it takes to crash the game somehow. As you can see, I'm not even instantiating the PhysicsHandler class yet, just importing it.
Okay, so I was able to sort this with some help on the Snowkit forums. Apparently, Luxe doesn't play well with the latest version of hxcpp, so downgrading to 3.2.102 worked. Result.