CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-odoo--owl

Odoo Web Library (OWL) is a modern, lightweight TypeScript UI framework for building reactive web applications with components, templates, and state management.

Overview
Eval results
Files

lifecycle.mddocs/

Lifecycle Hooks

Component lifecycle management hooks for handling mounting, updating, rendering, and cleanup operations.

Capabilities

onWillStart

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;
      }
    });
  }
}

onMounted

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);
  }
}

onWillUpdateProps

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;
        }
      }
    });
  }
}

onWillRender

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);
    });
  }
}

onRendered

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;
  }
}

onWillPatch

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;
      }
    });
  }
}

onPatched

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();
        }
      }
    });
  }
}

onWillUnmount

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()
      }));
    });
  }
}

onWillDestroy

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();
      }
    });
  }
}

onError

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

docs

app-components.md

blockdom.md

hooks.md

index.md

lifecycle.md

reactivity.md

templates.md

utils-validation.md

tile.json