@@ -361,6 +361,9 @@ code works identically to the previous example:
361361</div>
362362```
363363
364+ If an element has _ both_ ` data-model ` and ` name ` attributes, the
365+ ` data-model ` attribute takes precedence.
366+
364367## Loading States
365368
366369Often, 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:
11351138 #}
11361139>
11371140```
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