Forum Stats

  • 3,780,909 Users
  • 2,254,454 Discussions
  • 7,879,490 Comments

Discussions

Film strip is not working in Promise API calls

2769831
2769831 Member Posts: 55
edited Apr 12, 2016 3:06PM in Oracle JET

Hi all,

I need to have a film strip whose data is coming back from an API call. I put the observable array in a  Promise-based API call and the film strip is broken and it becomes a common div without any response.

For example:

    var tmp= [

            { name: 'Hydrogen' },

            { name: 'Helium' },

            { name: 'Lithium' },

            { name: 'Beryllium' },

            { name: 'Boron' },

            { name: 'Carbon' },

            { name: 'Nitrogen' },

            { name: 'Oxygen' },

            { name: 'Fluorine' },

            { name: 'Neon' },

            { name: 'Sodium' },

            { name: 'Magnesium' }

        ];

        self.chemicals(tmp);

If I put the above in the main block, it's fine but if I put it in an async Promise-based API call like below, it's broken:

someAPI.getChemical (queryParams)

            .then(function (c) {

           var tmp= [

            { name: 'Hydrogen' },

            { name: 'Helium' },

            { name: 'Lithium' },

            { name: 'Beryllium' },

            { name: 'Boron' },

            { name: 'Carbon' },

            { name: 'Nitrogen' },

            { name: 'Oxygen' },

            { name: 'Fluorine' },

            { name: 'Neon' },

            { name: 'Sodium' },

            { name: 'Magnesium' }

        ];

        self.chemicals(tmp);

            })

Can anyone please help? Any comments and opinions are much appreciated!

Regards,

Quincy

2769831

Best Answer

  • Jim.Marion-Oracle
    Jim.Marion-Oracle Member Posts: 929
    edited Apr 12, 2016 2:15AM Accepted Answer

    I have two suggestions:

    #1: Don't replace the array within the observableArray. Yes, sometimes that works. Sometimes it doesn't. It seems to depend on the implementation of the underlying component and how it binds to the observable and the observable's data. Instead, populate the observableArray itself when the promise is fulfilled. My favorite way to do this looks something like this:

    ko.utils.arrayPushAll(self.chemicals(), c);
    self.chemicals.valueHasMutated();
    

    If you need to manipulate the data returned by the promise, then assign the observable array to a temp variable and then push into the array OUTSIDE of the observable construct. Then invoke valueHasMutated. This will keep ko from attempting to update the DOM on every push.

    You can see an example of something like this here: Multi select does not work when binding options from REST call. This example uses window.setTimeout to simulate the asynchronous nature of Ajax, causing the UI to render before the data is ready. jsFiddle here: https://jsfiddle.net/jmarion/8079eeoc/.

    #2: Use the if binding to keep the chemicals array out of the DOM until the promise resolves and you have real data. Basically, you wrap the DOM that uses the chemicals binding in an if binding and then ko won't know about/process the binding until AFTER the data is ready.

    I like option #1 because I like to make as much of the UI visible/available as soon as possible. But if that doesn't work, then I fall back on option 2.

    27698312769831

Answers

  • Jim.Marion-Oracle
    Jim.Marion-Oracle Member Posts: 929
    edited Apr 12, 2016 2:15AM Accepted Answer

    I have two suggestions:

    #1: Don't replace the array within the observableArray. Yes, sometimes that works. Sometimes it doesn't. It seems to depend on the implementation of the underlying component and how it binds to the observable and the observable's data. Instead, populate the observableArray itself when the promise is fulfilled. My favorite way to do this looks something like this:

    ko.utils.arrayPushAll(self.chemicals(), c);
    self.chemicals.valueHasMutated();
    

    If you need to manipulate the data returned by the promise, then assign the observable array to a temp variable and then push into the array OUTSIDE of the observable construct. Then invoke valueHasMutated. This will keep ko from attempting to update the DOM on every push.

    You can see an example of something like this here: Multi select does not work when binding options from REST call. This example uses window.setTimeout to simulate the asynchronous nature of Ajax, causing the UI to render before the data is ready. jsFiddle here: https://jsfiddle.net/jmarion/8079eeoc/.

    #2: Use the if binding to keep the chemicals array out of the DOM until the promise resolves and you have real data. Basically, you wrap the DOM that uses the chemicals binding in an if binding and then ko won't know about/process the binding until AFTER the data is ready.

    I like option #1 because I like to make as much of the UI visible/available as soon as possible. But if that doesn't work, then I fall back on option 2.

    27698312769831
  • 2769831
    2769831 Member Posts: 55
    edited Apr 12, 2016 1:46PM

    The solution 2 is working! Let me try solution 1! Thank you very much Jim!

    Regards,

    Quincy

  • 2769831
    2769831 Member Posts: 55
    edited Apr 12, 2016 2:22PM

    Hi Jim,

    Thank you for your help!

    I'm trying the first solution and I have some questions:

    When you say "push into the array OUTSIDE of the observable construct", what do you mean by that? Do you mean I do push outside Promise? Or I do ko.utils.arrayPushAll(self.chemicals(), c) in the Promise?

    Thank you again for your kind help! I really appreciate it!

    Regards,

    Quincy

    2769831
  • Jim.Marion-Oracle
    Jim.Marion-Oracle Member Posts: 929
    edited Apr 12, 2016 3:06PM

    Great question. Do as you said:

    do ko.utils.arrayPushAll(self.chemicals(), c) in the Promise
    

    If your Ajax response data matches exactly what your view expects, then you are all set. I didn't want to assume that was the case. If you have to manipulate the data first (using forEach or map or something), that is when my extra piece of advice matters. When pushing data into an observableArray item-by-item, you have two options: use the observableArray methods or work with the raw array directly. If you are only updating one row, then using the observableArray array methods is best because that keeps the view and model in sync. In this case, however, you are pushing all of the data into the array at once. In this case, for performance reasons, "the experts" recommend that you work with the raw array directly. When done, you can trigger the view by invoking valueHasMutated.