Enhance
Learn how to use Aurelia's enhance feature to add interactivity to existing HTML, integrate with other frameworks, hydrate server-rendered content, and create multiple Aurelia instances in your applic
What is Enhancement?
Enhancement allows you to bring Aurelia's data binding, templating, and component features to existing DOM content without replacing it entirely. Instead of starting with an empty element and rendering into it, enhancement takes existing HTML and makes it "Aurelia-aware".
This is perfect for:
Progressive enhancement of server-rendered pages
Integration with existing applications or other frameworks
Widget development where you control specific sections of a page
Content Management Systems where you want to add interactivity to generated content
Legacy application modernization done incrementally
The startup sections showed how to start Aurelia for empty elements. Enhancement lets you work with existing DOM trees instead.
Understanding What Gets Enhanced
When you enhance an element, Aurelia treats that element as if it were the template of a custom element. The existing HTML becomes the "template" and your component object becomes the "view model" that provides data and behavior.
Basic Enhancement Syntax
// Using the convenience method (recommended)
const enhanceRoot = await Aurelia.enhance({
host: document.querySelector('#my-content'),
component: { message: 'Hello World' }
});
// Using instance method
const au = new Aurelia();
const enhanceRoot = await au.enhance({
host: document.querySelector('#my-content'),
component: { message: 'Hello World' }
});
Component Types: Classes, Instances, or Objects
You can enhance with three different component types:
// 1. Plain object (most common for simple cases)
const enhanceRoot = await Aurelia.enhance({
host: element,
component: {
message: 'Hello',
items: [1, 2, 3],
handleClick() {
console.log('Clicked!');
}
}
});
// 2. Class instance (when you need constructor logic)
class MyViewModel {
message = 'Hello';
constructor() {
// initialization logic
}
}
const enhanceRoot = await Aurelia.enhance({
host: element,
component: new MyViewModel()
});
// 3. Custom element class (for reusable components)
@customElement({ name: 'my-widget' })
class MyWidget {
@bindable message: string;
}
const enhanceRoot = await Aurelia.enhance({
host: element,
component: MyWidget
});
Key Enhancement Concepts
Existing DOM is preserved: Enhancement doesn't replace your HTML - it makes it interactive
Existing event handlers remain: Any JavaScript event listeners you've already attached stay functional
Manual lifecycle management: You're responsible for calling
deactivate()
when doneTemplate compilation: Aurelia compiles the existing HTML for bindings and directives
Proper Cleanup
Always clean up enhanced content to prevent memory leaks:
const enhanceRoot = await Aurelia.enhance({ host, component });
// Later, when you're done:
await enhanceRoot.deactivate();
Practical Enhancement Examples
Server-Rendered Content Enhancement
Suppose your server renders this HTML:
<!-- Server-rendered content -->
<div id="user-profile">
<h2>Welcome back!</h2>
<div class="stats">
<span>Loading user data...</span>
</div>
<button id="refresh-btn">Refresh</button>
</div>
You can enhance it to make it interactive:
import Aurelia from 'aurelia';
// Your existing server-rendered element
const profileElement = document.querySelector('#user-profile');
// Enhance with Aurelia interactivity
const enhanceRoot = await Aurelia.enhance({
host: profileElement,
component: {
username: 'Loading...',
loginCount: 0,
async created() {
// Load user data when component initializes
const userData = await fetch('/api/user/profile').then(r => r.json());
this.username = userData.username;
this.loginCount = userData.loginCount;
},
refreshData() {
this.created(); // Reload data
}
}
});
// Update your HTML to use bindings:
// <h2>Welcome back, ${username}!</h2>
// <div class="stats">
// <span>Login count: ${loginCount}</span>
// </div>
// <button click.delegate="refreshData()">Refresh</button>
Widget Integration Example
Create interactive widgets within existing pages:
<!-- Existing page content -->
<div class="article">
<h1>My Blog Post</h1>
<p>Some content...</p>
<!-- Widget placeholder -->
<div id="comment-widget">
<h3>Comments</h3>
<div class="loading">Loading comments...</div>
</div>
</div>
// Enhance just the comment widget
const commentWidget = document.querySelector('#comment-widget');
const enhanceRoot = await Aurelia.enhance({
host: commentWidget,
component: {
comments: [],
newComment: '',
async created() {
this.comments = await this.loadComments();
},
async loadComments() {
return fetch('/api/comments/123').then(r => r.json());
},
async addComment() {
if (!this.newComment.trim()) return;
await fetch('/api/comments', {
method: 'POST',
body: JSON.stringify({ text: this.newComment }),
headers: { 'Content-Type': 'application/json' }
});
this.newComment = '';
this.comments = await this.loadComments();
}
}
});
// Update HTML to:
// <div id="comment-widget">
// <h3>Comments (${comments.length})</h3>
// <div repeat.for="comment of comments">
// <p>${comment.text}</p>
// </div>
// <div>
// <input value.bind="newComment" placeholder="Add comment...">
// <button click.delegate="addComment()">Post</button>
// </div>
// </div>
Dynamic Content Enhancement
Enhancement is perfect for content that gets added to the page after initial load.
Enhancing Dynamically Loaded Content
import { Aurelia, resolve } from 'aurelia';
export class DynamicContentComponent {
private enhancedRoots: Array<any> = [];
constructor(private au = resolve(Aurelia)) {}
async loadMoreContent() {
// Load HTML from server
const response = await fetch('/api/content/next-page');
const htmlContent = await response.text();
// Create container for new content
const container = document.createElement('div');
container.innerHTML = htmlContent;
document.querySelector('#content-area').appendChild(container);
// Enhance the new content
const enhanceRoot = await this.au.enhance({
host: container,
component: {
currentUser: this.currentUser,
likePost: (postId) => this.likePost(postId),
sharePost: (postId) => this.sharePost(postId)
}
});
// Keep track for cleanup
this.enhancedRoots.push(enhanceRoot);
}
// Clean up when component is destroyed
async unbinding() {
for (const root of this.enhancedRoots) {
await root.deactivate();
}
this.enhancedRoots = [];
}
}
Enhancing Modal or Dialog Content
export class ModalService {
private currentModal: any = null;
async showModal(contentHtml: string, viewModel: any) {
// Create modal element
const modal = document.createElement('div');
modal.className = 'modal';
modal.innerHTML = `
<div class="modal-content">
<button class="close" click.delegate="closeModal()">×</button>
${contentHtml}
</div>
`;
document.body.appendChild(modal);
// Enhance the modal content
this.currentModal = await Aurelia.enhance({
host: modal,
component: {
...viewModel,
closeModal: () => this.closeModal()
}
});
}
async closeModal() {
if (this.currentModal) {
await this.currentModal.deactivate();
document.querySelector('.modal')?.remove();
this.currentModal = null;
}
}
}
Advanced Enhancement Patterns
Using Custom Containers
When you need specific services or configurations for enhanced content:
import { DI, Registration } from '@aurelia/kernel';
import { LoggerConfiguration, LogLevel } from 'aurelia';
// Create custom container for widget
const widgetContainer = DI.createContainer()
.register(
Registration.singleton('ApiService', MyApiService),
LoggerConfiguration.create({ level: LogLevel.debug })
);
const enhanceRoot = await Aurelia.enhance({
host: document.querySelector('#my-widget'),
component: MyWidget,
container: widgetContainer // Use custom container
});
Lifecycle Hooks in Enhanced Components
Enhanced components support all standard Aurelia lifecycle hooks:
const enhanceRoot = await Aurelia.enhance({
host: element,
component: {
data: null,
// Called when component is being set up
created() {
console.log('Component created');
},
// Called before data binding starts
binding() {
console.log('Starting data binding');
},
// Called after data binding completes
bound() {
console.log('Data binding complete');
},
// Called when component is being attached to DOM
attaching() {
console.log('Attaching to DOM');
},
// Called after component is attached to DOM
attached() {
console.log('Attached to DOM - ready for user interaction');
// Good place for focus, animations, etc.
},
// Called when component is being removed
detaching() {
console.log('Detaching from DOM');
},
// Called when data bindings are being torn down
unbinding() {
console.log('Unbinding data');
// Cleanup subscriptions, timers, etc.
}
}
});
Common Enhancement Patterns
Progressive Enhancement Checklist
Identify enhancement targets: Elements that need interactivity
Preserve existing functionality: Don't break existing event handlers
Plan your data flow: How will data get to enhanced components?
Handle cleanup: Always deactivate when done
Test without JavaScript: Ensure basic functionality works without enhancement
Best Practices
Start small: Enhance specific widgets before entire sections
Use meaningful component objects: Include methods and properties that make sense
Handle errors gracefully: Enhancement might fail if DOM structure changes
Document what gets enhanced: Make it clear to other developers
Consider performance: Don't enhance too many elements at once
When NOT to Use Enhancement
New applications: Use regular
Aurelia.app()
for greenfield projectsFull page control: When you control the entire page, standard app startup is simpler
Simple static content: If content doesn't need interactivity
Performance critical sections: Enhancement has overhead compared to pre-compiled templates
Enhancement shines when you need to add Aurelia's power to existing content without rebuilding everything from scratch.
Last updated
Was this helpful?