Show Menu
TOPICS×

Developing with the AEM SPA Editor - Hello World Tutorial

AEM's SPA Editor provides support for in-context editing of a Single Page Application or SPA. This tutorial is an introduction to SPA development to be used with AEM's SPA Editor JS SDK. The tutorial will extend the We.Retail Journal app by adding a custom Hello World component. Users can complete the tutorial using React or Angular frameworks.
The Single-Page Application (SPA) Editor feature requires AEM 6.4 service pack 2 or newer.
The SPA Editor is the recommended solution for projects that require SPA framework based client-side rendering (e.g. React or Angular).

Prerequisite Reading

This tutorial is intended to highlight the steps needed to map a SPA component to an AEM component to enable in-context editing. Users starting this tutorial should be familiar with basic concepts of development with Adobe Experience Manager, AEM, as well as developing with React of Angular frameworks. The tutorial covers both back-end and front-end development tasks.
The following resources are recommended to be reviewed before starting this tutorial:

Local Development Environment

This tutorial is designed for:
In this tutorial the following technologies and tools should be installed:
  1. Node.js - 8.11.1+ and npm 5.6.0+ (npm is installed with node.js)
Double check the installation of the above tools by opening up a new terminal and running the following:
$ java -version
java version "1.8 +"

$ mvn -version
Apache Maven 3.3.9

$ node --version
v8.11.1

$ npm --version
6.1.0


Overview

The basic concept is to map a SPA Component to an AEM Component. AEM components, running server-side, export content in the form of JSON. The JSON content is consumed by the SPA, running client-side in the browser. A 1:1 mapping between SPA components and an AEM component is created.
Popular frameworks React JS and Angular are supported out of the box. Users can complete this tutorial in either Angular or React, whichever framework they are most comfortable with.

Project Setup

SPA development has one foot in AEM development, and the other out. The goal is to allow SPA development to occur independently, and (mostly) agnostic to AEM.
  • SPA projects can operate independently of the AEM project during front-end development.
  • Front-end build tools and technologies like Webpack, NPM, Grunt and Gulp continue to be used.
  • To build for AEM, the SPA project is compiled and automatically included in the AEM project.
  • Standard AEM Packages used to deploy the SPA into AEM.
SPA development has one foot in AEM development, and the other out - allowing SPA development to occur independently, and (mostly) agnostic to AEM.
The goal of this tutorial is to extend the We.Retail Journal App with a new component. Start by downloading the source code for the We.Retail Journal app and deploying to a local AEM.
  1. Or clone the repository from the command line:
    $ git clone git@github.com:adobe/aem-sample-we-retail-journal.git
    
    
    
    The tutorial will be working against the master branch with 1.2.1-SNAPSHOT version of the project.
  2. The following structure should be visible:
    The project contains the following maven modules:
    • all : Embeds and installs the entire project in a single package.
    • bundles : Contains two OSGi bundles: commons and core that contain Sling Models and other Java code.
    • ui.apps : contains the /apps parts of the project, ie JS&CSS clientlibs, components, runmode specific configs.
    • ui.content : contains structural content and configurations ( /content , /conf )
    • react-app : We.Retail Journal React application. This is both a Maven module and a webpack project.
    • angular-app : We.Retail Journal Angular application. This is both a Maven module and a webpack project.
  3. Open a new terminal window and run the following command to build and deploy the entire app to a local AEM instance running on http://localhost:4502 .
    $ cd <src>/aem-sample-we-retail-journal
    $ mvn -PautoInstallSinglePackage clean install
    
    
    
    In this project the Maven profile to build and package the entire project is autoInstallSinglePackage
  4. Navigate to:
    The We.Retail Journal App should be displayed within the AEM Sites editor.
  5. In Edit mode, select a component to edit and make an update to the content.
  6. Select the Page Properties Icon to open the Page Properties Menu. Select Edit Template to open the page's template.
  7. In the latest version of the SPA Editor, Editable templates can be used in the same way as with traditional Sites implementations. This will be revisited later with our custom component.
    Only AEM 6.5 and AEM 6.4 + Service Pack 5 support Editable templates.

Development Overview

SPA development iterations occur independently of AEM. When the SPA is ready to be deployed into AEM the following high-level steps take place (as illustrated above).
  1. The AEM project build is invoked, which in turn triggers a build of the SPA project. The We.Retail Journal uses the frontend-maven-plugin .
  2. The SPA project's aem-clientlib-generator embeds the compiled SPA as an AEM Client Library in the AEM project.
  3. The AEM project generates an AEM package, including the compiled SPA, plus any other supporting AEM code.

Create AEM Component

Persona: AEM Developer
An AEM component will be created first. The AEM component is responsible for rendering the JSON properties that are read by the React component. The AEM component is also responsible for providing a dialog for any editable properties of the component.
Using Eclipse, or other IDE, import the We.Retail Journal Maven project.
  1. Update the reactor pom.xml to remove the apache rat plugin. This plugin checks each file to ensure that there is a License header. For our purposes we don't need to be concerned with this functionality.
    In aem-sample-we-retail-journal/pom.xml remove apache-rate-plugin :
    <!-- Remove apache-rat-plugin -->
    <plugin>
            <groupId>org.apache.rat</groupId>
            <artifactId>apache-rat-plugin</artifactId>
            <configuration>
                <excludes combine.children="append">
                    <exclude>*</exclude>
                        ...
                </excludes>
            </configuration>
            <executions>
                    <execution>
                        <phase>verify</phase>
                        <goals>
                            <goal>check</goal>
                        </goals>
                </execution>
            </executions>
        </plugin>
    
    
  2. In the we-retail-journal-content ( <src>/aem-sample-we-retail-journal/ui.apps ) module create a new node beneath ui.apps/jcr_root/apps/we-retail-journal/components named helloworld of type cq:Component .
  3. Add the following properties to the helloworld component, represented in XML ( /helloworld/.content.xml ) below:
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
        jcr:description="Hello World Component for We.Retail Journal"
        jcr:primaryType="cq:Component"
        jcr:title="Hello World"
        componentGroup="We.Retail Journal" />
    
    
    
    To illustrate the Editable Templates feature we have purposely set the componentGroup="Custom Components" . In a real-world project, it is best to minimize the number of component groups, so a better group would be "We.Retail Journal" to match the other content components.
    Only AEM 6.5 and AEM 6.4 + Service Pack 5 support Editable templates.
  4. Next a dialog will be created to allow for a custom message to be configured for the Hello World component. Beneath /apps/we-retail-journal/components/helloworld add a node name cq:dialog of nt:unstructured .
  5. The cq:dialog will display a single textfield that persists text to a property named message . Beneath the newly created cq:dialog add the following nodes and properties, represented in XML below ( helloworld/_cq_dialog/.content.xml ) :
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
        jcr:primaryType="nt:unstructured"
        jcr:title="We.Retail Journal - Hello World"
        sling:resourceType="cq/gui/components/authoring/dialog">
        <content
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/container">
            <items jcr:primaryType="nt:unstructured">
                <tabs
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/tabs"
                    maximized="{Boolean}true">
                    <items jcr:primaryType="nt:unstructured">
                        <properties
                            jcr:primaryType="nt:unstructured"
                            jcr:title="Properties"
                            sling:resourceType="granite/ui/components/coral/foundation/container"
                            margin="{Boolean}true">
                            <items jcr:primaryType="nt:unstructured">
                                <columns
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
                                    margin="{Boolean}true">
                                    <items jcr:primaryType="nt:unstructured">
                                        <column
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/container">
                                            <items jcr:primaryType="nt:unstructured">
                                                <message
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                    fieldLabel="Message"
                                                    name="./message"
                                                    required="{Boolean}true"/>
                                            </items>
                                        </column>
                                    </items>
                                </columns>
                            </items>
                        </properties>
                    </items>
                </tabs>
            </items>
        </content>
    </jcr:root>
    
    
    The above XML node definition will create a dialog with a single textfield that will allow a user to enter a "message". Note the property name="./message" within the <message /> node. This the name that of the property that will be stored in the JCR within AEM.
  6. Next an empty policy dialog will be created ( cq:design_dialog ). The Policy dialog is needed to see the component in the Template Editor. For this simple use case it will be an empty dialog.
    Beneath /apps/we-retail-journal/components/helloworld add a node name cq:design_dialog of nt:unstructured .
    The configuration is represented in XML below ( helloworld/_cq_design_dialog/.content.xml )
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured" />
    
    
    
  7. Deploy the code base to AEM from the command line:
    $ cd <src>/aem-sample-we-retail-journal/content
    $ mvn -PautoInstallPackage clean install
    
    
    In CRXDE-Lite validate the component has been deployed by inspecting the folder under /apps/we-retail-journal/components:

Create Sling Model

Persona: AEM Developer
Next a Sling Model is created to back the Hello World component. In a traditional WCM use case the Sling Model implements any business logic and a server-side rendering script (HTL) will make a call to the Sling Model. This keeps the rendering script relatively simple.
Sling Models are also used in the SPA use case to implement server-side business logic. The difference is that in the SPA use case, the Sling Model exposes it's methods as serialized JSON.
As a best practice, developers should look to use AEM Core Components when possible. Among other features, Core Components provide Sling Models with JSON output that is "SPA-ready", allowing developers to focus more on front-end presentation.
  1. In the editor of your choice, open the we-retail-journal-commons project ( <src>/aem-sample-we-retail-journal/bundles/commons ).
  2. In the package com.adobe.cq.sample.spa.commons.impl.models :
    • Create a new class named HelloWorld .
    • Add an implementing interface for com.adobe.cq.export.json.ComponentExporter.
    The ComponentExporter interface must be implemented in order for the Sling Model to be compatible with AEM Content Services.
     package com.adobe.cq.sample.spa.commons.impl.models;
    
     import com.adobe.cq.export.json.ComponentExporter;
    
     public class HelloWorld implements ComponentExporter {
    
         @Override
         public String getExportedType() {
             return null;
         }
     }
    
    
    
  3. Add a static variable named RESOURCE_TYPE to identify the HelloWorld component's resource type:
     ...
     public class HelloWorld implements ComponentExporter {
    
         static final String RESOURCE_TYPE = "we-retail-journal/components/helloworld";
    
         ...
     }
    
    
  4. Add the OSGi annotations for @Model and @Exporter . The @Model annotation will register the class as a Sling Model. The @Exporter annotation will expose the methods as serialized JSON using the Jackson Exporter framework.
    import org.apache.sling.api.SlingHttpServletRequest;
    import org.apache.sling.models.annotations.Exporter;
    import org.apache.sling.models.annotations.Model;
    import com.adobe.cq.export.json.ExporterConstants;
    ...
    
    @Model(
            adaptables = SlingHttpServletRequest.class,
            adapters = {ComponentExporter.class},
            resourceType = HelloWorld.RESOURCE_TYPE
    )
    @Exporter(
            name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, 
            extensions = ExporterConstants.SLING_MODEL_EXTENSION
    )
    public class HelloWorld implements ComponentExporter {
    
    ...
    
    
    
  5. Implement the method getDisplayMessage() to return the jcr property message. Use the Sling Model annotation of @ValueMapValue to make it easy to retrieve the property message stored beneath the component. The @Optional annotation is important since when the component is first added to the page, message will not be populated.
    As part of the business logic, a string, " Hello ", will be prepended to the message.
    import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
    import org.apache.sling.models.annotations.Optional;
    
    ...
    
    public class HelloWorld implements ComponentExporter {
    
       static final String RESOURCE_TYPE = "we-retail-journal/components/helloworld";
    
       private static final String PREPEND_MSG = "Hello";
    
        @ValueMapValue @Optional
        private String message;
    
        public String getDisplayMessage() {
            if(message != null && message.length() > 0) {
                return PREPEND_MSG + " "  + message;
            }
            return null;
        }
    
    ...
    
    
    The method name getDisplayMessage is important. When the Sling Model is serialized with the Jackson exporter it will be exposed as a JSON property: displayMessage . The Jackson exporter will serialize and expose all getter methods that do not take a parameter (unless explicitly marked to ignore). Later in the React/Angular app we will read this property value and display it as part of the application.
    The method getExportedType is also important. The value of the component resourceType will be used to "map" the JSON data to the front-end component (Angular/React). We will explore this in the next section.
  6. Implement the method getExportedType() to return the resource type of the helloworld component.
     @Override
        public String getExportedType() {
            return RESOURCE_TYPE;
        }
    
    
    The full code for HelloWorld.java
  7. Deploy the code to AEM using Maven:
    $ cd <src>/sample-we-retail-spa-content/bundles/commons
    $ mvn -PautoInstallPackage clean install
    
    
    Verify the deployment and registration of the Sling Model by navigating to Status in the OSGi Console.
    You should see that the HelloWorld Model is bound to the helloworld resource type and that it is registered as a Sling Model Exporter Servlet:
    com.adobe.cq.sample.spa.commons.impl.models.HelloWorld - we-retail-journal/components/helloworld
    com.adobe.cq.sample.spa.commons.impl.models.HelloWorld exports 'we-retail-journal/components/helloworld' with selector 'model' and extension '[Ljava.lang.String;@6480f3e5' with exporter 'jackson'
    
    

Create React Component

Persona: Front End Developer
Next, the React component will be created. Open the react-app module ( <src>/aem-sample-we-retail-journal/react-app ) using the editor of your choice.
Feel free to skip this section if you are only interested in Angular development .
  1. Inside the react-app folder navigate to its src folder. Expand the components folder to view the existing React component files.
  2. Add a new file beneath the components folder named HelloWorld.js .
  3. Open HelloWorld.js . Add an import statement to import the React component library. Add a second import statement to import the MapTo helper provided by Adobe. The MapTo helper provides a mapping of the React component to the AEM component's JSON.
    import React, {Component} from 'react';
    import {MapTo} from '@adobe/cq-react-editable-components';
    
    
    
  4. Beneath the imports create a new class named HelloWorld that extends the React Component interface. Add the required render() method to the HelloWorld class.
    import React, {Component} from 'react';
    import {MapTo} from '@adobe/cq-react-editable-components';
    
    class HelloWorld extends Component {
    
        render() {
    
        }
    }
    
    
  5. The MapTo helper automatically includes an object named cqModel as part of the React component's props. The cqModel includes all properties exposed by the Sling Model.
    Remember that the Sling Model created earlier contains a method getDisplayMessage() . getDisplayMessage() is translated as a JSON key named displayMessage when outputted.
    Implement the render() method to output an h1 tag that contains the value of displayMessage. JSX , a syntax extension to JavaScript, is used to return the final markup of the component.
    ...
    
    class HelloWorld extends Component {
        render() {
    
            if(this.props.displayMessage) {
                return (
                    <div className="cmp-helloworld">
                        <h1 className="cmp-helloworld_message">{this.props.displayMessage}</h1>
                    </div>
                );
            }
            return null;
        }
    }
    
    
    
  6. Implement an edit configuration method. This method is passed via the MapTo helper and provides the AEM editor with information to display a placeholder in the case the component is empty. This occurs when the component is added to the SPA but has not yet been authored. Add the following below the HelloWorld class:
    ...
    
    class HelloWorld extends Component {
        ...
    }
    
    const HelloWorldEditConfig = {
    
        emptyLabel: 'Hello World',
    
        isEmpty: function(props) {
            return !props || !props.displayMessage || props.displayMessage.trim().length < 1;
        }
    };
    
    ...
    
    
  7. At the end of the file, call the MapTo helper, passing the HelloWorld class and the HelloWorldEditConfig . This will map the React Component to the AEM component based on the AEM Component's resource type: we-retail-journal/components/ helloworld .
    MapTo('we-retail-journal/components/helloworld')(HelloWorld, HelloWorldEditConfig);
    
    
    The completed code for HelloWorld.js
  8. Open the file ImportComponents.js . It can found at <src>/aem-sample-we-retail-journal/react-app/src/ImportComponents.js .
    Add a line to require the HelloWorld.js with the other components in the compiled JavaScript bundle:
    ...
      require('./components/Text');
      require('./components/Image');
      require('./components/HelloWorld');
    ...
    
    
  9. In the components folder create a new file named HelloWorld.css as a sibling of HelloWorld.js. Populate the file with the following to create some basic styling for the HelloWorld component:
    /* HelloWorld.css to style HelloWorld component */
    
    .cmp-helloworld_message {
        text-align: center;
        color: #ff505e;
        text-transform: unset;
        letter-spacing: unset;
    }
    
    
  10. Re-open HelloWorld.js and update below the import statements to require HelloWorld.css :
    import React, {Component} from 'react';
    import {MapTo} from '@adobe/cq-react-editable-components';
    
    require('./HelloWorld.css');
    
    ...
    
    
  11. Deploy the code to AEM using Maven:
    $ cd <src>/sample-we-retail-spa-content
    $ mvn -PautoInstallSinglePackage clean install
    
    
    
  12. In CRXDE-Lite open /apps/we-retail-journal/react/clientlibs/we-retail-journal-react/js/app.js . Perform a quick search for HelloWorld in app.js to verify the React component has been included in the compiled app.
    app.js is the bundled React app. The code is no longer human readable. The npm run build command has triggered an optimized build that outputs compiled javascript that can be interpreted by modern browsers.

Create Angular Component

Persona: Front End Developer
Feel free to skip this section if you are only interested in React development.
Next, the Angular component will be created. Open the angular-app module ( <src>/aem-sample-we-retail-journal/angular-app ) using the editor of your choice.
  1. Inside the angular-app folder navigate to its src folder. Expand the components folder to view the existing Angular component files.
  2. Add a new folder beneath the components folder named helloworld . Beneath the helloworld folder add new files named helloworld.component.css, helloworld.component.html, helloworld.component.ts .
    /angular-app
        /src
            /app
                /components
    +                /helloworld
    +                    helloworld.component.css
    +                    helloworld.component.html
    +                    helloworld.component.ts
    
    
    
  3. Open helloworld.component.ts . Add an import statement to import the Angular Component and Input classes. Create a new component, pointing the styleUrls and templateUrl to helloworld.component.css and helloworld.component.html . Lastly export the class HelloWorldComponent with the expected input of displayMessage .
    //helloworld.component.ts
    
    import { Component, Input } from '@angular/core';
    
    @Component({
      selector: 'app-helloworld',
      host: { 'class': 'cmp-helloworld' },
      styleUrls:['./helloworld.component.css'],
      templateUrl: './helloworld.component.html',
    })
    
    export class HelloWorldComponent {
      @Input() displayMessage: string;
    }
    
    
    
    If you recall the Sling Model created earlier, there was a method getDisplayMessage() . The serialized JSON of this method will be displayMessage , which we are now reading in the Angular app.
  4. Open helloworld.component.html to include an h1 tag that will print the displayMessage property:
    <h1 *ngIf="displayMessage" class="cmp-helloworld_message">
        {{displayMessage}}
    </h1>
    
    
    
  5. Update helloworld.component.css to include some basic styles for the component.
    :host-context {
        display: block;
    };
    
    .cmp-helloworld {
        display:block;
    }
    .cmp-helloworld_message {
        text-align: center;
        color: #ff505e;
        text-transform: unset;
        letter-spacing: unset;
    }
    
    
    
  6. Update helloworld.component.spec.ts with the following test bed:
    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    import { HelloWorldComponent } from './helloworld.component';
    
        describe('HelloWorld', () => {
        let component: HelloWorldComponent;
        let fixture: ComponentFixture<HelloWorldComponent>;
    
        beforeEach(async(() => {
            TestBed.configureTestingModule({
            declarations: [ HelloWorldComponent ]
            })
            .compileComponents();
        }));
    
        beforeEach(() => {
            fixture = TestBed.createComponent(HelloWorldComponent);
            component = fixture.componentInstance;
            fixture.detectChanges();
        });
    
        it('should create', () => {
            expect(component).toBeTruthy();
        });
    });
    
    
  7. Next update src/components/mapping.ts to include the HelloWorldComponent . Add a HelloWorldEditConfig that will mark the placeholder in the AEM editor before the component has been configured. Lastly add a line to map the AEM component to the Angular component with the MapTo helper.
    // src/components/mapping.ts
    
    import { HelloWorldComponent } from "./helloworld/helloworld.component";
    
    ...
    
    const HelloWorldEditConfig = {
    
        emptyLabel: 'Hello World',
    
        isEmpty: function(props) {
            return !props || !props.displayMessage || props.displayMessage.trim().length < 1;
        }
    };
    
    ...
    
    MapTo('we-retail-journal/components/helloworld')(HelloWorldComponent, HelloWorldEditConfig);
    
    
    
    The full code for mapping.ts
  8. Update src/app.module.ts to update the NgModule . Add the HelloWorldComponent as a declaration that belongs to the AppModule . Also add the HelloWorldComponent as an entryComponent so that it is compiled and dynamically included in the app as the JSON model is processed.
    import { HelloWorldComponent } from './components/helloworld/helloworld.component';
    
    ...
    
    @NgModule({
      imports: [BrowserModule.withServerTransition({ appId: 'we-retail-sample-angular' }),
        SpaAngularEditableComponentsModule,
      AngularWeatherWidgetModule.forRoot({
        key: "37375c33ca925949d7ba331e52da661a",
        name: WeatherApiName.OPEN_WEATHER_MAP,
        baseUrl: 'http://api.openweathermap.org/data/2.5'
      }),
        AppRoutingModule,
        BrowserTransferStateModule],
      providers: [ModelManagerService,
        { provide: APP_BASE_HREF, useValue: '/' }],
      declarations: [AppComponent,
        TextComponent,
        ImageComponent,
        WeatherComponent,
        NavigationComponent,
        MenuComponent,
        MainContentComponent,
        HelloWorldComponent],
      entryComponents: [TextComponent,
        ImageComponent,
        WeatherComponent,
        NavigationComponent,
        MainContentComponent,
        HelloWorldComponent],
      bootstrap: [AppComponent]
     })
    
    
    
    The completed code for app.module.ts
  9. Deploy the code to AEM using Maven:
    $ cd <src>/sample-we-retail-spa-content
    $ mvn -PautoInstallSinglePackage clean install
    
    
    
  10. In CRXDE-Lite open /apps/we-retail-journal/angular/clientlibs/we-retail-journal-angular/js/main.js . Perform a quick search for HelloWorld in main.js to verify the Angular component has been included.
    main.js is the bundled Angular app. The code is no longer human readable. The npm run build command has triggered an optimized build that outputs compiled javascript that can be interpreted by modern browsers.

Updating the Template

  1. Navigate to the Editable Template for the React and/or Angular versions:
  2. Select the main Layout Container and select the Policy icon to open its policy:
    Under Properties > Allowed Components , perform a search for "Custom Components" . You should see the Hello World component, select it. Save your changes by clicking the checkbox in the upper-right-hand corner.
  3. After saving, you should see the HelloWorld component as an allowed component in the Layout Container.
    Only AEM 6.5 and AEM 6.4.5 supports the Editable Template feature of the SPA Editor. If using AEM 6.4, you will need to manually configure the policy for Allowed Components via CRXDE Lite: /conf/we-retail-journal/react/settings/wcm/policies/wcm/foundation/components/responsivegrid/default or /conf/we-retail-journal/angular/settings/wcm/policies/wcm/foundation/components/responsivegrid/default
    CRXDE-Lite showing the updated policy configurations for Allowed Components in the Layout Container:

Putting it all together

  1. Navigate to either the Angular or React pages:
  2. Find the Hello World component and drag and drop the Hello World component on to the page.
    The placeholder should appear.
  3. Select the component and add a message in the dialog, i.e "World" or "Your Name". Save the changes.
    Note that the string "Hello " is always prepended to the message. This is a result of the logic in the HelloWorld.java Sling Model.

Next Steps

Troubleshooting

Unable to build project in Eclipse

Error: An error when importing the We.Retail Journal project into Eclipse for unrecognized goal executions: " Execution npm install, Execution npm run build, Execution default-analyze-classes ".
Resolution : Click Finish to resolve these later. This should not prevent the completion of the tutorial.
Error : The React module, react-app, does not build successfully during a Maven build.
Resolution: Try deleting the node_modules folder beneath the react-app . Re-run the maven command mvn clean install -PautoInstallSinglePackage from the root of the project.

Unsatisfied dependencies in AEM

If an AEM dependency is not satisfied, in either the AEM Package Manager or in the AEM Web Console (Felix Console), this indicates that SPA Editor Feature is not available.

Component doesn't display

Error : Even after a successful deployment and verifying that the compiled versions of React/Angular apps have the updated helloworld component my component is not displayed when I drag it on to the page. I can see the component in the AEM UI.
Resolution : Clear your Browser's history/cache and/or open a new browser or use incognito mode. If that doesn't work, invalidate the client library cache on the local AEM instance. AEM attempts to cache large clientlibraries in order to be efficient. Sometimes manually invalidating the cache is needed to fix issues where out-dated code is cached.
Navigate to: http://localhost:4502/libs/granite/ui/content/dumplibs.rebuild.html and click Invalidate Cache. Return to your React/Angular page and refresh the page.