What if your bindable data is too large or complex to retrieve at page load? Or what if each SKU has a price that takes a long time to look up? Looking up prices for SKUs for non-viewed items is wasted work.

Fetching available sizes for a shirt

Let's make use of the ability to fetch remote data to look up prices of SKUs in our sample. Our Express.js development server in app.js already has an endpoint /shirts/sizes?shirt=<sku> which, given a shirt SKU, returns the available sizes and price for each size. It sends the response with an artificial delay of one second to simulate network latency.

Request Response
GET /shirts/sizesAndPrices?sku=1001 {"1001: {"sizes": {"XS": 8.99, "S" 9.99}}}

Similar to the JSON data within <amp-state> elements, the remote data returned from these fetches are merged into and available under the element's id attribute. For example, the data returned from the example response above can be accessed in an expression:

Expression Result
shirts['1001'].sizes['XS'] 8.99

Bind the data

Now, let's apply this to our e-commerce example. First let's fetch this shirt data when a new SKU is selected. Add a [src] binding to our amp-state#shirts element:

<!-- When `selected.sku` changes, update the `src` attribute and fetch
     JSON at the new URL. Then, merge that data under `id` ("shirts"). -->
<amp-state id="shirts" [src]="'/shirts/sizesAndPrices?sku=' + selected.sku">

Indicate unavailable sizes

Next, let's clearly mark unavailable sizes as such for a given SKU. The "unavailable" CSS class adds a diagonal line through an element -- we can add it to the elements within amp-selector[name="size"] corresponding to unavailable sizes:

<amp-selector name="size">
  <table>
    <tr>
      <!-- If 'XS' size is available for selected SKU, return empty string.
           Otherwise, return 'unavailable'. -->
      <td [class]="shirts[selected.sku].sizes['XS'] ? '' : 'unavailable'">
        <div option="XS">XS</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['S'] ? '' : 'unavailable'">
        <div option="S">S</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['M'] ? '' : 'unavailable'">
        <div option="M">M</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['L'] ? '' : 'unavailable'">
        <div option="L">L</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['XL'] ? '' : 'unavailable'">
        <div option="XL">XL</div>
      </td>
    </tr>
  </table>
</amp-selector>

Now, reload the page and try it out. Selecting a new SKU (shirt color) will cause unavailable sizes to be crossed-out (after a short delay).

Specify initial states

There's a small problem though -- what about the black shirt, the default selected color? We'll need to add the size and price data of the black shirt to amp-state#shirts because <amp-bind> only runs in response to explicit user action:

<amp-state id="shirts" [src]="'/shirts/sizesAndPrices?sku=' + selected.sku">
  <script type="application/json">
    {
      "1001": {
        "color": "black",
        "image": "./shirts/black.jpg",
        "sizes": {
          "XS": 8.99,
          "S": 9.99
        }
      },
<!-- ... -->

And, we'll need to update the default state of relevant elements:

<amp-selector name="size">
  <table>
    <tr>
      <!-- If 'XS' size is available for selected SKU, return empty string.
           Otherwise, return 'unavailable'. -->
      <td [class]="shirts[selected.sku].sizes['XS'] ? '' : 'unavailable'">
        <div option="XS">XS</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['S'] ? '' : 'unavailable'">
        <div option="S">S</div>
      </td>
      <!-- Add the 'unavailable' class to the next three <td> elements
           to be consistent with the available sizes of the default SKU. -->
      <td class="unavailable"
          [class]="shirts[selected.sku].sizes['M'] ? '' : 'unavailable'">
        <div option="M">M</div>
      </td>
      <td class="unavailable"
          [class]="shirts[selected.sku].sizes['L'] ? '' : 'unavailable'">
        <div option="L">L</div>
      </td>
      <td class="unavailable"
          [class]="shirts[selected.sku].sizes['XL'] ? '' : 'unavailable'">
        <div option="XL">XL</div>
      </td>
    </tr>
  </table>
</amp-selector>

Variable shirt prices

Now that we correctly display the available sizes, let's make sure the correct price also displays.

Our AMPPAREL store is peculiar in that shirt price is specific to both color AND size. That means we need a new variable to track the user-selected size. Add a new action to our size <amp-selector> element:

<!-- When an element is selected, set the `selectedSize` variable to the
     value of the "option" attribute of the selected element.  -->
<amp-selector name="size"
    on="select:AMP.setState({selectedSize: event.targetOption})">

Notice that we're not initializing the value of selectedSize via the amp-state#selected element. That's because we intentionally don't provide a default selected size and instead want to force the user to choose a size.

Add a new <span> element wrapping the price label and change the default text to "---" since there's no default size selection.

<h6>PRICE :
  <!-- Display the price of the selected shirt in the selected size if available.
       Otherwise, display the placeholder text '---'. -->
  <span [text]="shirts[selected.sku].sizes[selectedSize] || '---'">---</span>
</h6>

And we have correct prices! Try it out.

Conditionally-enabled button

We're almost done! Let's disable the "Add to cart" button when the selected size is unavailable:

<!-- Disable the "ADD TO CART" button when:
     1. There is no selected size, OR
     2. The available sizes for the selected SKU haven't been fetched yet
-->
<input type="submit" value="ADD TO CART" disabled
    class="mdl-button mdl-button--raised mdl-button--accent"
    [disabled]="!selectedSize || !shirts[selected.sku].sizes[selectedSize]">

Try it out: If you select a size that's unavailable, you can't add it to the cart.