1 | # Wire Service
|
2 |
|
3 | This is the implementation of Lightning Web Component's wire service. It enables declarative binding of services to a LWC component using the `@wire` decorator. It fulfills the goals of the [data service proposal](https://github.com/salesforce/lwc-rfcs/blob/master/text/0000-data-service.md).
|
4 |
|
5 | ## Summary
|
6 |
|
7 | The `@wire` decorator provides LWC components with a declarative mechanism to express their data requirements. Imperative access (eg for DML) is not part of the wire service. A summary of the wire service follows:
|
8 |
|
9 | - Loading data is expressed declaratively by a component using `@wire([adapterId], [adapterConfig])`
|
10 | - `[adapterId]` refers to the identity of a wire adapter.
|
11 | - `[adapterConfig]` is an optional parameter, of type object, that defines wire adapter-specific configuration.
|
12 | - `[adapterConfig]` may contain static values or reactive references.
|
13 | - A reactive reference is identified with a `$` prefix. The remainder of the string identifies a class property. A change to a referenced class property causes new data to be requested from the wire adapter.
|
14 | - The wire service delegates to `wire adapters` to source, manage, and provision data. The wire service sits between wire adapters and LWC components.
|
15 | - It's all data from the wire service's perspective. Nothing is metadata.
|
16 | - It is assumed all data mutates over time yet a given snapshot of data is immutable.
|
17 | - A component receiving data does not own that data. It is comparable to a component receiving props does not own the props.
|
18 |
|
19 | ## Example Use Of `@wire`
|
20 |
|
21 | Consider a component that wants to display the details of a todo item. It uses `@wire` to declare its data requirements.
|
22 |
|
23 | ```js
|
24 | import { LightningElement, api, wire } from 'lwc';
|
25 |
|
26 | // the wire adapter identifier
|
27 | import { getTodo } from 'todo-api';
|
28 |
|
29 | export default class TodoViewer extends LightningElement {
|
30 | @api id;
|
31 |
|
32 | // declare the need for data. $id creates a reactive property tied to this.id.
|
33 | // data is provisioned into this.todo.
|
34 | @wire(getTodo, { id: '$id' })
|
35 | todo;
|
36 | }
|
37 | ```
|
38 |
|
39 | ```html
|
40 | <template>
|
41 | <template if:true="{todo}">
|
42 | <input type="checkbox" checked="{todo.completed}" /> {todo.title}
|
43 | </template>
|
44 | </template>
|
45 | ```
|
46 |
|
47 | ## Wire Adapter Specification
|
48 |
|
49 | The following is a summary of the [wire adapter RFC](https://github.com/salesforce/lwc-rfcs/blob/master/text/0103-wire-adapters.md).
|
50 |
|
51 | A `wire adapter` provisions data to a wired property or method using an [Event Target](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget). A factory function is registered for declarative `@wire` use by a component.
|
52 |
|
53 | ```ts
|
54 | // The identifier for the wire adapter
|
55 | type WireAdapterId = Function|Symbol;
|
56 |
|
57 | // The factory function invoked for each @wire in a component. The WireEventTarget
|
58 | // allows the wire adapter instance to receive configuration data and provision
|
59 | // new values.
|
60 | type WireAdapterFactory = (eventTarget: WireEventTarget) => void;
|
61 |
|
62 | // Event the wire adapter dispatches to provision values to the wired property or method
|
63 | interface ValueChangedEvent {
|
64 | value: any;
|
65 | new(value: any) : ValueChangedEvent;
|
66 | }
|
67 |
|
68 | // Event types the wire adapter may listen for
|
69 | type EventType = 'config' | 'connect' | 'disconnect';
|
70 |
|
71 | // Event listener callback
|
72 | type Listener = (config?: { [key: string]: any ) => void;
|
73 |
|
74 | // Target of the @wire
|
75 | interface WireEventTarget extends EventTarget {
|
76 | dispatchEvent(event: ValueChangedEvent): boolean;
|
77 | addEventListener(type: EventType, listener: Listener): void;
|
78 | removeEventListener(type: EventType, listener: Listener): void;
|
79 | }
|
80 | ```
|
81 |
|
82 | In the component's `wiring` lifecycle, the wire service invokes the `wireAdapterFactory` function to configure an instance of the wire adapter for each `@wire` instance (which is per component instance).
|
83 |
|
84 | `eventTarget` is an implementation of [Event Target](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) that supports listeners for the following events:
|
85 |
|
86 | - `config` is delivered when the resolved configuration changes. A singular argument is provided: the resolved configuration.
|
87 | - `connect` is delivered when the component is connected.
|
88 | - `disconnect` is delivered when the component is disconnected.
|
89 |
|
90 | The wire service remains responsible for resolving the configuration object. `eventTarget` delivers a `config` event when the resolved configuration changes. The value of the configuration is specific to the wire adapter. The wire adapter must treat the object as immutable.
|
91 |
|
92 | The wire adapter is responsible for provisioning values by dispatching a `ValueChangedEvent` to the event target. `ValueChangedEvent`'s constructor accepts a single argument: the value to provision. There is no limitation to the shape or contents of the value to provision. The event target handles property assignment or method invocation based on the target of the `@wire`.
|
93 |
|
94 | The wire adapter is responsible for maintaining any context it requires. For example, tracking the values it provisions and the originating resolved configuration is shown in the basic example below.
|
95 |
|
96 | ### Registering A Wire Adapter
|
97 |
|
98 | A wire adapter must be registered with the wire service. The `wire-service` module provides a registration function.
|
99 |
|
100 | ```ts
|
101 | register(adapterId: WireAdapterId, wireAdapterFactory: WireAdapterFactory);
|
102 | ```
|
103 |
|
104 | ## Example Wire Adapter Implementation
|
105 |
|
106 | ```js
|
107 | import { register, ValueChangedEvent } from 'wire-service';
|
108 |
|
109 | // Imperative access.
|
110 | export function getTodo(config) {
|
111 | return getObservable(config).map(makeReadOnlyMembrane).toPromise();
|
112 | }
|
113 |
|
114 | // Declarative access: register a wire adapter factory for @wire(getTodo).
|
115 | register(getTodo, function getTodoWireAdapterFactory(eventTarget) {
|
116 | let subscription;
|
117 | let config;
|
118 |
|
119 | // Invoked when config is updated.
|
120 | eventTarget.addListener('config', (newConfig) => {
|
121 | // Capture config for use during subscription.
|
122 | config = newConfig;
|
123 | });
|
124 |
|
125 | // Invoked when component connected.
|
126 | eventTarget.addListener('connect', () => {
|
127 | // Subscribe to stream.
|
128 | subscription = getObservable(config)
|
129 | .map(makeReadOnlyMembrane)
|
130 | .subscribe({
|
131 | next: (data) =>
|
132 | wiredEventTarget.dispatchEvent(
|
133 | new ValueChangedEvent({ data, error: undefined })
|
134 | ),
|
135 | error: (error) =>
|
136 | wiredEventTarget.dispatchEvent(
|
137 | new ValueChangedEvent({ data: undefined, error })
|
138 | ),
|
139 | });
|
140 | });
|
141 |
|
142 | // Invoked when component disconnected.
|
143 | eventTarget.addListener('disconnect', () => {
|
144 | // Release all resources.
|
145 | subscription.unsubscribe();
|
146 | });
|
147 | });
|
148 | ```
|
149 |
|
150 | # Contributing To The Wire Service
|
151 |
|
152 | ## Build & run the playground
|
153 |
|
154 | A _playground_ of LWC components using @wire is included. They're served from a basic node server and accessible in your browser.
|
155 |
|
156 | ```bash
|
157 | yarn start
|
158 | ```
|
159 |
|
160 | Load the examples in a browser:
|
161 |
|
162 | ```bash
|
163 | open http://localhost:3000/
|
164 | ```
|