AEM: Sharing Data Between Components

Setting sling request attributes in AEM components and services is a common anti-pattern. It's equivalent to setting global variables without context, with no way of reliably determining what code set the data or what it was for. To do this safely, you need to have some sort of contract between the code setting the request attributes and the code using the request attributes. However, these contracts rarely exist, and if so they almost never exist in a formalized way. My general opinion is to avoid this altogether.

That being said, sometimes in AEM data needs shared between components. The normal approach to this is to set fields in the context repository. However, this only works for data that you want to persist. What about transient data that needs generated in the context of a single sling request? HTL has it's limitations in terms of data sharing across components. Shared templates could be used, but if you need multiple components each with their own dialogs and authored content, this won't work since you will need to use data-sly-resource instead of data-sly-include.

Sling request attributes can solve this, but how can we safely use this potentially dangerous solution? How can we provide reliability, consistency, and an easy to follow contract? Read on to find out.

First of all we need a unique identifier. This will come from three pieces of information: (1) the path of the component in the content repository, (2) the resource type of the component, (3) some name to identify the data being shared. This will provide a unique identifier that both the component setting the sling request attribute and the component getting the sling request attribute can use.

@Model(adaptables = { Resource.class, SlingHttpServletRequest.class })
public class ParentExampleModel {
    public static final String RESOURCE_TYPE = "myapp/components/parentexample";

    @SlingObject
    private SlingHttpServletRequest slingHttpServletRequest;

    @SlingObject
    private Resource currentResource;

    @PostConstruct
    protected void init() {
        slingHttpServletRequest.setAttribute(
            currentResource.getPath() + RESOURCE_TYPE + "helloMessage",
            getMessage()); 
    }

    public String getHelloMessage() {
        return "Hello from parent";
    }
}

In the above code the parent sets the data. This data is unique to the current sling request, is transient (so it will not be persisted beyond the life of the sling request), and is very unlikely to be accidentally accessed by the wrong code. In order to access this data you need to very specifically ask for a specific CRX node of a specific resource type with a specific name.

@Model(adaptables = { Resource.class, SlingHttpServletRequest.class })
public class ChildExampleModel {
    public static final String RESOURCE_TYPE = "myapp/components/childexample";

    @SlingObject
    private SlingHttpServletRequest slingHttpServletRequest;

    @SlingObject
    private Resource currentResource;

    public String getParentMessage() {
        return (String) slingHttpServletRequest.getAttribute(
            currentResource.getParent().getParent().getPath + "helloMessage"
            ParentExampleModel.RESOURCE_TYPE);
    }
}
One use case for this is having a parent component generate a random unique ID for a set of child components that need also need that ID. This could be used for bootstrap components, for example, where each item of a carousel or accordion needs the ID of the parent.

Comments

Popular Posts