LWC Wire Adapters #1 - Consuming Data Streams

The previous post talked about LWC reactivity using membranes, which re-render the components when the tracked data is changed, like bellow:

class MyComponent extends LightningElement {
  @track value;

But how does the data get to the component property, value in the example above? It can come from an external data source like a REST service, and it might even update over time. Think about a stream of data feeding the component with new values when they become available.

Part of the standard web component lifecycle, the connectedCallback() method is called when the component is added to a document connected element. In short, when the custom element is added to the DOM of your page. At that time, it has access to the DOM element with its attributes, so it can call a service using these values. This is the recommended way for developers to connect the component with data sources.

LWC goes further than that, as it makes it simpler, thanks to the "wire adapters". Technically, a wire adapter is a data provider that streams data to a component. The notion of stream is important: it is not only a one time data retrieval, like a Promise, but the data can be continuously updated and the component notified. For the JS experts, it is much like an RxJS Observable.

Wire Adapters from a Consumer Standpoint

Wire adapters are objects instantiated using the @wire decorator. This decorator uses two parameters:
  • The wire adapter key, that uniquely identifies the adapter to use
  • A set of options, specific to the wire adapter implementation
Let's use a fictitious wire adapter to access a list of books. With the use of a parameter, this wire adapter can even filter the books based on the author. Assigning a list of books to a component property is done with the following syntax:

  class Books extends LightningElement {
    @wire(BookProvider, {author: 'Mark Twain'}) bookList;
  • BookProvider is the wire adapter key. It is generally a Symbol, exported by the module defining the wire adapter. 
  • {author: 'Mark Twain'} is the set of options passed to the wire adapter.  This wire adapter only handles one, which is the author filter.
When the component is added to the DOM, the wire adapter is connected and executed. Obviously, it will retrieve a list of books and assign it to bookList. The result can be assigned synchronously or asynchronously, depending on the wire adapter implementation. With the later, the component should not assume that the data is available right away, but it can come later.

What the bookList variable contains is up to the wire adapter: it can be  raw data, like an array of books, of it can be a richer structure giving more information {data,error,loading, ...}. The later is a good practice. It allows, for example, the component to display an icon while the data is loading asynchronously. Moreover, the property content is not limited to values, but can also contain any kind of JS objects, including callback functions (ex: reload(), fetchMore(), ...). Again, it is up to the wire adapter to define the shape of the data being assigned to the property.

The wired properties are automatically reactive, similarly to properties decorated with @track.

Dynamic Wire Adapters

The wire adapter options can be dynamic. Let's, for example, filter the book list using an author passed as an attribute to the component. This way, we can use the same component multiple times while displaying different lists:

  <my-books author='Mark Twain'>
  <my-books author='Stephen King'>

Our Books component becomes:

  class Books extends LightningElement {
    @api author;
    @wire(BookProvider, {author: '$author'}) bookList;

The @api decorator in LWC means that the 'author' property is public: it can be set through an attribute in the markup, or programmatically from outside of the component. The $author value is an LWC convention: it means that the component's author property value should be used instead of a static string value. Note that this only applies to root values. Options like {user: {name:'$name'}} will not be recognized as a reference to a property value, because name is not a root property.

Advanced Wire Adapters

If the value provided by a wire adapter can be assigned to a component property, it can also be streamed to a component method. In this case, the value is passed to the method as an argument.
Let's assume, as an example, that we want to split a book properties into different component tracked properties, we can do something like:

  class Books extends LightningElement {
    @track title;
    @track author;
    @wire(BookByISBN, {ISBN: 'xxxxx'}) book(value) {
      this.title = value.title;      this.author = value.author;

Of course, the code of such a method can do a lot more, like calculation, sending updates, ...

Imperative Access

To be complete, the wire adapter key can be either a Symbol or a Function. When it is a function (a "callable"), it means that it can be called by code, simply by calling that function with parameters. If the parameters could match the wire adapter options, this is not a requirement. Also, the returned value is adapter specific: it can be anything, and does not have to be a Promise. This is reserved to very specific use cases.

The journey continues. Now that we know how to consume a wire adapter, let's figure out how to build one. That will be the topic of the next post.