Table of Contents

In this article, we will see how to extend, monkey-patch and, modify existing OWL Components in Odoo 14. There is a lot of confusion about that, and the existing way of overriding Odoo Widget doesn't work in that case.

We will focus on OWL Components inside Odoo 14, the process will probably be different in Odoo 15 since the WebClient has been entirely rewritten in OWL.

This article assumes you have a good understanding of OWL already, if this is not the case check out this article series where we create the Realworld App with OWL and go other most of the functionalities of the Framework.

All OWL-related content is available here.

Introduction to OWL Components

OWL Components are quite different from the usual Odoo JS Classes with the custom inheritance system you were familiar with.

First and foremost they are ES6 Classes, and if you are not familiar with ES6 classes, you can visit this Google presentation article. I would also refer you to the amazing book You Don't Know JS: ES6 and Beyond.

ES6 Classes are basically syntactical sugar over the existing prototype-based inheritance in JavaScript. At the most basic level, an ES6 Class is a constructor that conforms to prototype-based inheritance. ES6 classes still have Object.prototype!

To go deeper on this subject I would recommend this article about the difference between these ES6 classes and prototypes. This is a very confusing subject, but this quote from the article is very relevant:

The most important difference between class- and prototype-based inheritance is that a class defines a type which can be instantiated at runtime, whereas a prototype is itself an object instance.

Anyway, to work with Odoo 14 existing OWL Components, you still have to know some general concepts. We will keep it to a bare minimum, so let's begin with what an ES6 Class look like!

class Component {
  constructor(name) {
    this.name = name;
  }

  render() {
    console.log(`${this.name} renders itself.`);
  }
    
  // Getter/setter methods are supported in classes,
  // similar to their ES5 equivalents
  get uniqueId() {
    return `${this.name}-test`;
  }
}

You can inherit classes with the keyword extends and super to call parent function.

class MyBetterComponent extends Component {
  constructor(name) {
    super(name); // call the super class constructor and pass in the name parameter
  }

  render() {
    console.log(`${this.name} with id ${this.uniqueId} render itslef better.`);
  }
}

let comp = new MyBetterComponent('MyBetterComponent');
comp.render(); // MyBetterComponent with id MyBetterComponent-test renders itself better.

This is the standard ES6 super keyword, don't confuse it with the Odoo _super function built inside the Framework.

Most of the patching, extending, overriding of OWL Components in Odoo will make use of this basic knowledge, so let's dive in. Everything will become clearer with examples.

Odoo OWL utils patch, and patchMixin functions.

Extending Odoo OWL components is done via a patch function that comes in 2 flavors. Either the Component itself exposes a patch function because it is wrapped around the patchMixin. Or you have to use the patch function directly (in the web.utils package) to apply a patch to an OWL Component.

With the patchMixin, the Component exposes a "patch" function.

Inside odoo/addons/web/static/src/js/core/patch_mixin.js we have this patchMixinfunction:

function patchMixin(OriginalClass) {
    let unpatchList = [];
    class PatchableClass extends OriginalClass {}

    PatchableClass.patch = function (name, patch) {
        if (unpatchList.find(x => x.name === name)) {
            throw new Error(`Class ${OriginalClass.name} already has a patch ${name}`);
        }
        if (!Object.prototype.hasOwnProperty.call(this, 'patch')) {
            throw new Error(`Class ${this.name} is not patchable`);
        }
        const SubClass = patch(Object.getPrototypeOf(this));
        unpatchList.push({
            name: name,
            elem: this,
            prototype: this.prototype,
            origProto: Object.getPrototypeOf(this),
            origPrototype: Object.getPrototypeOf(this.prototype),
            patch: patch,
        });
        Object.setPrototypeOf(this, SubClass);
        Object.setPrototypeOf(this.prototype, SubClass.prototype);
    };

    PatchableClass.unpatch = function (name) {
        if (!unpatchList.find(x => x.name === name)) {
            throw new Error(`Class ${OriginalClass.name} does not have any patch ${name}`);
        }
        const toUnpatch = unpatchList.reverse();
        unpatchList = [];
        for (let unpatch of toUnpatch) {
            Object.setPrototypeOf(unpatch.elem, unpatch.origProto);
            Object.setPrototypeOf(unpatch.prototype, unpatch.origPrototype);
        }
        for (let u of toUnpatch.reverse()) {
            if (u.name !== name) {
                PatchableClass.patch(u.name, u.patch);
            }
        }
    };
    return PatchableClass;
}
odoo/addons/web/static/src/js/core/patch_mixin.js

A Component using this patchMixin is returned wrapped around the function, for example inside odoo/addons/mail/static/src/components/messaging_menu/messaging_menu.js the MessagingMenu is returned like that:

// ...
const patchMixin = require('web.patchMixin');

const { Component } = owl;

class MessagingMenu extends Component {
// ...
// content of the file
// ...
}
return patchMixin(MessagingMenu);

Be careful, there are actually not that many Components that are returned with the patchMixin, you should always check first if that is the case. We will call these kinds of components "Patchable Components".

Import "web.utils", patch function for a "non-patchable" Component as a last resort.

When the Component doesn't use the patchMixin you will not be able to extend the ES6 class properly but with the patch function you will be able to override the regular functions of the Component.

The web.utils patchfunction is kind of limited, and will work on the "regular functions" of the component. Constructor, getters, setters won't be inherited with technique as we will see in examples later.

This is the patch function content:

/**
 * Patch a class and return a function that remove the patch
 * when called.
 *
 * This function is the last resort solution for monkey-patching an
 * ES6 Class, for people that do not control the code defining the Class
 * to patch (e.g. partners), and when that Class isn't patchable already
 * (i.e. when it doesn't have a 'patch' function, defined by the 'web.patchMixin').
 *
 * @param {Class} C Class to patch
 * @param {string} patchName
 * @param {Object} patch
 * @returns {Function}
 */
patch: function (C, patchName, patch) {
    let metadata = patchMap.get(C.prototype);
    if (!metadata) {
        metadata = {
            origMethods: {},
            patches: {},
            current: []
        };
        patchMap.set(C.prototype, metadata);
    }
    const proto = C.prototype;
    if (metadata.patches[patchName]) {
        throw new Error(`Patch [${patchName}] already exists`);
    }
    metadata.patches[patchName] = patch;
    applyPatch(proto, patch);
    metadata.current.push(patchName);

    function applyPatch(proto, patch) {
        Object.keys(patch).forEach(function (methodName) {
            const method = patch[methodName];
            if (typeof method === "function") {
                const original = proto[methodName];
                if (!(methodName in metadata.origMethods)) {
                    metadata.origMethods[methodName] = original;
                }
                proto[methodName] = function (...args) {
                    const previousSuper = this._super;
                    this._super = original;
                    const res = method.call(this, ...args);
                    this._super = previousSuper;
                    return res;
                };
            }
        });
    }

    return utils.unpatch.bind(null, C, patchName);
},

As you may already see, the content of this function is problematic, it directly touches the prototype of the Object and do some checks on the typeof == "function" that can be misleading...

A Component returned from its module wrapped around the patchMixin has a patch function that you can use, if not your last resort is the web.utils general patch function.

In conclusion, this is what we have to work with. Now we will go through real world examples on how to apply this knowledge and see some specific cases.


Patchable Component (returned with "patchMixin"): Extend, monkey-patch, override.

Basic syntax

The basic syntax of extending a patchable component is:

PatchableComponent.patch("name_of_the_patch", (T) => {
    class NewlyPatchedComponent extends T {
        //... go wild 
    }
    return NewlyPatchedComponent
})

With this patch, you really play with ES6 classes syntax. Your extended Component is also an ES6 class so you can touch the constructor, getters, setters, properties, and other functions.

Example: the ControlPanel Component.

In this example, we will extend the ControlPanel Component. This component is returned with the patchMixin function, original file:

// at the end of file...
ControlPanel.template = 'web.ControlPanel';

return patchMixin(ControlPanel);

Describing the functionality.

The goal of our module is to be very obnoxious, we will be to display a message, under the ControlPanel (everywhere) that will call an API and show a random inspiring quote from some famous people.

Please don't use this code in a real project, everybody will hate you secretly.

To make our fetch request to our quotes API we will use the willUpdateProps hook so every time the user navigates on his WebClient it will fetch a new quote!

If you are not familiar with OWL hooks and particularly willUpdateProps, there is series about OWL from scratch here.
And the part talking about willUpdateProps is available here.

Implementing the code

First, let's extend the OWL XML template to add our div that will contain the quote.

<?xml version="1.0" encoding="UTF-8" ?>
<templates>
    <t t-inherit="web.ControlPanel" t-inherit-mode="extension" owl="1">
        <xpath expr="//div[hasclass('o_control_panel')]" position="inside">
            <div t-esc="state.customText" class="o_control_panel_random_quote"></div>
        </xpath>
    </t>
</templates>
/static/src/xml/control_panel.xml

Inheriting an OWL XML Template is very similar to extending standard QWeb templates except that you should not forget to add owl="1". We will put our div inside the control panel and show the customText inside the state of our Component.

We will make it prettier by adding some custom SCSS for it, inside /src/scss/control_panel.scss.

.o_control_panel {
  .o_control_panel_random_quote {
    color: $text-muted;
    font-style: italic;
    align-items: center;
    justify-content: center;
    font-weight: bolder;
  }
}

Now for the JavaScript module itself /src/js/control_panel.js

odoo.define("owl_tutorial.ControlPanel", function (require) {
  "use strict";
  const ControlPanel = require("web.ControlPanel");
  const { useState } = owl.hooks;

  // ConstrolPanel has a patch function thanks to the patchMixin 
  // This is the usual syntax, first argument is the name of our patch.
  ControlPanel.patch("owl_tutorial.ControlPanelCodingDodo", (T) => {
    class ControlPanelPatched extends T {
      constructor() {
        super(...arguments);
        this.state = useState({
          customText: "",
        });
        console.log(this.state);
      }
        
      async willUpdateProps(nextProps) {
        // Don't forget to call the super
        await super.willUpdateProps(nextProps);
        
        let self = this;
        fetch("https://type.fit/api/quotes")
          .then(function (response) {
            return response.json();
          })
          .then(function (data) {
            let quote = data[Math.floor(Math.random() * data.length)];
            // Update the state of the Component
            Object.assign(self.state, {
              customText: `${quote.text} - ${quote.author}`,
            });
          });
      }
    }
    return ControlPanelPatched;
  });
});

As you can see, having the Component returned with patchMixin makes it very easy to extend it directly, patch its function and add features!

Using patchMixin to display a quote inside OWL ControlPanel Component

Now let's take a loot at non-patchable Components.


Non-Patchable Component: Override a regular function with "web.utils" patch.

As of Odoo 14, most of the Components aren't returned with patchMixin and if we want to override the content of some Component functions we will use the web.utils patch function.

Example: the FileUpload Component.

Inside the mail addon the component FileUpload is responsible of handling the input files and the function that interests us is this one:

/**
 * @param {FileList|Array} files
 * @returns {Promise}
 */
async uploadFiles(files) {
    await this._unlinkExistingAttachments(files);
    this._createTemporaryAttachments(files);
    await this._performUpload(files);
    this._fileInputRef.el.value = '';
}

This Component isn't return wrapped with the patchMixin so we will have to use the "web.utils" function patch.

Describing the functionality

In this example we will change the behavior of the file upload inside the chatter send message box:

Example of chat box with upload file

We will try to extend the behavior of the FileUpload so it doesn't even try to compute any file with a size over 10MB.

Implementing the code.

This is the content of our JavaScript module file.

odoo.define(
  "owl_tutorial/static/src/components/file_uploader/file_uploader.js",
  function (require) {
    "use strict";

    const components = {
      FileUploader: require("mail/static/src/components/file_uploader/file_uploader.js"),
    };

    const { patch } = require("web.utils");

    patch(
      components.FileUploader,
      "owl_tutorial/static/src/components/file_uploader/file_uploader.js",
      {
        // You can add your own functions to the Component.
        getMaxSize() {
          return 10000000;
        },
          
        /**
         * @override
         */
        async uploadFiles(files) {
          for (const file of files) {
            if (file.size > this.getMaxSize()) {
              // Files over 10MB are now rejected
              this.env.services["notification"].notify({
                type: "danger",
                message: owl.utils.escape(
                  `Max file size allowed is 10 MB, This file ${file.name} is too big!`
                ),
              });
              return false;
            }
          }
          return this._super(files);
        },
      }
    );
    console.log(components.FileUploader.prototype);
  }
);

With that done we now have a limit of 10MB on the size of the file uploaded, and a little notification warning us. We return _super if no file reached the limit.

Extend FileUpload Component to show a Warning of file size over 10MB

Non-patchable Component: Override the "getter" of an OWL Component.

Some time ago I saw a question on the Odoo forums asking to override the get avatar getter of the Message component.

I noticed a lot of confusion around that and unfortunately, as we saw in the introduction, there is also an architectural problem with the way the patch function is coded in Odoo core.

Describing the problem

This is the original get avatar getter function:

/**
 * @returns {string}
 */
get avatar() {
    if (
        this.message.author &&
        this.message.author === this.env.messaging.partnerRoot
    ) {
        return '/mail/static/src/img/odoobot.png';
    } else if (this.message.author) {
        // TODO FIXME for public user this might not be accessible. task-2223236
        // we should probably use the correspondig attachment id + access token
        // or create a dedicated route to get message image, checking the access right of the message
        return this.message.author.avatarUrl;
    } else if (this.message.message_type === 'email') {
        return '/mail/static/src/img/email_icon.png';
    }
    return '/mail/static/src/img/smiley/avatar.jpg';
}

This syntax with a space between get and avatar is what we call a getter function.

To see the problem we have to look inside the content of the web.utils patch function and especially the applyPatch function. We can see this condition

if (typeof method === "function") {
    //...
}

But doing typeof on avatar will give us string in that case and not function type! So the patch will never get applied, we will have to find another way to hard override this getter function.

We could try to patch the components.Message.prototype instead of the Message class itself but that would also throw an error because the patch function stores a WeakMap on top of the file:

  const patchMap = new WeakMap();

To search and add patched prototype, the lookup is done via a WeakMap this way:

patch: function (C, patchName, patch) {
    let metadata = patchMap.get(C.prototype);
    if (!metadata) {
        metadata = {
            origMethods: {},
            patches: {},
            current: [],
        };
        patchMap.set(C.prototype, metadata);
    }

So the C.prototype will throw an error if the C given is already SomeClass.prototype.

Solution 1 - Redefining prototype property.

To quickly solve this problem we will apply standard JavaScript knowledge with Object.defineProperty on the prototype and change the "avatar" property.

odoo.define(
  "owl_tutorial/static/src/components/message/message.js",
  function (require) {
    "use strict";

    const components = {
      Message: require("mail/static/src/components/message/message.js"),
    };

    Object.defineProperty(components.Message.prototype, "avatar", {
      get: function () {
        if (
          this.message.author &&
          this.message.author === this.env.messaging.partnerRoot
        ) {
          // Here we replace the Robot with the better CodingDodo Avatar
          return "https://avatars.githubusercontent.com/u/81346769?s=400&u=614004f5f4dace9b3cf743ee6aa3069bff6659a2&v=4";
        } else if (this.message.author) {
          return this.message.author.avatarUrl;
        } else if (this.message.message_type === "email") {
          return "/mail/static/src/img/email_icon.png";
        }
        return "/mail/static/src/img/smiley/avatar.jpg";
      },
    });
  }
);
Overriding getter 'avatar', OdooBot is now a CodingDodo

Note that this is pure JavaScript override and no "Odoo magic" will save you here, the super is not called for you and you have to be really careful when doing that. Any other override after yours on the same getter will override yours!

Using directly Object.defineProperty works but you are closing the door to future easy extensions from other modules. It is important to keep that in mind as Odoo is a module-driven Framework.

Solution 2 - Putting the defineProperty inside the Component setup function (overridable).

It would be better if the standard getter would call a regular function called _get_avatar that could be overrideable by other modules.

With the patch we also cannot override the constructor so we will use a function available on each OWL Component called setup.

setup is called at the end of the constructor of an OWL Component and can be overridden, patched, etc

const { patch } = require("web.utils");

patch(
  components.Message,
  "owl_tutorial/static/src/components/message/message.js",
  {
    /**
     * setup is run just after the component is constructed. This is the standard
     * location where the component can setup its hooks.
     */
    setup() {
      Object.defineProperty(this, "avatar", {
        get: function () {
          return this._get_avatar();
        },
      });
    },
    /**
     * Get the avatar of the user. This function can be overriden
     *
     * @returns {string}
     */
    _get_avatar() {
      if (
        this.message.author &&
        this.message.author === this.env.messaging.partnerRoot
      ) {
        // Here we replace the Robot with the better CodingDodo Avatar
        return "https://avatars.githubusercontent.com/u/81346769?s=400&u=614004f5f4dace9b3cf743ee6aa3069bff6659a2&v=4";
      } else if (this.message.author) {
        return this.message.author.avatarUrl;
      } else if (this.message.message_type === "email") {
        return "/mail/static/src/img/email_icon.png";
      }
      return "/mail/static/src/img/smiley/avatar.jpg";
    },
  }
);

In that way, the function can now be overridden again by another patch in the future.

// Can be overriden again now
patch(
  components.Message,
  "another_module/static/src/components/message/message_another_patch.js",
  {
    _get_avatar() {
      let originAvatar = this._super(...arguments);
      console.log("originAvatar", originAvatar);
      if (originAvatar === "/mail/static/src/img/odoobot.png") {
        return "https://avatars.githubusercontent.com/u/81346769?s=400&u=614004f5f4dace9b3cf743ee6aa3069bff6659a2&v=4";
      }
      return originAvatar;
    },
  }
);

Getter and setters are a cool feature of Classes but leave little space for extendability, it is sometimes better to make a getter as a shortcut to an actual classic function that can be extended later.

Solution 3 - Force apply the "patchMixin" on the Component and replace it in the Components tree.

The last solution is to create another Component equal to the old Component returned with patchMixin, then replace them where they are used in parent Components.

const { QWeb } = owl;
const patchMixin = require("web.patchMixin");

// Create patchable component from original Message
const PatchableMessage = patchMixin(components.Message);
// Get parent Component 
const MessageList = require("mail/static/src/components/message_list/message_list.js");

PatchableMessage.patch(
  "owl_tutorial/static/src/components/message/message.js",
  (T) => {
    class MessagePatched extends T {
      /**
       * @override property
       */
      get avatar() {
        if (
          this.message.author &&
          this.message.author === this.env.messaging.partnerRoot
        ) {
          // Here we replace the Robot with the better CodingDodo Avatar
          return "https://avatars.githubusercontent.com/u/81346769?s=400&u=614004f5f4dace9b3cf743ee6aa3069bff6659a2&v=4";
        } else if (this.message.author) {
          return this.message.author.avatarUrl;
        } else if (this.message.message_type === "email") {
          return "/mail/static/src/img/email_icon.png";
        }
        return "/mail/static/src/img/smiley/avatar.jpg";
      }
    }
    return MessagePatched;
  }
);
MessageList.components.Message = PatchableMessage;

We had to import the parent MessageList component to redefine its own components and put our own PatchableMessage.

Be careful with that solution. If the original Message Component was patched in any other module (it is the case in the snailmessage module) then all the previous patches will be lost.

If you are certain you want to use that way of overriding, the good thing is that now, every other module can extend our PatchableMessage and override easily our function.

Solution 4 - Creating a patchInstanceMethods copied from the Odoo 15 version of the patch method.

Odoo 15 already has a solution for this common problem of wanting to patch the instance method., directly implemented in the patch function. As long as it is not backported into Odoo 14, we can create a utils file and add that function.

So let's create a utils.js file and add this:

odoo.define(
  "owl_tutorial_extend_override_components.utils",
  function (require) {
    "use strict";
    class AlreadyDefinedPatchError extends Error {
      constructor() {
        super(...arguments);
        this.name = "AlreadyDefinedPatchError";
      }
    }
    const classPatchMap = new WeakMap();
    const instancePatchMap = new WeakMap();
    var utils = {
      /**
       * Inspired by web.utils:patch utility function
       *
       * @param {Class} Class
       * @param {string} patchName
       * @param {Object} patch
       * @returns {function} unpatch function
       */
      patchClassMethods: function (Class, patchName, patch) {
        let metadata = classPatchMap.get(Class);
        if (!metadata) {
          metadata = {
            origMethods: {},
            patches: {},
            current: [],
          };
          classPatchMap.set(Class, metadata);
        }
        if (metadata.patches[patchName]) {
          throw new Error(`Patch [${patchName}] already exists`);
        }
        metadata.patches[patchName] = patch;
        applyPatch(Class, patch);
        metadata.current.push(patchName);

        function applyPatch(Class, patch) {
          Object.keys(patch).forEach(function (methodName) {
            const method = patch[methodName];
            if (typeof method === "function") {
              const original = Class[methodName];
              if (!(methodName in metadata.origMethods)) {
                metadata.origMethods[methodName] = original;
              }
              Class[methodName] = function (...args) {
                const previousSuper = this._super;
                this._super = original;
                const res = method.call(this, ...args);
                this._super = previousSuper;
                return res;
              };
            }
          });
        }

        return () => unpatchClassMethods.bind(Class, patchName);
      },

      /**
       * Patch an object and return a function that remove the patch
       * when called.
       *
       * @param {Object} obj Object to patch
       * @param {string} patchName
       * @param {Object} patch
       */
      patchInstanceMethods: function (obj, patchName, patch) {
        if (!instancePatchMap.has(obj)) {
          instancePatchMap.set(obj, {
            original: {},
            patches: [],
          });
        }
        const objDesc = instancePatchMap.get(obj);
        if (objDesc.patches.some((p) => p.name === patchName)) {
          throw new AlreadyDefinedPatchError(
            `Patch ${patchName} is already defined`
          );
        }
        objDesc.patches.push({
          name: patchName,
          patch,
        });

        for (const k in patch) {
          let prevDesc = null;
          let proto = obj;
          do {
            prevDesc = Object.getOwnPropertyDescriptor(proto, k);
            proto = Object.getPrototypeOf(proto);
          } while (!prevDesc && proto);

          const newDesc = Object.getOwnPropertyDescriptor(patch, k);
          if (!objDesc.original.hasOwnProperty(k)) {
            objDesc.original[k] = Object.getOwnPropertyDescriptor(obj, k);
          }
          if (prevDesc) {
            const patchedFnName = `${k} (patch ${patchName})`;

            if (prevDesc.value && typeof newDesc.value === "function") {
              makeIntermediateFunction(
                "value",
                prevDesc,
                newDesc,
                patchedFnName
              );
            }
            if (prevDesc.get || prevDesc.set) {
              // get and set are defined together. If they are both defined
              // in the previous descriptor but only one in the new descriptor
              // then the other will be undefined so we need to apply the
              // previous descriptor in the new one.
              newDesc.get = newDesc.get || prevDesc.get;
              newDesc.set = newDesc.set || prevDesc.set;
              if (prevDesc.get && typeof newDesc.get === "function") {
                makeIntermediateFunction(
                  "get",
                  prevDesc,
                  newDesc,
                  patchedFnName
                );
              }
              if (prevDesc.set && typeof newDesc.set === "function") {
                makeIntermediateFunction(
                  "set",
                  prevDesc,
                  newDesc,
                  patchedFnName
                );
              }
            }
          }

          Object.defineProperty(obj, k, newDesc);
        }

        function makeIntermediateFunction(
          key,
          prevDesc,
          newDesc,
          patchedFnName
        ) {
          const _superFn = prevDesc[key];
          const patchFn = newDesc[key];
          newDesc[key] = {
            [patchedFnName](...args) {
              const prevSuper = this._super;
              this._super = _superFn.bind(this);
              const result = patchFn.call(this, ...args);
              this._super = prevSuper;
              return result;
            },
          }[patchedFnName];
        }
      },

      /**
       * Inspired by web.utils:unpatch utility function
       *
       * @param {Class} Class
       * @param {string} patchName
       */
      unpatchClassMethods: function (Class, patchName) {
        let metadata = classPatchMap.get(Class);
        if (!metadata) {
          return;
        }
        classPatchMap.delete(Class);

        // reset to original
        for (let k in metadata.origMethods) {
          Class[k] = metadata.origMethods[k];
        }

        // apply other patches
        for (let name of metadata.current) {
          if (name !== patchName) {
            patchClassMethods(Class, name, metadata.patches[name]);
          }
        }
      },

      /**
       * @param {Class} Class
       * @param {string} patchName
       */
      unpatchInstanceMethods: function (Class, patchName) {
        return webUtilsUnpatch(Class.prototype, patchName);
      },
    };
    return utils;
  }
);

We can now use the patchInstanceMethods function on any Component:

const {
  patchInstanceMethods,
} = require("owl_tutorial_extend_override_components.utils");
patchInstanceMethods(components.Message.prototype, "messageFirstPatch", {
  /**
   * Get the avatar of the user. This function can be overriden
   *
   * @returns {string}
   */
  get avatar() {
    if (
      this.message.author &&
      this.message.author === this.env.messaging.partnerRoot
    ) {
      // Here we replace the Robot with the better CodingDodo Avatar
      return "https://avatars.githubusercontent.com/u/81346769?s=400&u=614004f5f4dace9b3cf743ee6aa3069bff6659a2&v=4";
    } else if (this.message.author) {
      return this.message.author.avatarUrl;
    } else if (this.message.message_type === "email") {
      return "/mail/static/src/img/email_icon.png";
    }
    return "/mail/static/src/img/smiley/avatar.jpg";
  },
});

patchInstanceMethods(components.Message.prototype, "messageSecondPatch", {
  /**
   * Override of override
   *
   * @returns {string}
   */
  get avatar() {
    let originAvatar = this._super(...arguments);
    return originAvatar + "?overridenPatch=Yes";
  },
});
In my opinion, his is the cleanest way to do it, but yes it involves you adding a utils function. You will then have to communicate with your team so everybody now uses this new patching method on the Components.

Conclusion

In this article, we reviewed the two main available methods of patching, overriding, and extending Odoo 14 OWL Components. The patchfunction available when the Component is returned with the patchMixin and the global patch function from "web.utils" when we want to override basic functions of a Component.

I hope this guide was helpful to you on your journey customizing OWL Components in Odoo 14. In another article, we will so how to create Odoo 14 OWL Components from scratch and take a look at all the adapters available to us to mix OWL Components with good old Odoo Widgets.

The repository for this tutorial is available here:

GitHub - Coding-Dodo/owl_tutorial_extend_override_components
Contribute to Coding-Dodo/owl_tutorial_extend_override_components development by creating an account on GitHub.

Please consider subscribing to be alerted when new content is released here on Coding Dodo.

You can also follow me on Twitter and interact with me for requests about the content you would like to see here!

Buy Me A Coffee

Join the conversation

Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Coding Dodo - Odoo, Python & JavaScript Tutorials.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.