Odoo Web Library (OWL) is a modern, lightweight TypeScript UI framework for building reactive web applications with components, templates, and state management.
Component lifecycle management hooks for handling mounting, updating, rendering, and cleanup operations.
Called before the component starts its initial rendering process.
/**
* Hook called before component starts rendering
* Supports both synchronous and asynchronous operations
* @param callback - Function to execute, can return Promise
*/
function onWillStart(callback: () => void | Promise<void>): void;Usage Examples:
import { Component, xml, onWillStart, useState } from "@odoo/owl";
class DataLoader extends Component {
static template = xml`
<div>
<div t-if="state.loading">Loading...</div>
<div t-else="">
<h2><t t-esc="state.data.title" /></h2>
<p><t t-esc="state.data.description" /></p>
</div>
</div>
`;
setup() {
this.state = useState({
loading: true,
data: null
});
// Async data loading before component renders
onWillStart(async () => {
try {
const response = await fetch("/api/data");
this.state.data = await response.json();
} catch (error) {
console.error("Failed to load data:", error);
this.state.data = { title: "Error", description: "Failed to load" };
} finally {
this.state.loading = false;
}
});
}
}Called after the component has been mounted to the DOM.
/**
* Hook called after component is mounted to DOM
* @param callback - Function to execute after mounting
*/
function onMounted(callback: () => void): void;Usage Examples:
import { Component, xml, onMounted, useRef } from "@odoo/owl";
class ChartComponent extends Component {
static template = xml`
<div>
<canvas t-ref="canvas" width="400" height="300"></canvas>
</div>
`;
setup() {
this.canvasRef = useRef("canvas");
onMounted(() => {
// DOM is ready, we can manipulate elements
const canvas = this.canvasRef.el;
const ctx = canvas.getContext("2d");
// Draw chart
this.drawChart(ctx);
// Focus management
if (this.props.autoFocus) {
canvas.focus();
}
// Third-party library initialization
this.chartInstance = new ThirdPartyChart(canvas, this.props.chartData);
});
}
drawChart(ctx) {
// Chart drawing logic
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 100, 50);
}
}Called before component props are updated.
/**
* Hook called before props update
* @param callback - Function receiving the next props, can return Promise
*/
function onWillUpdateProps(callback: (nextProps: any) => void | Promise<void>): void;Usage Examples:
import { Component, xml, onWillUpdateProps, useState } from "@odoo/owl";
class UserProfile extends Component {
static template = xml`
<div>
<img t-att-src="state.avatar" alt="User avatar" />
<h2><t t-esc="props.user.name" /></h2>
<div t-if="state.loading">Loading profile...</div>
</div>
`;
setup() {
this.state = useState({
avatar: null,
loading: false
});
// Load avatar when user changes
onWillUpdateProps(async (nextProps) => {
if (nextProps.user.id !== this.props.user.id) {
this.state.loading = true;
try {
const response = await fetch(`/api/users/${nextProps.user.id}/avatar`);
this.state.avatar = await response.text();
} finally {
this.state.loading = false;
}
}
});
}
}Called before each rendering cycle.
/**
* Hook called before each render
* @param callback - Function to execute before rendering
*/
function onWillRender(callback: () => void): void;Usage Examples:
import { Component, xml, onWillRender, useState } from "@odoo/owl";
class AnimatedComponent extends Component {
static template = xml`
<div t-att-class="state.cssClass">
<p>Render count: <t t-esc="state.renderCount" /></p>
</div>
`;
setup() {
this.state = useState({
renderCount: 0,
cssClass: ""
});
onWillRender(() => {
// Increment render counter
this.state.renderCount++;
// Prepare animations or computations
this.state.cssClass = this.state.renderCount % 2 === 0 ? "even-render" : "odd-render";
console.log("About to render, count:", this.state.renderCount);
});
}
}Called after each rendering cycle completes.
/**
* Hook called after each render completes
* @param callback - Function to execute after rendering
*/
function onRendered(callback: () => void): void;Usage Examples:
import { Component, xml, onRendered, useRef, useState } from "@odoo/owl";
class ScrollableList extends Component {
static template = xml`
<div t-ref="container" class="scrollable-list">
<div t-foreach="state.items" t-as="item" t-key="item.id">
<t t-esc="item.text" />
</div>
</div>
`;
setup() {
this.containerRef = useRef("container");
this.state = useState({
items: [],
shouldScrollToBottom: false
});
onRendered(() => {
// Scroll to bottom after new items are added
if (this.state.shouldScrollToBottom && this.containerRef.el) {
this.containerRef.el.scrollTop = this.containerRef.el.scrollHeight;
this.state.shouldScrollToBottom = false;
}
// Update third-party library after DOM changes
if (this.chartInstance) {
this.chartInstance.update();
}
});
}
addItem(text) {
this.state.items.push({ id: Date.now(), text });
this.state.shouldScrollToBottom = true;
}
}Called before DOM patching occurs.
/**
* Hook called before DOM patch
* @param callback - Function to execute before patching
*/
function onWillPatch(callback: () => void): void;Usage Examples:
import { Component, xml, onWillPatch, useRef } from "@odoo/owl";
class VideoPlayer extends Component {
static template = xml`
<video t-ref="video" t-att-src="props.src" controls="true" />
`;
setup() {
this.videoRef = useRef("video");
onWillPatch(() => {
// Save current playback state before DOM changes
const video = this.videoRef.el;
if (video) {
this.savedTime = video.currentTime;
this.wasPaused = video.paused;
}
});
}
}Called after DOM patching completes.
/**
* Hook called after DOM patch completes
* @param callback - Function to execute after patching
*/
function onPatched(callback: () => void): void;Usage Examples:
import { Component, xml, onPatched, onWillPatch, useRef } from "@odoo/owl";
class VideoPlayer extends Component {
static template = xml`
<video t-ref="video" t-att-src="props.src" controls="true" />
`;
setup() {
this.videoRef = useRef("video");
onWillPatch(() => {
// Save state before patch
const video = this.videoRef.el;
if (video) {
this.savedTime = video.currentTime;
this.wasPaused = video.paused;
}
});
onPatched(() => {
// Restore state after patch
const video = this.videoRef.el;
if (video && this.savedTime !== undefined) {
video.currentTime = this.savedTime;
if (!this.wasPaused) {
video.play();
}
}
});
}
}Called before the component is unmounted from the DOM.
/**
* Hook called before component unmounts from DOM
* @param callback - Function to execute before unmounting
*/
function onWillUnmount(callback: () => void): void;Usage Examples:
import { Component, xml, onWillUnmount, onMounted } from "@odoo/owl";
class TimerComponent extends Component {
static template = xml`
<div>Timer: <t t-esc="state.seconds" />s</div>
`;
setup() {
this.state = useState({ seconds: 0 });
onMounted(() => {
// Start timer
this.interval = setInterval(() => {
this.state.seconds++;
}, 1000);
});
onWillUnmount(() => {
// Cleanup timer
if (this.interval) {
clearInterval(this.interval);
}
// Save state to localStorage
localStorage.setItem("timerState", JSON.stringify({
seconds: this.state.seconds,
timestamp: Date.now()
}));
});
}
}Called before the component is completely destroyed.
/**
* Hook called before component is destroyed
* @param callback - Function to execute before destruction
*/
function onWillDestroy(callback: () => void): void;Usage Examples:
import { Component, xml, onWillDestroy, onMounted } from "@odoo/owl";
class WebSocketComponent extends Component {
static template = xml`
<div>
<p>Status: <t t-esc="state.status" /></p>
<p>Messages: <t t-esc="state.messages.length" /></p>
</div>
`;
setup() {
this.state = useState({
status: "disconnected",
messages: []
});
onMounted(() => {
// Initialize WebSocket connection
this.ws = new WebSocket("ws://localhost:8080");
this.ws.onopen = () => {
this.state.status = "connected";
};
this.ws.onmessage = (event) => {
this.state.messages.push(JSON.parse(event.data));
};
this.ws.onclose = () => {
this.state.status = "disconnected";
};
});
onWillDestroy(() => {
// Close WebSocket connection
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.close();
}
// Unsubscribe from global services
GlobalEventBus.off("data-update", this.handleDataUpdate);
// Clean up any pending async operations
if (this.pendingRequest) {
this.pendingRequest.abort();
}
});
}
}Called when an error occurs in the component or its children.
/**
* Hook called when component or child errors occur
* @param callback - Function to handle errors
*/
function onError(callback: (error: Error) => void): void;Usage Examples:
import { Component, xml, onError, useState } from "@odoo/owl";
class ErrorBoundary extends Component {
static template = xml`
<div>
<div t-if="state.hasError" class="error-message">
<h3>Something went wrong!</h3>
<p><t t-esc="state.errorMessage" /></p>
<button t-on-click="resetError">Try Again</button>
</div>
<div t-else="">
<t t-slot="default" />
</div>
</div>
`;
setup() {
this.state = useState({
hasError: false,
errorMessage: ""
});
onError((error) => {
console.error("Component error caught:", error);
this.state.hasError = true;
this.state.errorMessage = error.message;
// Report error to monitoring service
this.reportError(error);
});
}
resetError() {
this.state.hasError = false;
this.state.errorMessage = "";
}
reportError(error) {
// Send to error tracking service
fetch("/api/errors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
})
});
}
}Install with Tessl CLI
npx tessl i tessl/npm-odoo--owl