You are on page 1of 56

OWL Documentation 

https://github.com/odoo/owl/blob/ma
ster/doc/readme.md
Learning Owl

Are you new to Owl? This is the place to start!

 Tutorial: create a TodoList application


 Quick Overview
 How to start an Owl project
 How to test Components
 How to write Single File Components
 How to write debug Owl applications

Reference

You will find here a complete reference of every feature, class or object provided by
Owl.

 Animations
 Browser
 Component
 Content
 Concurrency Model
 Configuration
 Context
 Environment
 Event Bus
 Event Handling
 Error Handling
 Hooks
 Mounting a component
 Miscellaneous Components
 Observer
 Props
 Props Validation
 QWeb Templating Language
 QWeb Engine
 Router
 Store
 Slots
 Tags
 Utils

Other Topics

This section provides miscellaneous document that explains some topics which
cannot be considered either a tutorial, or reference documentation.

 Owl architecture: the Virtual DOM


 Owl architecture: the rendering pipeline
 Comparison with React/Vue
 Why did Odoo built Owl?

Found an issue in the documentation? A broken link? Some outdated information?


Please open an issue or submit a PR!

OWL Tutorial: TodoApp 🦉


For this tutorial, we will build a very simple Todo list application. The app should
satisfy the following requirements:

 let the user create and remove tasks


 tasks can be marked as completed
 tasks can be filtered to display active/completed tasks

This project will be an opportunity to discover and learn some important Owl
concepts, such as components, store, and how to organize an application.
Content

1. Setting up the project


2. Adding a first component
3. Displaying a list of tasks
4. Layout: some basic css
5. Extracting Task as a subcomponent
6. Adding tasks (part 1)
7. Adding tasks (part 2)
8. Toggling tasks
9. Deleting tasks
10. Using a store
11. Saving tasks in local storage
12. Filtering tasks
13. The Final Touch
14. Final Code

1. Setting up the project

For this tutorial, we will do a very simple project, with static files and no additional
tooling. The first step is to create the following file structure:

todoapp/
index.html
app.css
app.js
owl.js
The entry point for this application is the file index.html, which should have the
following content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>OWL Todo App</title>
<link rel="stylesheet" href="app.css" />
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
Then, app.css can be left empty for now. It will be useful later on to style our
application. app.js is where we will write all our code. For now, let's just put the
following code:
(function () {
console.log("hello owl", owl.__info__.version);
})();
Note that we put everything inside an immediately executed function to avoid
leaking anything to the global scope.

Finally, owl.js should be the last version downloaded from the Owl repository (you
can use owl.min.js if you prefer). Be aware that you should download
the owl.iife.js or owl.iife.min.js, because these files are built to run directly on the
browser (other files such as owl.cjs.js are built to be bundled by other tools).
Now, the project should be ready. Loading the index.html file into a browser should
show an empty page, with the title Owl Todo App, and it should log a message such
as hello owl 1.0.0 in the console.

2. Adding a first component

An Owl application is made out of components, with a single root component. Let us
start by defining an App component. Replace the content of the function in app.js by
the following code:
const { Component, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;

// Owl Components
class App extends Component {
static template = xml`<div>todo app</div>`;
}

// Setup code
function setup() {
mount(App, { target: document.body });
}

whenReady(setup);
Now, reloading the page in a browser should display a message.

The code is pretty simple, but let us explain the last line in more detail. The browser
tries to execute the javascript code in app.js as quickly as possible, and it could
happen that the DOM is not ready yet when we try to mount the App component. To
avoid this situation, we use the whenReady helper to delay the execution of
the setup function until the DOM is ready.
Note 1: in a larger project, we would split the code in multiple files, with components
in a sub folder, and a main file that would initialize the application. However, this is a
very small project, and we want to keep it as simple as possible.

Note 2: this tutorial uses the static class field syntax. This is not yet supported by all
browsers. Most real projects will transpile their code, so this is not a problem, but for
this tutorial, if you need the code to work on every browser, you will need to translate
each static keyword to an assignation to the class:
class App extends Component {}
App.template = xml`<div>todo app</div>`;
Note 3: writing inline templates with the xml helper is nice, but there is no syntax
highlighting, and this makes it very easy to have malformed xml. Some editors
support syntax highlighting for this situation. For example, VS Code has an
addon Comment tagged template, which, if installed, will properly display tagged
templates:
static template = xml /* xml */`<div>todo app</div>`;
Note 4: Large applications will probably want to be able to translate templates. Using
inline templates makes it slightly harder, since we need additional tooling to extract
the xml from the code, and to replace it with the translated values.

3. Displaying a list of tasks

Now that the basics are done, it is time to start thinking about tasks. To accomplish
what we need, we will keep track of the tasks as an array of objects with the following
keys:

 id: a number. It is extremely useful to have a way to uniquely identify tasks. Since the
title is something created/edited by the user, it offers no guarantee that it is unique.
So, we will generate a unique id number for each task.
 title: a string, to explain what the task is about.
 isCompleted: a boolean, to keep track of the status of the task

Now that we decided on the internal format of the state, let us add some demo data
and a template to the App component:
class App extends Component {
static template = xml/* xml */ `
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<div class="task">
<input type="checkbox" t-att-checked="task.isCompleted"/>
<span><t t-esc="task.title"/></span>
</div>
</t>
</div>`;

tasks = [
{
id: 1,
title: "buy milk",
isCompleted: true,
},
{
id: 2,
title: "clean house",
isCompleted: false,
},
];
}
The template contains a t-foreach loop to iterate through the tasks. It can find
the tasks list from the component, since the component is the rendering context.
Note that we use the id of each task as a t-key, which is very common. There are two
css classes: task-list and task, that we will use in the next section.
Finally, notice the use of the t-att-checked attribute: prefixing an attribute by t-
att makes it dynamic. Owl will evaluate the expression and set it as the value of the
attribute.

4. Layout: some basic css

So far, our task list looks quite bad. Let us add the following to app.css:
.task-list {
width: 300px;
margin: 50px auto;
background: aliceblue;
padding: 10px;
}

.task {
font-size: 18px;
color: #111111;
}
This is better. Now, let us add an extra feature: completed tasks should be styled a
little differently, to make it clearer that they are not as important. To do that, we will
add a dynamic css class on each task:

<div class="task" t-att-class="task.isCompleted ? 'done' : ''">


.task.done {
opacity: 0.7;
}
Notice that we have here another use of a dynamic attribute.

5. Extracting Task as a subcomponent

It is now clear that there should be a Task component to encapsulate the look and
behavior of a task.
This Task component will display a task, but it cannot own the state of the task: a
piece of data should only have one owner. Doing otherwise is asking for trouble. So,
the Task component will get its data as a prop. This means that the data is still owned
by the App component, but can be used by the Task component (without modifying
it).
Since we are moving code around, it is a good opportunity to refactor the code a
little bit:

// -------------------------------------------------------------------------
// Task Component
// -------------------------------------------------------------------------
const TASK_TEMPLATE = xml /* xml */`
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"/>
<span><t t-esc="props.task.title"/></span>
</div>`;
class Task extends Component {
static template = TASK_TEMPLATE;
static props = ["task"];
}

// -------------------------------------------------------------------------
// App Component
// -------------------------------------------------------------------------
const APP_TEMPLATE = xml /* xml */`
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>`;

class App extends Component {


static template = APP_TEMPLATE;
static components = { Task };

tasks = [
...
];
}

// -------------------------------------------------------------------------
// Setup code
// -------------------------------------------------------------------------
function setup() {
owl.config.mode = "dev";
mount(App, { target: document.body });
}

whenReady(setup);
A lot of stuff happened here:

 first, we have now a sub component Task, defined on top of the file,


 whenever we define a sub component, it needs to be added to the
static components key of its parent, so Owl can get a reference to it,
 the templates have been extracted out of the components, to make it easier to
differentiate the "view/template" code from the "script/behavior" code,
 the Task component has a props key: this is only useful for validation purpose. It says
that each Task should be given exactly one prop, named task. If this is not the case,
Owl will throw an error. This is extremely useful when refactoring components
 finally, to activate the props validation, we need to set Owl's mode to dev. This is done
in the setup function. Note that this should be removed when an app is used in a real
production environment, since dev mode is slightly slower, due to extra checks and
validations.
6. Adding tasks (part 1)

We still use a list of hardcoded tasks. It's really time to give the user a way to add
tasks himself. The first step is to add an input to the App component. But this input
will be outside of the task list, so we need to adapt App template, js, and css:
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask"/>
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>
</div>
addTask(ev) {
// 13 is keycode for ENTER
if (ev.keyCode === 13) {
const title = ev.target.value.trim();
ev.target.value = "";
console.log('adding task', title);
// todo
}
}
.todo-app {
width: 300px;
margin: 50px auto;
background: aliceblue;
padding: 10px;
}

.todo-app > input {


display: block;
margin: auto;
}

.task-list {
margin-top: 8px;
}
We now have a working input, which log to the console whenever the user adds a
task. Notice that when you load the page, the input is not focused. But adding tasks is
a core feature of a task list, so let us make it as fast as possible by focusing the input.

Since App is a component, it has a mounted lifecycle method that we can implement.


We will also need to get a reference to the input, by using the t-ref directive with
the useRef hook:
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
// on top of file:
const { useRef } = owl.hooks;
// in App
inputRef = useRef("add-input");

mounted() {
this.inputRef.el.focus();
}
The inputRef is defined as a class field, so it is equivalent to defining it in the
constructor. It simply instructs Owl to keep a reference to anything with the
corresponding t-ref keyword. We then implement the mounted lifecycle method,
where we now have an active reference that we can use to focus the input.

7. Adding tasks (part 2)

In the previous section, we did everything except implement the code that actually
create tasks! So, let us do that now.

We need a way to generate unique id numbers. To do that, we will simply add


a nextId number in App. At the same time, let us remove the demo tasks in App:
nextId = 1;
tasks = [];
Now, the addTask method can be implemented:
addTask(ev) {
// 13 is keycode for ENTER
if (ev.keyCode === 13) {
const title = ev.target.value.trim();
ev.target.value = "";
if (title) {
const newTask = {
id: this.nextId++,
title: title,
isCompleted: false,
};
this.tasks.push(newTask);
}
}
}
This almost works, but if you test it, you will notice that no new task is ever displayed
when the user press Enter. But if you add a debugger or a console.log statement, you
will see that the code is actually running as expected. The problem is that Owl has no
way of knowing that it needs to rerender the user interface. We can fix the issue by
making tasks reactive, with the useState hook:
// on top of the file
const { useRef, useState } = owl.hooks;

// replace the task definition in App with the following:


tasks = useState([]);
It now works as expected!

8. Toggling tasks

If you tried to mark a task as completed, you may have noticed that the text did not
change in opacity. This is because there is no code to modify the isCompleted flag.
Now, this is an interesting situation: the task is displayed by the Task component, but
it is not the owner of its state, so it cannot modify it. Instead, we want to
communicate the request to toggle a task to the App component. Since App is a parent
of Task, we can trigger an event in Task and listen for it in App.
In Task, change the input to:
<input type="checkbox" t-att-checked="props.task.isCompleted" t-on-
click="toggleTask"/>
and add the toggleTask method:
toggleTask() {
this.trigger('toggle-task', {id: this.props.task.id});
}
We now need to listen for that event in the App template:
<div class="task-list" t-on-toggle-task="toggleTask">
and implement the toggleTask code:
toggleTask(ev) {
const task = this.tasks.find(t => t.id === ev.detail.id);
task.isCompleted = !task.isCompleted;
}

9. Deleting tasks

Let us now add the possibility do delete tasks. To do that, we first need to add a trash
icon on each task, then we will proceed just like in the previous section.

First, let us update the Task template, css and js:


<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted" t-on-
click="toggleTask"/>
<span><t t-esc="props.task.title"/></span>
<span class="delete" t-on-click="deleteTask">🗑</span>
</div>
.task {
font-size: 18px;
color: #111111;
display: grid;
grid-template-columns: 30px auto 30px;
}

.task > input {


margin: auto;
}

.delete {
opacity: 0;
cursor: pointer;
text-align: center;
}

.task:hover .delete {
opacity: 1;
}
deleteTask() {
this.trigger('delete-task', {id: this.props.task.id});
}
And now, we need to listen to the delete-task event in App:
<div class="task-list" t-on-toggle-task="toggleTask" t-on-delete-
task="deleteTask">
deleteTask(ev) {
const index = this.tasks.findIndex(t => t.id === ev.detail.id);
this.tasks.splice(index, 1);
}

10. Using a store

Looking at the code, it is apparent that we now have code to handle tasks scattered
in more than one place. Also, it mixes UI code and business logic code. Owl has a way
to manage state separately from the user interface: a Store.
Let us use it in our application. This is a pretty large refactoring (for our application),
since it involves extracting all task related code out of the components. Here is the
new content of the app.js file:
const { Component, Store, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
const { useRef, useDispatch, useStore } = owl.hooks;

// -------------------------------------------------------------------------
// Store
// -------------------------------------------------------------------------
const actions = {
addTask({ state }, title) {
title = title.trim();
if (title) {
const task = {
id: state.nextId++,
title: title,
isCompleted: false,
};
state.tasks.push(task);
}
},
toggleTask({ state }, id) {
const task = state.tasks.find((t) => t.id === id);
task.isCompleted = !task.isCompleted;
},
deleteTask({ state }, id) {
const index = state.tasks.findIndex((t) => t.id === id);
state.tasks.splice(index, 1);
},
};
const initialState = {
nextId: 1,
tasks: [],
};

// -------------------------------------------------------------------------
// Task Component
// -------------------------------------------------------------------------
const TASK_TEMPLATE = xml/* xml */ `
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"
t-on-click="dispatch('toggleTask', props.task.id)"/>
<span><t t-esc="props.task.title"/></span>
<span class="delete" t-on-click="dispatch('deleteTask', props.task.id)">🗑
</span>
</div>`;
class Task extends Component {
static template = TASK_TEMPLATE;
static props = ["task"];
dispatch = useDispatch();
}

// -------------------------------------------------------------------------
// App Component
// -------------------------------------------------------------------------
const APP_TEMPLATE = xml/* xml */ `
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-
input"/>
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>
</div>`;

class App extends Component {


static template = APP_TEMPLATE;
static components = { Task };

inputRef = useRef("add-input");
tasks = useStore((state) => state.tasks);
dispatch = useDispatch();

mounted() {
this.inputRef.el.focus();
}

addTask(ev) {
// 13 is keycode for ENTER
if (ev.keyCode === 13) {
this.dispatch("addTask", ev.target.value);
ev.target.value = "";
}
}
}

// -------------------------------------------------------------------------
// Setup code
// -------------------------------------------------------------------------
function setup() {
owl.config.mode = "dev";
const store = new Store({ actions, state: initialState });
App.env.store = store;
mount(App, { target: document.body });
}

whenReady(setup);

11-Saving tasks in local storage

Now, our TodoApp works great, except if the user closes or refresh the browser! It is
really inconvenient to only keep the state of the application in memory. To fix this, we
will save the tasks in the local storage. With our current codebase, it is a simple
change: only the setup code needs to be updated.

function makeStore() {
const localState = window.localStorage.getItem("todoapp");
const state = localState ? JSON.parse(localState) : initialState;
const store = new Store({ state, actions });
store.on("update", null, () => {
localStorage.setItem("todoapp", JSON.stringify(store.state));
});
return store;
}

function setup() {
owl.config.mode = "dev";
const env = { store: makeStore() };
mount(App, { target: document.body, env });
}
The key point is to use the fact that the store is an EventBus which triggers
an update event whenever it is updated.

12. Filtering tasks

We are almost done, we can add/update/delete tasks. The only missing feature is the
possibility to display the task according to their completed status. We will need to
keep track of the state of the filter in App, then filter the visible tasks according to its
value.
// on top of file, readd useState:
const { useRef, useDispatch, useState, useStore } = owl.hooks;

// in App:
filter = useState({value: "all"})

get displayedTasks() {
switch (this.filter.value) {
case "active": return this.tasks.filter(t => !t.isCompleted);
case "completed": return this.tasks.filter(t => t.isCompleted);
case "all": return this.tasks;
}
}

setFilter(filter) {
this.filter.value = filter;
}
Finally, we need to display the visible filters. We can do that, and at the same time,
display the number of tasks in a small panel below the main list:

<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
<div class="task-list">
<t t-foreach="displayedTasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>
<div class="task-panel" t-if="tasks.length">
<div class="task-counter">
<t t-esc="displayedTasks.length"/>
<t t-if="displayedTasks.length lt tasks.length">
/ <t t-esc="tasks.length"/>
</t>
task(s)
</div>
<div>
<span t-foreach="['all', 'active', 'completed']"
t-as="f" t-key="f"
t-att-class="{active: filter.value===f}"
t-on-click="setFilter(f)"
t-esc="f"/>
</div>
</div>
</div>
.task-panel {
color: #0088ff;
margin-top: 8px;
font-size: 14px;
display: flex;
}

.task-panel .task-counter {
flex-grow: 1;
}

.task-panel span {
padding: 5px;
cursor: pointer;
}

.task-panel span.active {
font-weight: bold;
}
Notice here that we set dynamically the class of the filter with the object syntax: each
key is a class that we want to set if its value is truthy.

13. The Final Touch

Our list is feature complete. We can still add a few extra details to improve the user
experience.

1. Add a visual feedback when the user mouse is over a task:

.task:hover {
background-color: #def0ff;
}

2. Make the title of a task clickable, to toggle its checkbox:

<input type="checkbox" t-att-checked="props.task.isCompleted"


t-att-id="props.task.id"
t-on-click="dispatch('toggleTask', props.task.id)"/>
<label t-att-for="props.task.id"><t t-esc="props.task.title"/></label>
3. Strike the title of completed task:

.task.done label {
text-decoration: line-through;
}

Final code

Our application is now complete. It works, the UI code is well separated from the
business logic code, it is testable, all under 150 lines of code (template included!).

For reference, here is the final code:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>OWL Todo App</title>
<link rel="stylesheet" href="app.css" />
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
(function () {
const { Component, Store, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
const { useRef, useDispatch, useState, useStore } = owl.hooks;

// -------------------------------------------------------------------------
// Store
// -------------------------------------------------------------------------
const actions = {
addTask({ state }, title) {
title = title.trim();
if (title) {
const task = {
id: state.nextId++,
title: title,
isCompleted: false,
};
state.tasks.push(task);
}
},
toggleTask({ state }, id) {
const task = state.tasks.find((t) => t.id === id);
task.isCompleted = !task.isCompleted;
},
deleteTask({ state }, id) {
const index = state.tasks.findIndex((t) => t.id === id);
state.tasks.splice(index, 1);
},
};

const initialState = {
nextId: 1,
tasks: [],
};

// -------------------------------------------------------------------------
// Task Component
// -------------------------------------------------------------------------
const TASK_TEMPLATE = xml/* xml */ `
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"
t-att-id="props.task.id"
t-on-click="dispatch('toggleTask', props.task.id)"/>
<label t-att-for="props.task.id"><t t-esc="props.task.title"/></label>
<span class="delete" t-on-click="dispatch('deleteTask', props.task.id)">🗑
</span>
</div>`;

class Task extends Component {


static template = TASK_TEMPLATE;
static props = ["task"];
dispatch = useDispatch();
}

// -------------------------------------------------------------------------
// App Component
// -------------------------------------------------------------------------
const APP_TEMPLATE = xml/* xml */ `
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-
input"/>
<div class="task-list">
<Task t-foreach="displayedTasks" t-as="task" t-key="task.id"
task="task"/>
</div>
<div class="task-panel" t-if="tasks.length">
<div class="task-counter">
<t t-esc="displayedTasks.length"/>
<t t-if="displayedTasks.length lt tasks.length">
/ <t t-esc="tasks.length"/>
</t>
task(s)
</div>
<div>
<span t-foreach="['all', 'active', 'completed']"
t-as="f" t-key="f"
t-att-class="{active: filter.value===f}"
t-on-click="setFilter(f)"
t-esc="f"/>
</div>
</div>
</div>`;

class App extends Component {


static template = APP_TEMPLATE;
static components = { Task };

inputRef = useRef("add-input");
tasks = useStore((state) => state.tasks);
filter = useState({ value: "all" });
dispatch = useDispatch();
mounted() {
this.inputRef.el.focus();
}

addTask(ev) {
// 13 is keycode for ENTER
if (ev.keyCode === 13) {
this.dispatch("addTask", ev.target.value);
ev.target.value = "";
}
}

get displayedTasks() {
switch (this.filter.value) {
case "active":
return this.tasks.filter((t) => !t.isCompleted);
case "completed":
return this.tasks.filter((t) => t.isCompleted);
case "all":
return this.tasks;
}
}
setFilter(filter) {
this.filter.value = filter;
}
}

// -------------------------------------------------------------------------
// Setup code
// -------------------------------------------------------------------------
function makeStore() {
const localState = window.localStorage.getItem("todoapp");
const state = localState ? JSON.parse(localState) : initialState;
const store = new Store({ state, actions });
store.on("update", null, () => {
localStorage.setItem("todoapp", JSON.stringify(store.state));
});
return store;
}

function setup() {
owl.config.mode = "dev";
const env = { store: makeStore() };
mount(App, { target: document.body, env });
}

whenReady(setup);
})();
.todo-app {
width: 300px;
margin: 50px auto;
background: aliceblue;
padding: 10px;
}

.todo-app > input {


display: block;
margin: auto;
}

.task-list {
margin-top: 8px;
}

.task {
font-size: 18px;
color: #111111;
display: grid;
grid-template-columns: 30px auto 30px;
}

.task:hover {
background-color: #def0ff;
}

.task > input {


margin: auto;
}

.delete {
opacity: 0;
cursor: pointer;
text-align: center;
}

.task:hover .delete {
opacity: 1;
}

.task.done {
opacity: 0.7;
}
.task.done label {
text-decoration: line-through;
}

.task-panel {
color: #0088ff;
margin-top: 8px;
font-size: 14px;
display: flex;
}

.task-panel .task-counter {
flex-grow: 1;
}

.task-panel span {
padding: 5px;
cursor: pointer;
}

.task-panel span.active {
font-weight: bold;
}
Quick Overview 🦉
Owl components in an application are used to define a (dynamic) tree of
components.

Root
/ \
A B
/ \
C D
State: each component can manage its own local state. It is a simple ES6 class, there
are no special rules:

class Counter extends Component {


static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;

state = { value: 0 };

increment() {
this.state.value++;
this.render();
}
}

The example above shows a component with a local state. Note that since there is
nothing magical to the state object, we need to manually call the render function
whenever we update it. This can quickly become annoying (and not efficient if we do
it too much). There is a better way: using the useState hook, which transforms an
object into a reactive version of itself:
const { useState } = owl.hooks;

class Counter extends Component {


static template = xml`
<button t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>`;

state = useState({ value: 0 });

increment() {
this.state.value++;
}
}

Note that the t-on-click handler can even be replaced by an inline statement:


<button t-on-click="state.value++">
Props: sub components often needs some information from their parents. This is
done by adding the required information to the template. This will then be accessible
by the sub component in the props object. Note that there is an important rule here:
the information contained in the props object is not owned by the sub component,
and should never be modified.
class Child extends Component {
static template = xml`<div>Hello <t t-esc="props.name"/></div>`;
}

class Parent extends Component {


static template = xml`
<div>
<Child name="'Owl'" />
<Child name="'Framework'" />
</div>`;
static components = { Child };
}

Communication: there are multiple ways to communicate information between


components. However, the two most important ways are the following:

 from parent to children: by using props,


 from a children to one of its parent: by triggering events.

The following example illustrate both mechanisms:

class OrderLine extends Component {


static template = xml`
<div t-on-click="add">
<div><t t-esc="props.line.name"/></div>
<div>Quantity: <t t-esc="props.line.quantity"/></div>
</div>`;

add() {
this.trigger("add-to-order", { line: this.props.line });
}
}

class Parent extends Component {


static template = xml`
<div t-on-add-to-order="addToOrder">
<OrderLine
t-foreach="orders"
t-as="line"
line="line" />
</div>`;
static components = { OrderLine };
orders = useState([
{ id: 1, name: "Coffee", quantity: 0 },
{ id: 2, name: "Tea", quantity: 0 },
]);

addToOrder(event) {
const line = event.detail.line;
line.quantity++;
}
}

In this example, the OrderLine component trigger a add-to-order event. This will


generate a DOM event which will bubble along the DOM tree. It will then be
intercepted by the parent component, which will then get the line (from
the detail key) and then increment its quantity. See the page on event handling for
more details on how events work.
Note that this example would have also worked if the OrderLine component directly
modifies the line object. However, this is not a good practice: this only works
because the props object received by the child component is reactive, so the child
component is then coupled to the parents implementation.

How to start an Owl project 🦉


Content

 Overview
 Simple html file
 With a static server
 Standard Javascript project

Overview

Each software project has its specific needs. Many of these needs can be solved with
some tooling: webpack, gulp, css preprocessor, bundlers, transpilers, ...
Because of that, it is usually not simple to just start a project. Some frameworks
provide their own tooling to help with that. But then, you have to integrate and learn
how these applications work.

Owl is designed to be used with no tooling at all. Because of that, Owl can "easily" be
integrated in a modern build toolchain. In this section, we will discuss a few different
setups to start a project. Each of these setups has advantages and disadvantages in
different situations.

Simple html file

The simplest possible setup is the following: a simple javascript file with your code. To
do that, let us create the following file structure:

hello_owl/
index.html
owl.js
app.js
The file owl.js can be downloaded from the last release published
at https://github.com/odoo/owl/releases. It is a single javascript file which export all
Owl into the global owl object.
Now, index.html should contain the following:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
And app.js should look like this:
const { Component, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;

// Owl Components
class App extends Component {
static template = xml`<div>Hello Owl</div>`;
}

// Setup code
function setup() {
mount(App, target: { document.body })
}

whenReady(setup);
Now, simply loading this html file in a browser should display a welcome message.
This setup is not fancy, but it is extremely simple. There are no tooling at all required.
It can be slightly optimized by using the minified build of Owl.

With a static server

The previous setup has a big disadvantage: the application code is located in a single
file. Obviously, we could split it in several files and add multiple <script> tags in the
html page, but then we need to make sure the script are inserted in the proper order,
we need to export each file content in global variables and we lose autocompletion
across files.
There is a low tech solution to this issue: using native javascript modules. This
however has a requirement: for security reasons, browsers will not accept modules on
content served through the file protocol. This means that we need to use a static
server.
Let us start a new project with the following file structure:

hello_owl/
src/
app.js
index.html
main.js
owl.js
As previously, the file owl.js can be downloaded from the last release published
at https://github.com/odoo/owl/releases.
Now, index.html should contain the following:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
<script src="owl.js"></script>
<script src="main.js" type="module"></script>
</head>
<body></body>
</html>
Not that the main.js script tag has the type="module" attribute. This means that the
browser will parse the script as a module, and load all its dependencies.
Here is the content of app.js and main.js:
// app.js ----------------------------------------------------------------------
const { Component, mount } = owl;
const { xml } = owl.tags;

export class App extends Component {


static template = xml`<div>Hello Owl</div>`;
}

// main.js ---------------------------------------------------------------------
import { App } from "./app.js";

function setup() {
mount(App, { target: document.body });
}

owl.utils.whenReady(setup);
The main.js file import the app.js file. Note that the import statement has a .js suffix,
which is important. Most text editor can understand this syntax and will provide
autocompletion.
Now, to execute this code, we need to serve the src folder statically. A low tech way
to do that is to use for example the python SimpleHTTPServer feature:
$ cd src
$ python -m SimpleHTTPServer 8022 # now content is available at localhost:8022
Another more "javascripty" way to do it is to create a npm application. To do that, we
can add the following package.json file at the root of the project:
{
"name": "hello_owl",
"version": "0.1.0",
"description": "Starting Owl app",
"main": "src/index.html",
"scripts": {
"serve": "serve src"
},
"author": "John",
"license": "ISC",
"devDependencies": {
"serve": "^11.3.0"
}
}
We can now install the serve tool with the command npm install, and then, start a
static server with the simple npm run serve command.

Standard Javascript project

The previous setup works, and is certainly good for some usecases, including quick
prototyping. However, it lacks some useful features, such as livereload, a test suite, or
bundling the code in a single file.

Each of these features, and many others, can be done in many different ways. Since it
is really not trivial to configure such a project, we provide here an example that can
be used as a starting point.

Our standard Owl project has the following file structure:

hello_owl/
public/
index.html
src/
components/
App.js
main.js
tests/
components/
App.test.js
helpers.js
.gitignore
package.json
webpack.config.js
This project as a public folder, meant to contain all static assets, such as images and
styles. The src folder has the javascript source code, and finally, tests contains the
test suite.
Here is the content of index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
</head>
<body></body>
</html>
Note that there are no <script> tag here. They will be injected by webpack. Now, let's
have a look at the javascript files:
// src/components/App.js -------------------------------------------------------
import { Component, tags, useState } from "@odoo/owl";

const { xml } = tags;

export class App extends Component {


static template = xml`<div t-on-click="update">Hello <t t-
esc="state.text"/></div>`;
state = useState({ text: "Owl" });
update() {
this.state.text = this.state.text === "Owl" ? "World" : "Owl";
}
}

// src/main.js -----------------------------------------------------------------
import { utils, mount } from "@odoo/owl";
import { App } from "./components/App";

function setup() {
mount(App, { target: document.body });
}

utils.whenReady(setup);

// tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl";

let fixture;

beforeEach(() => {
fixture = makeTestFixture();
});

afterEach(() => {
fixture.remove();
});

describe("App", () => {
test("Works as expected...", async () => {
await mount(App, { target: fixture });
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");

click(fixture, "div");
await nextTick();
expect(fixture.innerHTML).toBe("<div>Hello World</div>");
});
});

// tests/helpers.js ------------------------------------------------------------
import { Component } from "@odoo/owl";
import "regenerator-runtime/runtime";

export async function nextTick() {


return new Promise(function (resolve) {
setTimeout(() => Component.scheduler.requestAnimationFrame(() => resolve()));
});
}

export function makeTestFixture() {


let fixture = document.createElement("div");
document.body.appendChild(fixture);
return fixture;
}

export function click(elem, selector) {


elem.querySelector(selector).dispatchEvent(new Event("click"));
}
Finally, here is the configuration files .gitignore, package.json and webpack.config.js:
node_modules/
package-lock.json
dist/
{
"name": "hello_owl",
"version": "0.1.0",
"description": "Demo app",
"main": "src/index.html",
"scripts": {
"test": "jest",
"build": "webpack --mode production",
"dev": "webpack-dev-server --mode development"
},
"author": "Someone",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"babel-jest": "^25.1.0",
"babel-loader": "^8.0.6",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"html-webpack-plugin": "^3.2.0",
"jest": "^25.1.0",
"regenerator-runtime": "^0.13.3",
"serve": "^11.3.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.2"
},
"dependencies": {
"@odoo/owl": "^1.0.4"
},
"babel": {
"plugins": ["@babel/plugin-proposal-class-properties"],
"env": {
"test": {
"plugins": ["transform-es2015-modules-commonjs"]
}
}
},
"jest": {
"verbose": false,
"testRegex": "(/tests/.*(test|spec))\\.js?$",
"moduleFileExtensions": ["js"],
"transform": {
"^.+\\.[t|j]sx?$": "babel-jest"
}
}
}
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const host = process.env.HOST || "localhost";

module.exports = function (env, argv) {


const mode = argv.mode || "development";
return {
mode: mode,
entry: "./src/main.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".js", ".jsx"],
},
devServer: {
contentBase: path.resolve(__dirname, "public/index.html"),
compress: true,
hot: true,
host,
port: 3000,
publicPath: "/",
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: path.resolve(__dirname, "public/index.html"),
}),
],
};
};
With this setup, we can now use the following script commands:

npm run build # build the full application in prod mode in dist/

npm run dev # start a dev server with livereload

npm run test # run the jest test suite

How to test Components 🦉


Content

 Overview
 Unit Tests
Overview

It is a good practice to test applications and components to ensure that they behave
as expected. There are many ways to test a user interface: manual testing, integration
testing, unit testing, ...

In this section, we will discuss how to write unit tests for components.

Unit Tests

Writing unit tests for Owl components really depends on the testing framework used
in a project. But usually, it involves the following steps:

 create a test file: for example SomeComponent.test.js,


 in that file, import the code for SomeComponent,
 add a test case:
o create a real DOM element to use as test fixture,
o create a test environment
o create an instance of SomeComponent, mount it to the fixture
o interact with the component and assert some properties.

To help with this, it is useful to have a helper.js file that contains some common
utility functions:
export function makeTestFixture() {
let fixture = document.createElement("div");
document.body.appendChild(fixture);
return fixture;
}

export function nextTick() {


let requestAnimationFrame = owl.Component.scheduler.requestAnimationFrame;
return new Promise(function(resolve) {
setTimeout(() => requestAnimationFrame(() => resolve()));
});
}

export function makeTestEnv() {


// application specific. It needs a way to load actual templates
const templates = ...;

return {
qweb: new QWeb(templates),
..., // each service can be mocked here
};
}
With such a file, a typical test suite for Jest will look like this:

// in SomeComponent.test.js
import { SomeComponent } from "../../src/ui/SomeComponent";
import { nextTick, makeTestFixture, makeTestEnv} from '../helpers';
//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
let fixture: HTMLElement;
let env: Env;

beforeEach(() => {
fixture = makeTestFixture();
env = makeTestEnv();
// we set here the default environment for each component created in the test
Component.env = env;
});

afterEach(() => {
fixture.remove();
});

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
describe("SomeComponent", () => {
test("component behaves as expected", async () => {
const props = {...}; // depends on the component
const comp = await mount(SomeComponent, { target: fixture, props });

// do some assertions
expect(...).toBe(...);

fixture.querySelector('button').click();
await nextTick();

// some other assertions


expect(...).toBe(...);
});
});
Note that Owl does wait for the next animation frame to actually update the DOM.
This is why it is necessary to wait with the nextTick (or other methods) to make sure
that the DOM is up-to-date.
It is sometimes useful to wait until Owl is completely done updating components (in
particular, if we have a highly concurrent user interface). This next helper simply polls
every 20ms the internal Owl task queue and returns a promise which resolves when it
is empty:

function afterUpdates() {
return new Promise((resolve, reject) => {
let timer = setTimeout(poll, 20);
let counter = 0;
function poll() {
counter++;
if (owl.Component.scheduler.tasks.length) {
if (counter > 10) {
reject(new Error("timeout"));
} else {
timer = setTimeout(poll);
}
} else {
resolve();
}
}
});
}

How to write Single File Components 🦉


It is very useful to group code by feature instead of by type of file. It makes it easier
to scale application to larger size.

To do so, Owl has two small helpers that make it easy to define a template or a
stylesheet inside a javascript (or typescript) file: the xml and css helper.
This means that the template, the style and the javascript code can be defined in the
same file. For example:

const { Component } = owl;


const { xml, css } = owl.tags;

// -----------------------------------------------------------------------------
// TEMPLATE
// -----------------------------------------------------------------------------
const TEMPLATE = xml/* xml */ `
<div class="main">
<Sidebar/>
<Content />
</div>`;

// -----------------------------------------------------------------------------
// STYLE
// -----------------------------------------------------------------------------
const STYLE = css/* css */ `
.main {
display: grid;
grid-template-columns: 200px auto;
}
`;

// -----------------------------------------------------------------------------
// CODE
// -----------------------------------------------------------------------------
class Main extends Component {
static template = TEMPLATE;
static style = STYLE;
static components = { Sidebar, Content };

// rest of component...
}
Note that the above example has an inline xml comment, just after the xml call. This is
useful for some editor plugins, such as the VS Code addon Comment tagged template,
which, if installed, add syntax highlighting to the content of the template string.
How to debug Owl applications 🦉
Non trivial applications become quickly more difficult to understand. It is then useful
to have a solid understanding of what is going on. To help with that, logging useful
information is extremely valuable. There is a javascript file which can be evaluated in
an application.

Once it is executed, it will log a lot of information on each component main hooks.
The following code is a minified version to make it easier to copy/paste:

function debugOwl(t,e){let n,o="[OWL_DEBUG]";function r(t){let


e;try{e=JSON.stringify(t||{})}catch(t){e="<JSON error>"}return
e.length>200&&(e=e.slice(0,200)+"..."),e}if(Object.defineProperty(t.Component,"cur
rent",{get:()=>n,set(s){n=s;const
i=s.constructor.name;if(e.componentBlackList&&e.componentBlackList.test(i))return;
if(e.componentWhiteList&&!e.componentWhiteList.test(i))return;let
l;Object.defineProperty(n,"__owl__",{get:()=>l,set(n){!function(n,s,i){let l=`$
{s}<id=${i}>`,c=t=>console.log(`${o} ${l} ${t}`),u=t=>(!e.methodBlackList||!
e.methodBlackList.includes(t))&&!(e.methodWhiteList&&!
e.methodWhiteList.includes(t));u("constructor")&&c(`constructor, props=$
{r(n.props)}`);u("willStart")&&t.hooks.onWillStart(()=>{c("willStart")});u("mounte
d")&&t.hooks.onMounted(()=>{c("mounted")});u("willUpdateProps")&&t.hooks.onWillUpd
ateProps(t=>{c(`willUpdateProps, nextprops=$
{r(t)}`)});u("willPatch")&&t.hooks.onWillPatch(()=>{c("willPatch")});u("patched")&
&t.hooks.onPatched(()=>{c("patched")});u("willUnmount")&&t.hooks.onWillUnmount(()=
>{c("willUnmount")});const d=n.__render.bind(n);n.__render=function(...t)
{c("rendering template"),d(...t)};const h=n.render.bind(n);n.render=function(...t)
{const e=n.__owl__;let o="render";return e.isMounted||e.currentFiber||(o+="
(warning: component is not mounted, this render has no
effect)"),c(o),h(...t)};const p=n.mount.bind(n);n.mount=function(...t){return
c("mount"),p(...t)}}(s,i,(l=n).id)}})}}),e.logScheduler){let
e=t.Component.scheduler.start,n=t.Component.scheduler.stop;t.Component.scheduler.s
tart=function(){this.isRunning||console.log(`${o} scheduler: start running tasks
queue`),e.call(this)},t.Component.scheduler.stop=function()
{this.isRunning&&console.log(`${o} scheduler: stop running tasks
queue`),n.call(this)}}if(e.logStore){let
e=t.Store.prototype.dispatch;t.Store.prototype.dispatch=function(t,...n){return
console.log(`${o} store: action '${t}' dispatched. Payload: '$
{r(n)}'`),e.call(this,t,...n)}}}
debugOwl(owl, {
// componentBlackList: /App/, // regexp
// componentWhiteList: /SomeComponent/, // regexp
// methodBlackList: ["mounted"], // list of method names
// methodWhiteList: ["willStart"], // list of method names
logScheduler: false, // display/mute scheduler logs
logStore: true, // display/mute store logs
});
The above code, once pasted somewhere in the main javascript file of an owl
application, will log information looking like this:

[OWL_DEBUG] TodoApp<id=1> constructor, props={}


[OWL_DEBUG] TodoApp<id=1> mount
[OWL_DEBUG] TodoApp<id=1> willStart
[OWL_DEBUG] TodoApp<id=1> rendering template
[OWL_DEBUG] TodoItem<id=2> constructor,
props={"id":2,"completed":false,"title":"hey"}
[OWL_DEBUG] TodoItem<id=2> willStart
[OWL_DEBUG] TodoItem<id=3> constructor,
props={"id":4,"completed":false,"title":"aaa"}
[OWL_DEBUG] TodoItem<id=3> willStart
[OWL_DEBUG] TodoItem<id=2> rendering template
[OWL_DEBUG] TodoItem<id=3> rendering template
[OWL_DEBUG] TodoItem<id=3> mounted
[OWL_DEBUG] TodoItem<id=2> mounted
[OWL_DEBUG] TodoApp<id=1> mounted
Each component has an internal id, which is very useful when debugging.
Note that it is certainly useful to run this code at some point in an application, just to
get a feel of what each user action implies, for the framework.

REFERENCE
Animations 🦉
Animation is a complex topic. There are many different use cases, and many solutions
and technologies. Owl only supports some basic use cases.

Simple CSS effects

Sometimes, using pure CSS is enough. For these use cases, Owl is not really
necessary: it just needs to render a DOM element with a specific class. For example:

<a class="btn flash" t-on-click="doSomething">Click</a>


with the following CSS:

btn {
background-color: gray;
}

.flash {
transition: background 0.5s;
}

.flash:active {
background-color: #41454a;
transition: background 0s;
}
will produce a nice flash effect whenever the user clicks (or activates with the
keyboard) the button.
CSS Transitions

A more complex situation occurs when we want to transition an element in or out of


the page. For example, we may want a fade-in and fade-out effect.

The t-transition directive is here to help us. It works on html elements and on


components, by adding and removing some css classes.
To perform useful transition effects, whenever an element appears or disappears, it is
necessary to add/remove some css style or class at some precise moment in the
lifetime of a node. Since this is not easy to do by hand, Owl t-transition directive is
there to help.
Whenever a node has a t-transition directive, with a name value, the following
sequence of events will happen:
At node insertion:

 the css classes name-enter and name-enter-active will be added directly when the


node is inserted into the DOM.
 on the next animation frame: the css class name-enter will be removed and the
class name-enter-to will be added (so they can be used to trigger css transition
effects).
 at the end of the transition, name-enter-to and name-enter-active will be removed.

At node destruction:

 the css classes name-leave and name-leave-active will be added before the node is


removed to the DOM.
 on the next animation frame: the css class name-leave will be removed and the
class name-leave-to will be added (so they can be used to trigger css transition
effects).
 at the end of the transition, name-leave-to and name-leave-active will be removed.

For example, a simple fade in/out effect can be done with this:

<div>
<div t-if="state.flag" class="square" t-transition="fade">Hello</div>
</div>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
The t-transition directive can be applied on a node element or on a component.
Notes:

Owl does not support more than one transition on a single node, so the t-
transition expression must be a single value (i.e. no space allowed).
SCSS Mixins

If you use SCSS, you can use mixins to make generic animations. Here is an exemple
with a fade in / fade out animation:

@mixin animation-fade($time, $name) {


.#{$name}_fade-enter-active,
.#{$name}_fade-active {
transition: all $time;
}

.#{$name}_fade-enter {
opacity: 0;
}

.#{$name}_fade-leave-to {
opacity: 0;
}
}
Usage:

@include animation-fade(0.5s, "o_notification");


You can now have in your template:

<SomeTag t-transition="o_notification_fade"/>

rowser 🦉
Content

 Overview
 Browser Content

Overview

The browser object contains some browser native APIs, such as setTimeout, that are
used by Owl and its utility functions. They are exposed with the intent of making
them mockable if necessary.
owl.browser.setTimeout === window.setTimeout; // return true
For now, this object contains some functions that are not used by Owl. They will
eventually be removed in Owl 2.0.

Browser Content

More specifically, the browser object contains the following methods and objects:


 setTimeout
 clearTimeout
 setInterval
 clearInterval
 requestAnimationFrame
 random
 Date
 fetch
 localStorage

OWL Component 🦉
Content

 Overview
 Example
 Reference
o Reactive System
o Properties
o Static Properties
o Methods
o Lifecycle
 constructor(parent, props)
 setup()
 willStart()
 mounted()
 willUpdateProps(nextProps)
 willPatch()
 patched(snapshot)
 willUnmount()
 catchError(error)
o Root Component
o Composition
o Form Input Bindings
o References
o Dynamic sub components
o Functional Components
o SVG Components

Overview

OWL components are the building blocks for user interface. They are designed to be:

1. declarative: the user interface should be described in terms of the state of the


application, not as a sequence of imperative steps.
2. composable: each component can seamlessly be created in a parent
component by a simple tag or directive in its template.

3. asynchronous rendering: the framework will transparently wait for each sub


components to be ready before applying the rendering. It uses native
promises under the hood.

4. uses QWeb as a template system: the templates are described in XML and


follow the QWeb specification. This is a requirement for Odoo.

OWL components are defined as a subclass of Component. The rendering is


exclusively done by a QWeb template (which needs to be preloaded in QWeb).
Rendering a component generates a virtual dom representation of the component,
which is then patched to the DOM, in order to apply the changes in an efficient way.

Example

Let us have a look at a simple component:

const { useState } = owl.hooks;

class ClickCounter extends owl.Component {


state = useState({ value: 0 });

increment() {
this.state.value++;
}
}
<button t-name="ClickCounter" t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>
Note that this code is written in ESNext style, so it will only run on the latest browsers
without a transpilation step.

This example shows how a component should be defined: it simply subclasses the
Component class. If no static template key is defined, then Owl will use the
component's name as template name. Here, a state object is defined, by using
the useState hook. It is not mandatory to use the state object, but it is certainly
encouraged. The result of the useState call is observed, and any change to it will
cause a rerendering.

Reference

An Owl component is a small class which represents a component or some UI


element. It exists in the context of an environment (env), which is propagated from a
parent to its children. The environment needs to have a QWeb instance, which will be
used to render the component template.
Be aware that the name of the component may be significant: if a component does
not define a template key, then Owl will lookup in QWeb to find a template with the
component name (or one of its ancestors).

Reactive system

OWL components are normal javascript classes. So, changing a component internal
state does nothing more:

class Counter extends Component {


static template = xml`<div t-on-click="increment"><t t-
esc="state.value"/></div>`;
state = { value: 0 };

increment() {
this.state.value++;
}
}
Clicking on the Counter component defined above will call the increment method, but
it will not rerender the component. To fix that, one could add an explicit call
to render in increment:
increment() {
this.state.value++;
this.render();
}
However, it may be simple in this case, but it quickly become cumbersome, as a
component get more complex, and its internal state is modified by more than one
method.

A better way is to use the reactive system: by using the useState hook (see


the hooks section for more details), one can make Owl react to state changes.
The useState hook generates a proxy version of an object (this is done by
an observer), which allows the component to react to any change. So,
the Counter example above can be improved like this:
const { useState } = owl.hooks;

class Counter extends Component {


static template = xml`<div t-on-click="increment"><t t-
esc="state.value"/></div>`;
state = useState({ value: 0 });

increment() {
this.state.value++;
}
}
Obviously, we can call the useState hook more than once:
const { useState } = owl.hooks;

class Counter extends Component {


static template = xml`
<div>
<span t-on-click="increment(counter1)"><t t-esc="counter1.value"/></span>
<span t-on-click="increment(counter2)"><t t-esc="counter2.value"/></span>
</div>`;
counter1 = useState({ value: 0 });
counter2 = useState({ value: 0 });

increment(counter) {
counter.value++;
}
}
Note that hooks are subject to one important rule: they need to be called in the
constructor.

Properties

 el (HTMLElement | null): reference to the DOM root node of the element. It


is null when the component is not mounted.
 env (Object): the component environment, which contains a QWeb instance.
 props (Object): this is an object containing all the properties given by the
parent to a child component. For example, in the following situation, the
parent component gives a user and a color value to the ChildComponent.
 <div>
 <ChildComponent user="state.user" color="color">
</div>
Note that props are owned by the parent, not by the component. As such, it
should not ever be modified by the component (otherwise you risk unintended
effects, since the parent may not be aware of the change)!!
The props can be modified dynamically by the parent. In that case, the
component will go through the following lifecycle
methods: willUpdateProps, willPatch and patched.

Static Properties

 template (string, optional): if given, this is the name of the QWeb template that will
render the component. Note that there is a helper xml to make it easy to define an
inline template.

 components (Object, optional): if given, this is an object that contains the classes


of any sub components needed by the template. This is the main way used by
Owl to be able to create sub components.
 class ParentComponent extends owl.Component {
 static components = { SubComponent };
}
 props (Object, optional): if given, this is an object that describes the type and
shape of the (actual) props given to the component. If Owl mode is dev, this
will be used to validate the props each time the component is
created/updated. See Props Validation for more information.
 class Counter extends owl.Component {
 static props = {
 initialValue: Number,
 optional: true,
 };
}
 defaultProps (Object, optional): if given, this object define default values for
(top-level) props. Whenever props are given to the object, they will be altered
to add default value (if missing). Note that it does not change the initial object,
a new object will be created instead.
 class Counter extends owl.Component {
 static defaultProps = {
 initialValue: 0,
 };
}
 style (string, optional): it should be the return value of the css tag, which is
used to inject stylesheet whenever the component is visible on the screen.
There is another static property defined on the Component class: current. This property
is set to the currently being defined component (in the constructor). This is the
way hooks are able to get a reference to the target component.

Methods

We explain here all the public methods of the Component class.


 mount(target, options) (async): this is the main way a component is added to
the DOM: the root component is mounted to a target HTMLElement (or
document fragment). Obviously, this is asynchronous, since each children need
to be created as well. Most applications will need to call mount exactly once, on
the root component.
The options argument is an optional object with a position key.
The position key can have three possible values: first-child, last-child, self.

o first-child: with this option, the component will be prepended inside the
target,
o last-child (default value): with this option, the component will be appended
in the target element,
o self: the target will be used as the root element for the component. This
means that the target has to be an HTMLElement (and not a document
fragment). In this situation, it is possible that the component cannot be
unmounted. For example, if its target is document.body.

Note that if a component is mounted, unmounted and remounted, it will be


automatically re-rendered to ensure that changes in its state (or something in
the environment, or in the store, or ...) will be taken into account.

If a component is mounted inside an element or a fragment which is not in the


DOM, then it will be rendered fully, but not active: the mounted hooks will not
be called. This is sometimes useful if we want to load an application in
memory. In that case, we need to mount the root component again in an
element which is in the DOM:
const app = new App();
await app.mount(document.createDocumentFragment());
// app is rendered in memory, but not active
await app.mount(document.body);
// app is now visible
Note that the normal way of mounting an application is by using
the mount method on a component class, not by creating the instance by hand.
See the documentation on mounting applications.
 unmount(): in case a component needs to be detached/removed from the
DOM, this method can be used. Most applications should not call unmount, this
is more useful to the underlying component system.
 render() (async): calling this method directly will cause a rerender. Note that
this should be very rare to have to do it manually, the Owl framework is most
of the time responsible for doing that at an appropriate moment.

Note that the render method is asynchronous, so one cannot observe the
updated DOM in the same stack frame.

 shouldUpdate(nextProps): this method is called each time a component's props


are updated. It returns a boolean, which indicates if the component should
ignore a props update. If it returns false, then willUpdateProps will not be
called, and no rendering will occur. Its default implementation is to always
return true. Note that this is an optimization, similar to
React's shouldComponentUpdate. Most of the time, this should not be used, but it
can be useful if we are handling large number of components. Since this is an
optimization, Owl has the freedom to ignore the result of shouldUpdate in some
cases (for example, if a component is remounted, or if we want to force a full
rerender of the UI). However, if shouldUpdate returns true, then Owl provides
the guarantee that the component will be rendered at some point in the future
(except if the component is destroyed or if some part of the UI crashes).
 destroy(). As its name suggests, this method will remove the component, and
perform all necessary cleanup, such as unmounting the component, its
children, removing the parent/children relationship. This method should
almost never be called directly (except maybe on the root component), but
should be done by the framework instead.
Obviously, these methods are reserved for Owl, and should not be used by Owl users,
unless they want to override them. Also, Owl reserves all method names starting
with __, in order to prevent possible future conflicts with user code whenever Owl
needs to change.
Lifecycle

A solid and robust component system needs useful hooks/methods to help


developers write components. Here is a complete description of the lifecycle of a owl
component:

Method Description

setup setup

willStart async, before first rendering

mounted just after component is rendered and added to the DOM

willUpdateProp
async, before props update
s

willPatch just before the DOM is patched

patched just after the DOM is patched

willUnmount just before removing component from DOM

catchError catch errors (see error handling page)

Notes:

 hooks call order is precisely defined: [willX] hooks are called first on parent, then on
children, and [Xed] are called in the reverse order: first children, then parent.
 no hook method should ever be called manually. They are supposed to be called by
the owl framework whenever it is required.

constructor(parent, props)
The constructor is not exactly a hook, it is the regular, normal, constructor of the
component. Since it is not a hook, you need to make sure that super is called.
This is usually where you would set the initial state and the template of the
component.

constructor(parent, props) {
super(parent, props);
this.state = useState({someValue: true});
this.template = 'mytemplate';
}
Note that with ESNext class fields, the constructor method does not need to be
implemented in most cases:

class ClickCounter extends owl.Component {


state = useState({ value: 0 });

...
}
Hook functions can be called in the constructor.

setup()
setup is run just after the component is constructed. It is a lifecycle method, very
similar to the constructor, except that it does not receive any argument.

It is a valid method to call hook functions. Note that one of the main reason to have
the setup hook in the component lifecycle is to make it possible to monkey patch it. It
is a common need in the Odoo ecosystem.
setup() {
useSetupAutofocus();
}
willStart()
willStart is an asynchronous hook that can be implemented to perform some action
before the initial rendering of a component.

It will be called exactly once before the initial rendering. It is useful in some cases, for
example, to load external assets (such as a JS library) before the component is
rendered. Another use case is to load data from a server.

async willStart() {
await owl.utils.loadJS("my-awesome-lib.js");
}
At this point, the component is not yet rendered. Note that a slow willStart method
will slow down the rendering of the user interface. Therefore, some care should be
made to make this method as fast as possible.
mounted()
mounted is called each time a component is attached to the DOM, after the initial
rendering and possibly later if the component was unmounted and remounted. At
this point, the component is considered active. This is a good place to add some
listeners, or to interact with the DOM, if the component needs to perform some
measure for example.
It is the opposite of willUnmount. If a component has been mounted, it will always be
unmounted at some point in the future.
The mounted method will be called recursively on each of its children. First, the
parent, then all its children.

It is allowed (but not encouraged) to modify the state in the mounted hook. Doing so


will cause a rerender, which will not be perceptible by the user, but will slightly slow
down the component.
willUpdateProps(nextProps)
The willUpdateProps is an asynchronous hook, called just before new props are set.
This is useful if the component needs to perform an asynchronous task, depending
on the props (for example, assuming that the props are some record Id, fetching the
record data).

willUpdateProps(nextProps) {
return this.loadData({id: nextProps.id});
}
This hook is not called during the first render (but willStart is called and performs a
similar job).

willPatch()
The willPatch hook is called just before the DOM patching process starts. It is not
called on the initial render. This is useful to read information from the DOM. For
example, the current position of the scrollbar.

Note that modifying the state is not allowed here. This method is called just before
an actual DOM patch, and is only intended to be used to save some local DOM state.
Also, it will not be called if the component is not in the DOM.

patched(snapshot)
This hook is called whenever a component did actually update its DOM (most likely
via a change in its state/props or environment).

This method is not called on the initial render. It is useful to interact with the DOM
(for example, through an external library) whenever the component was patched.
Note that this hook will not be called if the component is not in the DOM.

Updating the component state in this hook is possible, but not encouraged. One
needs to be careful, because updates here will create an additional rendering, which
in turn will cause other calls to the patched method. So, we need to be particularly
careful at avoiding endless cycles.
willUnmount()
willUnmount is a hook that is called each time just before a component is unmounted
from the DOM. This is a good place to remove listeners, for example.

mounted() {
this.env.bus.on('someevent', this, this.doSomething);
}
willUnmount() {
this.env.bus.off('someevent', this, this.doSomething);
}
This is the opposite method of mounted.
catchError(error)
The catchError method is useful when we need to intercept and properly react to
(rendering) errors that occur in some sub components. See the page on error
handling.
Root Component

Most of the time, an Owl component will be created automatically by a tag (or the t-
component directive) in a template. There is however an obvious exception: the root
component of an Owl application has to be created manually:
class App extends owl.Component { ... }

const app = new App();


app.mount(document.body);
The root component does not have a parent nor props (see note below). It will be
setup with an environment (either the env defined on its class, or a default empty
environment).
Note: a root component can however be given a props object in its constructor, like
this: new App(null, {some: 'object'});. It will not be a true props object, managed by
Owl (so, for example, it will never be updated).

Composition

The example above shows a QWeb template with a sub component. In a template,
components are declared with a tagname corresponding to the class name. It has to
be capitalized.

<div t-name="ParentComponent">
<span>some text</span>
<MyComponent info="13" />
</div>
class ParentComponent extends owl.Component {
static components = { MyComponent: MyComponent};
...
}
In this example, the ParentComponent's template creates a component MyComponent just
after the span. The info key will be added to the subcomponent's props. Each props is
a string which represents a javascript (QWeb) expression, so it is dynamic. If it is
necessary to give a string, this can be done by quoting it: someString="'somevalue'".
Note that the rendering context for the template is the component itself. This means
that the template can access state (if it exists), props, env, or any methods defined in
the component.
<div t-name="ParentComponent">
<ChildComponent count="state.val" />
</div>
class ParentComponent {
static components = { ChildComponent };
state = useState({ val: 4 });
}
Whenever the template is rendered, it will automatically create the
subcomponent ChildComponent at the correct place. It needs to find the reference to
the actual component class in the special static components key, or the class registered
in QWeb's global registry (see register function of QWeb). It first looks inside the
static components key, then fallbacks on the global registry.
Props: In this example, the child component will receive the object {count: 4} in its
constructor. This will be assigned to the props variable, which can be accessed on the
component (and also, in the template). Whenever the state is updated, then the sub
component will also be updated automatically. See the props section for more
information.
CSS and style: Owl allows the parent to declare additional css classes or style for the
sub component: css declared in class, style, t-att-class or t-att-style will be added
to the root component element.
<div t-name="ParentComponent">
<MyComponent class="someClass" style="font-weight:bold;" info="13" />
</div>
Warning: there is a small caveat with dynamic class attributes: since Owl needs to be
able to add/remove proper classes whenever necessary, it needs to be aware of the
possible classes. Otherwise, it will not be able to make the difference between a valid
css class added by the component, or other custom code, and a class that need to be
removed. This is why we only support the explicit syntax with a class object:

<MyComponent t-att-class="{a: state.flagA, b: state.flagB}" />

Form Input Bindings

It is very common to need to be able to read the value out of an


html input (or textarea, or select) in order to use it (note: it does not need to be in a
form!). A possible way to do this is to do it by hand:
class Form extends owl.Component {
state = useState({ text: "" });

_updateInputValue(event) {
this.state.text = event.target.value;
}
}
<div>
<input t-on-input="_updateInputValue" />
<span t-esc="state.text" />
</div>
This works. However, this requires a little bit of plumbing code. Also, the plumbing
code is slightly different if you need to interact with a checkbox, or with radio
buttons, or with select tags.

To help with this situation, Owl has a builtin directive t-model: its value should be an
observed value in the component (usually state.someValue). With the t-
model directive, we can write a shorter code, equivalent to the previous example:
class Form extends owl.Component {
state = { text: "" };
}
<div>
<input t-model="state.text" />
<span t-esc="state.text" />
</div>
The t-model directive works with <input>, <input type="checkbox">, <input
type="radio">, <textarea> and <select>:
<div>
<div>Text in an input: <input t-model="state.someVal"/></div>
<div>Textarea: <textarea t-model="state.otherVal"/></div>
<div>Boolean value: <input type="checkbox" t-model="state.someFlag"/></div>
<div>Selection:
<select t-model="state.color">
<option value="">Select a color</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
</div>
<div>
Selection with radio buttons:
<span>
<input type="radio" name="color" id="red" value="red" t-
model="state.color"/>
<label for="red">Red</label>
</span>
<span>
<input type="radio" name="color" id="blue" value="blue" t-
model="state.color" />
<label for="blue">Blue</label>
</span>
</div>
</div>
Like event handling, the t-model directive accepts the following modifiers:
Modifier Description

.lazy update the value on the change event (default is on input event)

.number try to parse the value to a number (using parseFloat)

.trim trim the resulting value

For example:

<input t-model.lazy="state.someVal" />


These modifiers can be combined. For instance, t-model.lazy.number will only update
a number whenever the change is done.
Note: the online playground has an example to show how it works.

References

The useRef hook is useful when we need a way to interact with some inside part of a
component, rendered by Owl. It can work either on a DOM node, or on a component,
tagged by the t-ref directive. See the hooks section for more detail.
As a short example, here is how we could set the focus on a given input:
<div>
<input t-ref="input"/>
<button t-on-click="focusInput">Click</button>
</div>
import { useRef } from "owl/hooks";

class SomeComponent extends Component {


inputRef = useRef("input");

focusInput() {
this.inputRef.el.focus();
}
}
The useRef hook can also be used to get a reference to an instance of a sub
component rendered by Owl. In that case, we need to access it with the comp property
instead of el:
<div>
<SubComponent t-ref="sub"/>
<button t-on-click="doSomething">Click</button>
</div>
import { useRef } from "owl/hooks";

class SomeComponent extends Component {


static components = { SubComponent };
subRef = useRef("sub");

doSomething() {
this.subRef.comp.doSomeThingElse();
}
}
Note that these two examples uses the suffix ref to name the reference. This is not
mandatory, but it is a useful convention, so we do not forget to access it with
the el or comp suffix.

Dynamic sub components

It is not common, but sometimes we need a dynamic component name. In this case,
the t-component directive can also be used to accept dynamic values with string
interpolation (like the t-attf- directive):
<div t-name="ParentComponent">
<t t-component="ChildComponent{{id}}" />
</div>
class ParentComponent {
static components = { ChildComponent1, ChildComponent2 };
state = { id: 1 };
}
There is an even more dynamic way to use t-component: its value can be an expression
evaluating to an actual component class. In that case, this is the class that will be used
to create the component:
class A extends Component<any, any, any> {
static template = xml`<span>child a</span>`;
}
class B extends Component<any, any, any> {
static template = xml`<span>child b</span>`;
}
class App extends Component<any, any, any> {
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;

state = { child: "a" };

get myComponent() {
return this.state.child === "a" ? A : B;
}
}
In this example, the component App selects dynamically the concrete sub component
class.
Note that the t-component directive can only be used on <t> nodes.

Functional Components

Owl does not exactly have functional components. However, there is an extremely
close alternative: calling sub templates.

A stateless functional component in react is usually some kind of function that maps
props to a virtual dom (often with jsx). So, basically, almost like a template rendered
with props. In Owl, this can be done by simply defining a template, that will access
the props object:
const Welcome = xml`<h1>Hello, <t t-esc="props.name"/></h1>`;

class MyComponent extends Component {


static template = xml`
<div>
<t t-call=${Welcome}/>
<div>something</div>
</div>
`;
}
The way this works is that sub templates are inlined, and have access to the ambient
context. They can therefore access props, and any other part of the caller component.

SVG Components

Owl components can be used to generate dynamic SVG graphs:

class Node extends Component {


static template = xml`
<g>
<circle t-att-cx="props.x" t-att-cy="props.y" r="4" fill="black"/>
<text t-att-x="props.x - 5" t-att-y="props.y + 18"><t t-
esc="props.node.label"/></text>
<t t-set="childx" t-value="props.x + 100"/>
<t t-set="height" t-value="props.height/(props.node.children ||
[]).length"/>
<t t-foreach="props.node.children || []" t-as="child">
<t t-set="childy" t-value="props.y + child_index*height"/>
<line t-att-x1="props.x" t-att-y1="props.y" t-att-x2="childx" t-
att-y2="childy" stroke="black" />
<Node x="childx" y="childy" node="child" height="height"/>
</t>
</g>
`;
static components = { Node };
}

class RootNode extends Component {


static template = xml`
<svg height="180">
<Node node="graph" x="10" y="20" height="180"/>
</svg>
`;
static components = { Node };
graph = {
label: "a",
children: [
{ label: "b" },
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
{ label: "f", children: [{ label: "g" }] },
],
};
}
This RootNode component will then display a live SVG representation of the graph
described by the graph property. Note that there is a recursive structure here:
the Node component uses itself as a subcomponent.
Note that since SVG needs to be handled in a specific way (its namespace needs to
be properly set), there is a small constraint for Owl components: if an owl component
is supposed to be a part of an svg graph, then its root node needs to be a g tag, so
Owl can properly set the namespace.

Owl Content 🦉
Here is a complete visual representation of everything exported by the owl global
object.
For example, Component is available at owl.Component and EventBus is exported
as owl.core.EventBus.
browser
Component misc
Context AsyncRoot
QWeb Portal
mount router
Store Link
useState RouteComponent
config Router
mode
core tags
EventBus css
Observer xml
hooks utils
onWillStart debounce
onMounted escape
onWillUpdateProps loadJS
onWillPatch loadFile
onPatched shallowEqual
onWillUnmount whenReady
useContext
useState
useRef
useComponent
useEnv
useSubEnv
useStore
useDispatch
useGetters
Note that for convenience, the useState hook is also exported at the root of
the owl object.

Concurrency Model 🦉
Content

 Overview
 Rendering Components
 Semantics
 Asynchronous Rendering

Overview

Owl was designed from the very beginning with asynchronous components. This
comes from the willStart and the willUpdateProps lifecycle hooks. With these
methods, it is possible to build complex highly concurrent applications.
Owl concurrent mode has several benefits: it makes it possible to delay the rendering
until some asynchronous operation is complete, it makes it possible to lazy load
libraries, while keeping the previous screen completely functional. It is also good for
performance reasons: Owl uses it to only apply the result of many different
renderings only once in an animation frame. Owl can cancel a rendering that is no
longer relevant, restart it, reuse it in some cases.

But even though using concurrency is quite simple (and is the default behaviour),
asynchrony is difficult, because it introduces an additional dimension that vastly
increase the complexity of an application. This section will explain how Owl manages
this complexity, how concurrent rendering works in a general way.
Rendering Components

The word rendering is a little vague, so, let us explain more precisely the process by
which Owl components are displayed on a screen.

When a component is mounted or updated, a new rendering is started. It has two


phases: virtual rendering and patching.

Virtual rendering

This phase represent the process of rendering a template, in memory, which create a
virtual representation of the desired component html). The output of this phase is a
virtual DOM.

It is asynchronous: each subcomponents needs to either be created (so, willStart will


need to be called), or updated (which is done with the willUpdateProps method). This
is completely a recursive process: a component is the root of a component tree, and
each sub component needs to be (virtually) rendered.

Patching

Once a rendering is complete, it will be applied on the next animation frame. This is
done synchronously: the whole component tree is patched to the real DOM.

Semantics

We give here an informal description of the way components are created/updated in


an application. Here, ordered lists describe actions that are executed sequentially,
bullet lists describe actions that are executed in parallel.

Scenario 1: initial rendering Imagine we want to render the following component


tree:

A
/ \
B C
/ \
D E
Here is what happen whenever we mount the root component (with some code
like app.mount(document.body)).
1. willStart is called on A
2. when it is done, template A is rendered.
o component B is created
a. willStart is called on B
b. template B is rendered
o component C is created
a. willStart is called on C
b. template C is rendered
 component D is created
a. willStart is called on D
b. template D is rendered
 component E is created
a. willStart is called on E
b. template E is rendered
3. each components are patched into a detached DOM element, in the following
order: E, D, C, B, A. (so the actual full DOM tree is created in one pass)
4. the component A root element is actually appended to document.body
5. The method mounted is called recursively on all components in the following
order: E, D, C, B, A.
Scenario 2: rerendering a component. Now, let's assume that the user clicked on
some button in C, and this results in a state update, which is supposed to:

 update D,
 remove E,
 add new component F.

So, the component tree should look like this:

A
/ \
B C
/ \
D F
Here is what Owl will do:

1. because of a state change, the method render is called on C


2. template C is rendered again
o component D is updated:
a. hook willUpdateProps is called on D (async)
b. template D is rerendered
o component F is created:
a. hook willStart is called on F (async)
b. template F is rendered
3. willPatch hooks are called recursively on components C, D (not on F, because it
is not mounted yet)
4. components F, D are patched in that order
5. component C is patched, which will cause recursively:

o willUnmount hook on E
o destruction of E,
ii. mounted hook is called on F, patched hooks are called on D, C
Tags are very small helpers to make it easy to write inline templates. There is only one
currently available tag: xml, but we plan to add other tags later, such as a css tag,
which will be used to write single file components.

Asynchronous Rendering

Working with asynchronous code always adds a lot of complexity to a system.


Whenever different parts of a system are active at the same time, one needs to think
carefully about all possible interactions. Clearly, this is also true for Owl components.

There are two different common problems with Owl asynchronous rendering model:

 any component can delay the rendering (initial and subsequent) of the whole
application
 for a given component, there are two independant situations that will trigger an
asynchronous rerendering: a change in the state, or a change in the props. These
changes may be done at different times, and Owl has no way of knowing how to
reconcile the resulting renderings.

Here are a few tips on how to work with asynchronous components:

1. Minimize the use of asynchronous components!

2. Maybe move the asynchronous logic in a store, which then triggers (mostly)
synchronous renderings
3. Lazy loading external libraries is a good use case for async rendering. This is
mostly fine, because we can assume that it will only takes a fraction of a
second, and only once (see owl.utils.loadJS)
4. For all the other cases, the AsyncRoot component is there to help you. When
this component is met, a new rendering sub tree is created, such that the
rendering of that component (and its children) is not tied to the rendering of
the rest of the interface. It can be used on an asynchronous component, to
prevent it from delaying the rendering of the whole interface, or on a
synchronous one, such that its rendering isn't delayed by other (asynchronous)
components. Note that this directive has no effect on the first rendering, but
only on subsequent ones (triggered by state or props changes).
5. <div t-name="ParentComponent">
6. <SyncChild />
7. <AsyncRoot>
8. <AsyncChild/>
9. </AsyncRoot>
</div>
Config 🦉
The Owl framework is designed to work in many situations. However, it is sometimes
necessary to customize some behaviour. This is done by using the
global config object. It provides two settings:

 mode (default value: prod),
 enableTransitions (default value: true).

Mode

By default, Owl is in production mode, this means that it will try to do its job fast, and
skip some expensive operations. However, it is sometimes necessary to have better
information on what is going on, this is the purpose of the dev mode.
Owl has a mode flag, in owl.config.mode. Its default value is prod, but it can be set
to dev:
owl.config.mode = "dev";
Note that templates compiled with the prod settings will not be recompiled. So,
changing this setting is best done at startup.
An important job done by the dev mode is to validate props for each component
creation and update. Also, extra props will cause an error.
enableTransitions
Transitions are usually nice, but they can cause issues in some specific cases, such as
automated tests. It is uncomfortable having to wait for a transition to end before
moving to the next step.

To solve this issue, Owl can be configured to ignore the t-transition directive. To do


that, one only needs to set the enableTransitions flag to false:
owl.config.enableTransitions = false;
Note that it suffers from the same drawback as the "dev" mode: all compiled
templates, if any, will keep their current behaviours.

Context 🦉
Content

 Overview
 Example
 Reference
o Context
o useContext
Overview

The Context object provides a way to share data between an arbitrary number of


components. Usually, data is passed from a parent to its children component, but
when we have to deal with some mostly global information, this can be annoying,
since each component will need to pass the information to each children, even
though some or most of them will not use the information.
With a Context object, each component can subscribe (with the useContext hook) to
its state, and will be updated whenever the context state is updated.

Example

Assume that we have an application with various components which needs to render
differently depending on the size of the device. Here is how we could proceed to
make sure that the information is properly shared. First, let us create a context, and
add it to the environment:

const deviceContext = new Context({ isMobile: true });


App.env.deviceContext = deviceContext;
If we want to make it completely responsive, we need to update its value whenever
the size of the screen is updated:

const isMobile = () => window.innerWidth <= 768;


window.addEventListener(
"resize",
owl.utils.debounce(() => {
const state = deviceContext.state;
if (state.isMobile !== isMobile()) {
state.isMobile = !state.isMobile;
}
}, 15)
);
Then, each component that want can subscribe and render differently depending on
the fact that we are in a mobile or desktop mode.

class SomeComponent extends Component {


static template = xml`
<div>
<t t-if=device.isMobile>
some simplified user interface
</t>
<t t-else="">
a more advanced user interface
</t>
</div>`;
device = useContext(this.env.deviceContext);
}
Reference
Context
A Context object should be created with a state object:
const someContext = new Context({ some: "key" });
Its state is now available in the state key:
someContext.state.some = "other key";
This is the way some global code (such as the responsive code above) should read
and update the context state. However, components should not ever read the context
state directly from the context, they should instead use the useContext hook to
properly register themselves to state changes.
Note that the Context hook is different from the React version. For example, there is
no concept of provider/consumer. So, the Context feature does not by itself allow the
use of a different context state depending on the component place in the component
tree. However, this functionality can be obtained, if necessary, with the use of sub
environment.
useContext
The useContext hook is the normal way for a component to register themselve to
context state changes. The useContext method returns the context state:
device = useContext(this.env.deviceContext);
It is a simple observed state (with an owl Observer), which contains the shared
information.

You might also like