AMP

The value of amp-list initialization from state

Websites

The amp-list component recently added support for rendering AMP state on page load. This can deliver a better user experience while simplifying assembly of AMP pages for some use cases.

Example use case: Search Results

Consider a search results page after the user performs a search. Search results frequently have buttons (or similar) to change the sort order of the results (e.g. A to Z vs Z to A) or apply additional filter constraints (e.g. only show “small” items) to the current set of search results.

A common approach to implement this in AMP is using an amp-list component where the “[src]” attribute is a URL constructed from AMP state. Clicking buttons updates the AMP state, causing the URL to update, which triggers the amp-list to be updated.

<amp-state id="filter"> <script type="application/json"> { "order": "atoz", "size": "small" } </script> </amp-state> <button on="tap:AMP.setState({filter: {order: 'atoz'})">Increasing</button> <button on="tap:AMP.setState({filter: {order: 'ztoa'})">Decreasing</button> <amp-list ... [src]="'/api/search?order=' + filter.order + '&size=' + filter.size" ...>

AMP list page load flicker

In general this works well as buttons update the AMP state and the AMP framework orchestrates the rest of the page update.

There is however a negative. The first page load will display the page without the amp-list results until an API call is made to the backend using the URL in the “src” attribute. The page is then updated with the result.

<amp-list src="/api/search?order=atoz&size=small" [src]="'/api/search?order=' + filter.order + '&size=' + filter.size" ...>

This can cause page flicker if the “src” URL takes too long to respond, which is an undesirable user experience.

A page flicker workaround

There are existing techniques to work around this issue in AMP. For example, a div element can be put on the page and rendered server side using the API results for the first page. Initially the div is visible and the amp-list is hidden. As soon as the user clicks on a button, the div is updated to be hidden and the amp-list is visible.

<div hidden="false" [hidden]="filter.changesMade" > ... server side rendered content ... </div> <amp-list hidden="true" [hidden]="!filter.changesMade" src="/api/search?order=atoz&size=small" [src]="'/api/search?order=' + filter.order + '&size=' + filter.size" ...>

While this approach works, it has some drawbacks such as there being additional AMP state to maintain and you have the added complexity of rendering the content identically on the server and in the browser. 

Introducing the amp-state prefix

There is, however, a simpler solution now available in AMP which leverages amp-list support of rendering content based on AMP state instead of making an API call. API calls when made update AMP state rather than the amp-list directly.

<amp-list src="amp-state:searchResponse" [src]="searchResponse.items" ...>

The “amp-state:” prefix (the recent addition to AMP) allows the initial amp-list render to be initialized from the AMP state without an API call. Notice how the “src” and “[src]” attributes both reference the same “searchResponse” AMP state, ensuring consistency. This works in conjunction with an amp-state element which can be initialized with JSON contents, and then later updated via API calls via the “[src]” attribute.

<amp-state id="searchResponse" [src]="'/api/search?order=' + filter.order + '&size=' + filter.size" > <script type="application/json"> ... server side injected initial AMP state goes here ... </script> </amp-state>

That is, on page load AMP state will be initialized from JSON embedded in the page. The amp-list then renders this state using the Mustache template. The initial AMP state is server side injected into the page. Many programming languages have JSON support simplifying the injection process. (The AMP Mustache library is only available in JavaScript and so is harder to integrate, for example, if you are using PHP for server side rendering.)

In this example, after the initial page is rendered, if a button is pressed it will update “filter.order” or “filter.size”. This will trigger the amp-state component to do an API call to refresh the “searchResponse” AMP state. This in turn will trigger the amp-list component to render the updated response using the Mustache template.

The end result is the amp-list can be initialized by injecting JSON into the returned page without a separate API call required to perform the initial render. It also helps distinguish the responsibility of different buttons and components. User interface controls update the state. AMP then performs the cascade of updates and API calls required to refresh the page appropriately.

It also avoids maintenance challenges of keeping the AMP page mustache template in sync with the server side rendering logic.

Designing pages with state

The above discussion focussed on the technical aspects of how the new “amp-state:” prefix works, but there is another question about what a good, maintainable page looks like. AMP supports complex bind expressions, but pushing these expressions to the limit can end up with overly complex pages that can be difficult to maintain.

For example the Model/View/Controller (MVC) pattern encourages the separation of capturing the underlying data structure (the model) from code rendering content onto a page (the view). Controllers then modified the model, triggering a view update.

The same pattern is relevant for AMP pages. The AMP state is the model, amp-list and bind expressions are how you define a “view”, then “on” attributes implement controller logic, with amp-script available for more complex controller logic. That is, it is good design to avoid putting overly complex logic into view logic (bind expressions). Instead, design the model to be easy to render and move any complex logic (when required) into the code computing the new state.

Conclusions

In conclusion, the new “amp-state:” prefix in the “src” attribute of the amp-list element can avoid the need for an additional API call on page load. This improves the initial page render time and can avoid page flicker when using amp-list.

It also simplifies the previous workaround of using the “hidden” attribute to hide and reveal div and amp-list elements. It not only eliminates the need for the div element, it also means the server only has to server side render JSON rather than the full HTML markup that is normally generated by the amp-list Mustache template. This avoids code redundancy.

The end model also often ends up in simpler AMP markup as there is good separation of concerns in the AMP page. Buttons are only responsible to update AMP state, the amp-list always reads from AMP state, AMP orchestrates the series of updates, and so forth.

As always, please let us know if you have any issues or feature requests especially in rendering dynamic content in AMP!

Posted by Alan Kent, Developer Advocate, Google