Frameworks for modern web development must be quick, responsive, and simple to understand. In response to this, Odoo created its own JavaScript framework, called OWL (Odoo Web Library).
Understanding the structure and lifecycle of OWL components is essential whether you're developing a unique Odoo module, expanding pre-existing views, or developing advanced client-side interfaces. To help you create better, more dependable Odoo frontends, we'll go into great detail about the structure of an OWL component and go over each lifecycle hook with clear examples in this blog.
What is an OWL Component?
An OWL component is a JavaScript class that extends Component. It pairs a template (QWeb) with logic and manages its own reactive state. A parent component can render a child component, making it easier to build advanced UIs from small pieces.
Here's what a basic Component Structure looks like:
import { Component, xml } from "@odoo/owl";
export class HelloWorld extends Component {
static template = xml`
<div>Hello, OWL!</div>
`;
}Complete Component Structure
import { Component, useState, useRef, onMounted, onWillUnmount, xml } from "@odoo/owl";
export class UserCard extends Component {
static template = xml`
<div class="user-card">
<h2 t-esc="props.username"/>
<p>Messages: <t t-esc="state.messageCount"/></p>
<button t-on-click="loadMessages">Refresh</button>
</div>
`;
static props = {
username: { type: String },
userId: { type: Number },
};
static components = {}; /* child components */
setup() {
this.state = useState({ messageCount: 0, loading: false });
this.containerRef = useRef("container");
onMounted(() => {
console.log("UserCard mounted!");
this.loadMessages();
});
onWillUnmount(() => {
console.log("UserCard is being destroyed.");
});
}
/* Methods or Functions */
async loadMessages() {
this.state.loading = true;
this.state.messageCount = await
fetchMessageCount(this.props.userId);
this.state.loading = false;
}
}1.Template
The template defines what gets rendered in the DOM. OWL templates use QWeb Odoo's XML templating engine with directives like t-if, t-foreach, and t-esc.
You can define templates in two ways:
Inline (using XML tag helper):
static template = xml`
<div t-if="state.visible">
<t t-foreach="props.items" t-as="item" t-key="item.id">
<span t-esc="item.name"/>
</t>
</div>
`;
External XML file:
<!-- my_module/static/src/components/my_component.xml -->
<templates>
<t t-name="my_module.MyComponent">
<div class="my-component">
<h1 t-esc="props.title"/>
</div>
</t>
</templates>
<!-- my_module/static/src/components/my_component.js -->
static template = "my_module.MyComponent";
2. Props
The inputs a parent provides to a child component are known as props.
static props = {
title: { type: String },
count: { type: Number, optional: true },
onSave: { type: Function },
tags: { type: Array },
};
static defaultProps = {
count: 0,
};The child component contains read only props. Components can call callback functions passed as props or emit custom events to communicate back to the parent.
3. setup()
In OWL, everything starts in setup(). It follows a hooks-based pattern similar to React, meaning all state initialization, service injections, and lifecycle hooks must be declared within the setup() function.
setup() {
// State
this.state = useState({ count: 0 });
// Services
this.orm = useService("orm");
this.notification = useService("notification");
// DOM ref
this.inputRef = useRef("myInput");
// Lifecycle hooks
onMounted(() => { /* ... */ });
onWillUnmount(() => { /* ... */ });
}LifeCycle Hooks
OWL offers a comprehensive set of lifecycle hooks that allow you to interact with every phase of a component’s life, from its creation all the way to its destruction. Here’s a full walkthrough.
onwillStart - Before Initial Render
This hook executes once, asynchronously, before the component is rendered for the first time. It’s especially useful for tasks like loading data from a server or fetching external assets. By using onWillStart, you can ensure that all required data and resources are ready before the component appears on screen, resulting in a smoother and more seamless user experience.
import { onWillStart } from "@odoo/owl";
setup() {
onWillStart(async () => {
this.data = await fetchData();
});
}The component won't render until this async function completes. Use it for critical data fetching.
onMounted - After First Render
The mounted hook runs after a component’s DOM has been inserted into the page. It’s the right place for DOM manipulation, initializing third-party libraries, starting subscriptions, adding event listeners, or taking measurements. This hook is triggered after the first render and every time the component is attached to the DOM, ensuring the component is fully integrated and ready for user interaction.
import { onMounted } from "@odoo/owl";
setup() {
onMounted(() => {
window.addEventListener("scroll", this.handleScroll);
});
}onWillRender - Just Before Every Render
The onWillRender hook runs synchronously just before the component’s template function executes, both on the initial render and on every subsequent re-render. It is called in a parent first, then children order. This hook allows developers to run code right before rendering, making it useful for adjusting component data, preparing variables, or performing last-minute calculations needed for the template.
import { onWillRender } from "@odoo/owl";
setup() {
onWillRender(() => {
console.log("Component about to render");
});
}onRendered - Just After Every Render
The rendered hook runs synchronously right after the component’s template function executes, both on the initial render and on every re-render. At this stage, the actual DOM may not yet exist (during the first render) or may not have been updated, as DOM updates occur in the next animation frame once all components are ready. This hook allows developers to run code immediately after rendering logic completes, making it useful for handling post-render operations or preparing interactions that depend on the component’s rendered state.
import { onRendered } from "@odoo/owl";
setup() {
onRendered(() => {
console.log("Component rendered");
});
}onWillUpdateProps - Before Props Change
The onWillUpdateProps hook runs before a component receives new props from its parent. It is asynchronous and provides the ideal place to respond to incoming prop changes, such as resetting state, fetching new data, or making API calls based on an updated ID. By using onWillUpdateProps, developers can handle any logic that depends on the new props before the component re-renders, ensuring everything is properly prepared in advance.
import { onWillUpdateProps } from "@odoo/owl";
setup() {
onWillUpdateProps(async (nextProps) => {
this.data = await fetchData(nextProps.id);
});
}onWillPatch - Before DOM Update
The onWillPatch hook runs synchronously just before the DOM is patched due to a state or props change. It allows developers to read and capture information from the current DOM state, such as scroll positions, element sizes, or other measurements, before the update takes place. This makes it especially useful for preserving or reacting to DOM related details during re-renders.
import { onWillPatch } from "@odoo/owl";
setup() {
onWillPatch(() => {
this.scrollState = this.getScrollSTate();
});
}onPatched - After DOM Update
The patched hook runs synchronously after the DOM has been updated following a state or props change. It pairs well with onWillPatch, allowing developers to restore scroll positions, reapply DOM state, or update third-party libraries that depend on DOM changes. Triggered after every successful DOM update, this hook provides an opportunity to interact with the updated elements and ensure the component’s visual output stays fully in sync with its internal state and the surrounding DOM.
import { onPatched } from "@odoo/owl";
setup() {
onPatched(() => {
const element = document.getElementById("myElement");
element.classList.add("highlight");
});
}onWillUnmount - Before Destruction
The onWillUnmount hook runs once, just before a component is removed from the DOM. It’s primarily used for cleanup tasks such as clearing timers, removing event listeners, canceling pending requests, closing WebSocket connections, or destroying third-party library instances. By using willUnmount, developers can ensure the component is properly cleaned up, and no unnecessary resources remain after it is detached from the DOM.
import { onMounted, onWillUnmount } from "@odoo/owl";
setup() {
onMounted(() => {
window.addEventListener("resize", this.handleResize);
});
onWillUnmount(() => {
window.removeEventListener("resize", this.handleResize);
});
}onWillDestroy - Final Cleanup
The onWillDestroy hook is similar to willUnmount, but it runs slightly later in the teardown process after the component has been removed from the virtual DOM tree. It is always called when a component is destroyed, whether or not it was ever mounted. This makes it a reliable place to perform final cleanup tasks such as releasing resources, clearing timers, or handling any remaining teardown logic. In most cases, however, onWillUnmount is sufficient for typical cleanup needs.
import { onWillDestroy } from "@odoo/owl";
setup() {
onWillDestroy(() => {
// Clean up resources
this.cleanup();
});
}onError - Error Boundary
The onError hook catches errors thrown by child components, turning a component into an error boundary. It allows developers to gracefully handle failures within its subtree by implementing custom error-handling logic such as displaying a fallback UI, showing error messages, logging issues for debugging, or attempting recovery. By using onError, applications can remain stable and provide a smoother user experience even when unexpected errors occur.
import { onError } from "@odoo/owl";
setup() {
onError((error) => {
console.error("Component error:", error);
// Perform error handling logic
});
}OWL represents a significant leap forward for Odoo's frontend architecture. By embracing a component driven, hooks based model, it makes building and maintaining complex UIs far more intuitive than the old Widget system ever could.
Understanding the component structure templates, props, setup(), and child components gives you a solid foundation. Mastering the lifecycle hooks gives you precise control over every stage of your component's existence.
To read more about An Overview of OWL Components in Odoo 18, refer to our blog An Overview of OWL Components in Odoo 18.