@@ -361,6 +361,9 @@ code works identically to the previous example:
361
361
</div>
362
362
```
363
363
364
+ If an element has _ both_ ` data-model ` and ` name ` attributes, the
365
+ ` data-model ` attribute takes precedence.
366
+
364
367
## Loading States
365
368
366
369
Often, you'll want to show (or hide) an element while a component is
@@ -1135,3 +1138,226 @@ You can also trigger a specific "action" instead of a normal re-render:
1135
1138
#}
1136
1139
>
1137
1140
```
1141
+
1142
+ ## Embedded Components
1143
+
1144
+ Need to embed one live component inside another one? No problem! As a rule
1145
+ of thumb, ** each component exists in its own, isolated universe** . This
1146
+ means that embedding one component inside another could be really simple
1147
+ or a bit more complex, depending on how inter-connected you want your components
1148
+ to be.
1149
+
1150
+ Here are a few helpful things to know:
1151
+
1152
+ ### Each component re-renders independent of one another
1153
+
1154
+ If a parent component re-renders, the child component will _ not_ (most
1155
+ of the time) be updated, even though it lives inside the parent. Each
1156
+ component is its own, isolated universe.
1157
+
1158
+ But this is not always what you want. For example, suppose you have a
1159
+ parent component that renders a form and a child component that renders
1160
+ one field in that form. When you click a "Save" button on the parent
1161
+ component, that validates the form and re-renders with errors - including
1162
+ a new ` error ` value that it passes into the child:
1163
+
1164
+ ``` twig
1165
+ {# templates/components/post_form.html.twig #}
1166
+
1167
+ {{ component('textarea_field', {
1168
+ value: this.content,
1169
+ error: this.getError('content')
1170
+ }) }}
1171
+ ```
1172
+
1173
+ In this situation, when the parent component re-renders after clicking
1174
+ "Save", you _ do_ want the updated child component (with the validation
1175
+ error) to be rendered. And this _ will_ happen automatically. Why? because
1176
+ the live component system detects that the ** parent component has
1177
+ _ changed_ how it's rendering the child** .
1178
+
1179
+ This may not always be perfect, and if your child component has its own
1180
+ ` LiveProp ` that has changed since it was first rendered, that value will
1181
+ be lost when the parent component causes the child to re-render. If you
1182
+ have this situation, use ` data-model-map ` to map that child ` LiveProp ` to
1183
+ a ` LiveProp ` in the parent component, and pass it into the child when
1184
+ rendering.
1185
+
1186
+ ### Actions, methods and model updates in a child do not affect the parent
1187
+
1188
+ Again, each component is its own, isolated universe! For example, suppose
1189
+ your child component has:
1190
+
1191
+ ``` html
1192
+ <button data-action =" live#action" data-action-name =" save" >Save</button >
1193
+ ```
1194
+
1195
+ When the user clicks that button, it will attempt to call the ` save ` action
1196
+ in the _ child_ component only, even if the ` save ` action actually only
1197
+ exists in the parent. The same is true for ` data-model ` , though there is
1198
+ some special handling for this case (see next point).
1199
+
1200
+ ### If a child model updates, it will attempt to update the parent model
1201
+
1202
+ Suppose a child component has a:
1203
+
1204
+ ``` html
1205
+ <textarea data-model =" markdown_value" data-action =" live#update" >
1206
+ ```
1207
+
1208
+ When the user changes this field, this will _ only_ update the ` markdown_value `
1209
+ field in the _ child_ component... because (yup, we're saying it again):
1210
+ each component is its own, isolated universe.
1211
+
1212
+ However, sometimes this isn't what you want! Sometimes, in addition
1213
+ to updating the child component's model, you _ also_ want to update a
1214
+ model on the _ parent_ component.
1215
+
1216
+ To help with this, whenever a model updates, a ` live:update-model ` event
1217
+ is dispatched. All components automatically listen to this event. This
1218
+ means that, when the ` markdown_value ` model is updated in the child
1219
+ component, _ if_ the parent component _ also_ has a model called ` markdown_value `
1220
+ it will _ also_ be updated. This is done as a "deferred" update
1221
+ (i.e. [ updateDefer()] ( #deferring-a-re-render-until-later ) ).
1222
+
1223
+ If the model name in your child component (e.g. ` markdown_value ` ) is
1224
+ _ different_ than the model name in your parent component (e.g. ` post.content ` ),
1225
+ you have two options. First, you can make sure both are set by
1226
+ leveraging both the ` data-model ` and ` name ` attributes:
1227
+
1228
+ ``` twig
1229
+ <textarea
1230
+ data-model="markdown_value"
1231
+ name="post[content]"
1232
+ data-action="live#update"
1233
+ >
1234
+ ```
1235
+
1236
+ In this situation, the ` markdown_value ` model will be updated on the child
1237
+ component (because ` data-model ` takes precedence over ` name ` ). But if
1238
+ any parent components have a ` markdown_value ` model _ or_ a ` post.content `
1239
+ model (normalized from ` post[content ` ] `), their model will also be updated.
1240
+
1241
+ A second option is to wrap your child element in a special ` data-model-map `
1242
+ element:
1243
+
1244
+ ``` twig
1245
+ {# templates/components/post_form.html.twig #}
1246
+
1247
+ <div data-model-map="from(markdown_value)|post.content">
1248
+ {{ component('textarea_field', {
1249
+ value: this.content,
1250
+ error: this.getError('content')
1251
+ }) }}
1252
+ </div>
1253
+ ```
1254
+
1255
+ Thanks to the ` data-model-map ` , whenever the ` markdown_value ` model
1256
+ updates in the child component, the ` post.content ` model will be
1257
+ updated in the parent component.
1258
+
1259
+ ** NOTE** : If you _ change_ a ` LiveProp ` of a child component on the server
1260
+ (e.g. during re-rendering or via an action), that change will _ not_ be
1261
+ reflected on any parent components that share that model.
1262
+
1263
+ ### Full Embedded Component Example
1264
+
1265
+ Let's look at a full, complex example of an embedded component. Suppose
1266
+ you have an ` EditPostComponent ` :
1267
+
1268
+ ``` php
1269
+ <?php
1270
+
1271
+ namespace App\Twig\Components;
1272
+
1273
+ use App\Entity\Post;
1274
+ use Doctrine\ORM\EntityManagerInterface;
1275
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1276
+ use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
1277
+ use Symfony\UX\LiveComponent\Attribute\LiveAction;
1278
+ use Symfony\UX\LiveComponent\Attribute\LiveProp;
1279
+
1280
+ #[AsLiveComponent('edit_post')]
1281
+ final class EditPostComponent extends AbstractController
1282
+ {
1283
+ #[LiveProp(exposed: ['title', 'content'])]
1284
+ public Post $post;
1285
+
1286
+ #[LiveAction]
1287
+ public function save(EntityManagerInterface $entityManager)
1288
+ {
1289
+ $entityManager->flush();
1290
+
1291
+ return $this->redirectToRoute('some_route');
1292
+ }
1293
+ }
1294
+ ```
1295
+
1296
+ And a ` MarkdownTextareaComponent ` :
1297
+
1298
+ ``` php
1299
+ <?php
1300
+
1301
+ namespace App\Twig\Components;
1302
+
1303
+ use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
1304
+ use Symfony\UX\LiveComponent\Attribute\LiveProp;
1305
+
1306
+ #[AsLiveComponent('markdown_textarea')]
1307
+ final class MarkdownTextareaComponent
1308
+ {
1309
+ #[LiveProp]
1310
+ public string $label;
1311
+
1312
+ #[LiveProp]
1313
+ public string $name;
1314
+
1315
+ #[LiveProp(writable: true)]
1316
+ public string $value = '';
1317
+ }
1318
+ ```
1319
+
1320
+ In the ` EditPostComponent ` template, you render the ` MarkdownTextareaComponent ` :
1321
+
1322
+ ``` twig
1323
+ {# templates/components/edit_post.html.twig #}
1324
+ <div {{ init_live_component(this) }}>
1325
+ <input
1326
+ type="text"
1327
+ name="post[title]"
1328
+ data-action="live#update"
1329
+ value="{{ this.post.title }}"
1330
+ >
1331
+
1332
+ {{ component('markdown_textarea', {
1333
+ name: 'post[content]',
1334
+ label: 'Content',
1335
+ value: this.post.content
1336
+ }) }}
1337
+
1338
+ <button
1339
+ data-action="live#action"
1340
+ data-action-name="save"
1341
+ >Save</button>
1342
+ </div>
1343
+ ```
1344
+
1345
+ ``` twig
1346
+ <div {{ init_live_component(this) }} class="mb-3">
1347
+ <textarea
1348
+ name="{{ this.name }}"
1349
+ data-model="value"
1350
+ data-action="live#update"
1351
+ >{{ this.value }}</textarea>
1352
+
1353
+ <div class="markdown-preview">
1354
+ {{ this.value|markdown_to_html }}
1355
+ </div>
1356
+ </div>
1357
+ ```
1358
+
1359
+ Notice that ` MarkdownTextareaComponent ` allows a dynamic ` name ` attribute to
1360
+ be passed in. This makes that component re-usable in any form. But it
1361
+ also makes sure that when the ` textarea ` changes, both the ` value ` model
1362
+ in ` MarkdownTextareaComponent ` _ and_ the ` post.content ` model in
1363
+ ` EditPostcomponent ` will be updated.
0 commit comments