Session 5 — Sling Models
Purpose: Move logic out of templates into Sling Models — the custom hooks of AEM — with injection, defaults and exporters.
Who this page is for
| Audience | Why it matters to you |
|---|---|
| Backend engineers | Core daily skill |
| Frontend engineers | You need to read these fluently |
The custom-hook of AEM
A Sling Model adapts a resource (raw content) into a typed, view-ready API — exactly the job of a custom hook adapting store state for a component. HTL stays dumb; the model owns logic.
@Model(adaptables = SlingHttpServletRequest.class,
adapters = PlanCardModel.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class PlanCardModel {
@ValueMapValue
private String title; // ← like reading a store field
@ValueMapValue @Default(doubleValues = 0)
private double monthlyPremium;
@ChildResource
private List<Benefit> benefits; // ← multifield rows
public String getFormattedPremium() { // ← derived state lives HERE,
return String.format("$%.2f / month", monthlyPremium); // not in HTL
}
public boolean isAffordableTier() { return monthlyPremium < 300; }
public String getTitle() { return title; }
public List<Benefit> getBenefits() { return benefits; }
}
Injection annotations you will actually use
| Annotation | Injects | Hook analogy |
|---|---|---|
@ValueMapValue | A property of the current resource | useSelector(s => s.x) |
@ChildResource | Child node(s), optionally adapted to classes | Selecting a slice of rows |
@Self | The adaptable itself (request/resource) | Hook context |
@OSGiService | A backend service | useContext(ServiceContext) |
@ScriptVariable | Page-level objects (currentPage, pageManager) | Router/context objects |
@RequestAttribute | A request attribute (e.g. passed by data-sly-resource) | Props from parent |
@PostConstruct method | Runs after injection — derive/validate here | The hook body |
Rules of thumb
- OPTIONAL injection + explicit defaults — models must survive missing content the way components survive missing props.
- Expose getters with view-language names (
getFormattedPremium), keep raw fields private. - Adaptables: use
SlingHttpServletRequestwhen you need request context, plainResourceotherwise (more cacheable, easier to test). - Test with wcm.io AEM Mocks — models are plain classes, unit test them like hooks with a mock store.
Exporters: your model as JSON
Add @Exporter(name = "jackson", extensions = "json") (Sling Model Exporter) and the same model that feeds HTL serves headless clients at resource.model.json — one adapter, two channels. This is how AEM SPA/headless delivery works under the hood.