Nativescript: Nested Observable issue

Created on 5 Apr 2016  路  6Comments  路  Source: NativeScript/NativeScript

Please, provide the details below:

A nested observer fires the change event apparently at the wrong time..

Did you verify this is a real problem by searching

yes

Which platform(s) does your issue occur on?

Android (probably both)

Please provide the following version numbers that your issue occurs with:

1.7x

Please tell us how to recreate the issue in as much detail as possible.

See code below, basically clicking the button I would expect both lines to disappear in the UI; however, the second one somehow the event is triggered improperly; so the line disappears when it is true and appears when it is false. But the label below actually shows the proper value.

Is there code involved?

<Page id="Page" onloaded="pageLoaded">
  <StackLayout>
      <Label visibility="{{ pageData.visible ? 'visible' : 'collapsed' }}" text="Hi1 this should appear/disappear" class ="lab1"/>
      <Label visibility="{{ pageObs.visible ? 'visible' : 'collapsed' }}"  text="Hi2 this should appear/disappear" class ="lab1"/>
<Label text="{{pageObs.visible}}"/>
   <Button text="tap"  tap="onTap"/>
  </StackLayout>
</Page>

var Observable = require('data/observable').Observable;
var myData = new Observable({
        pageData: {visible: true},
        pageObs: new Observable({visible: true})
    });

exports.pageLoaded = function(args) {
    console.log("Loaded");
    var page = args.object;
    page.bindingContext = myData;
};
exports.onTap  = function(args) {
    myData.pageData.visible = !myData.pageData.visible;
    myData.pageObs.visible = !myData.pageObs.visible;
};

Most helpful comment

@xuezier actually a little known fact -- if you do new Observable({dataName: value}); the Observable has "magic" ;-) that will automatically create setters and getters for each of the passed in as data key objects during construction. So, the code I wrote is perfectly fine. :grinning:

If I just did new Observable(); then you would be correct as any keys added to the Observable AFTER creation you would have to use set...

All 6 comments

the last code should be writen like this:

exports.onTap  = function(args) {
    myData.set("pageData",{visible:!myData.pageData.visible})
    myData.set("pageObs",{visible:!myData.pageObs.visible});
};

@xuezier actually a little known fact -- if you do new Observable({dataName: value}); the Observable has "magic" ;-) that will automatically create setters and getters for each of the passed in as data key objects during construction. So, the code I wrote is perfectly fine. :grinning:

If I just did new Observable(); then you would be correct as any keys added to the Observable AFTER creation you would have to use set...

So basically both labels are set as Observers to the main Observable object, myData. When the first call changes the visibility property, an event is called on both of them, causing the visibility property to evaluate the current state. Because the first label evaluates the value after the main Observable event is called, the value is fine. But when the second label evaluates the current value, it sees the old value from the nested Observable, as it's setter wasn't called yet.
This might be tricky to solve.

As expected, if you change the order:

myData.pageObs.visible = !myData.pageObs.visible;
myData.visible = !myData.visible;

They show / hide just fine.

Sorry for the late reply. I'll try to bring some light into the problem. The problem comes from the fact that when expression is used for binding (without sourceProperty):

<Label visibility="{{ pageObs.visible ? 'visible' : 'collapsed' }}" text="Hi1 this should appear/disappear"/>

Internal binding infrastructure sets special $value for a sourceProperty. Unfortunately $value is bindingContext of the Label (in that example myData object). So binding system attaches a property change listener to myData object, but not for myData.pageObs which will be the case if we have a correct sourceProperty in xml like:

<Label visibility="{{ pageObs.visible, pageObs.visible ? 'visible' : 'collapsed' }}" text="Hi1 this should appear/disappear"/>

And since that no direct property of myData is changed Label will not be hidden. Placing a correct sourceProperty (with full XML binding syntax) will solve the problem.

However this will not solve the problem with pageData label, since pageData is not an Observable and does not emit propertyChange event. In order to use this you could change entire pageData object:

myData.set("pageData", {{visible:!myData.pageData.visible}});

This will change pageData property of myData and attached propertyChange listener (for $value) will be triggered.

Previous is also available within NativeScript documentation.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings