Replicate data-sly-resource in AEM SPA Component

In traditional AEM development, composite components are built by combining several atomic components through the data-sly-resource statement. For instance, Teaser component is built using image, text, and button components. However, in the SPA paradigm, neither React nor Angular implementations offer direct alternatives for this approach.
To implement a feature similar to data-sly-resource in a Single Page Application (SPA), we can utilize the Container core component. Here are the steps that need to be followed.
  • Create a proxy component by extending Container core component.
    teaser / .content.xml
    <jcr:root xmlns:sling="" xmlns:cq="" xmlns:jcr="" jcr:primaryType="cq:Component" cq:isContainer="{Boolean}true" jcr:title="Teaser" sling:resourceSuperType="core/wcm/components/container/v1/container" componentGroup="AEM React (SPA) - Content"/>
  • Included the components you want to embed, which will be added to the JCR as soon as the component is dropped in the parsys.
    teaser / _cq_template / .content.xml
    <jcr:root xmlns:sling="" xmlns:cq="" xmlns:jcr="" jcr:primaryType="nt:unstructured"> <teaser_text jcr:primaryType="nt:unstructured" sling:resourceType="aem-react-spa/components/text"/> <teaser_action jcr:primaryType="nt:unstructured" sling:resourceType="aem-react-spa/components/button"/> <teaser_image jcr:primaryType="nt:unstructured" sling:resourceType="aem-react-spa/components/image"/> </jcr:root>
  • Fetch embedded components in Sling Model using the Sling Model Delegation Pattern.
    @Model( adaptables = SlingHttpServletRequest.class, adapters = { LayoutContainer.class, ComponentExporter.class }, resourceType = Teaser.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL ) @Exporter( name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION ) public class Teaser implements LayoutContainer { protected static final String RESOURCE_TYPE = "aem-react-spa/components/teaser"; @Self @Delegate(excludes = DelegationExclusion.class) @Via(type = ResourceSuperType.class) private LayoutContainer layoutContainer; @Override public String getExportedType() { return TextImageImpl.RESOURCE_TYPE; } private interface DelegationExclusion { String getExportedType(); } }
  • In the last step, add the embedded components into the React Component, where this.childComponents containing all embedded elements, aligning with the order specified in the _cq_template. For instance, index 0 represents text, index 1 represents a button, and so forth.
    class Teaser extends Container<ContainerProperties, ContainerState> { render() { return ( <div className='cmp-teaser'> <div className='cmp-teaser__text'> {this.childComponents[0]} </div> <div className='cmp-teaser__action'> {this.childComponents[1]} </div> <div className='cmp-teaser__image'> {this.childComponents[2]} </div> </div> ); } }
If you've been following along with us thus far, in your Author, you should observe something similar to the following.
Teaser Author View