9

I'm looking to modularize my LWC without having to embed them in the template. Ideally, would this kind of pattern be feasible? I'm not sure if it's my lack of ES6 knowledge (modules) or that LWC blocks this but this is what I'm attempting:

UtilityLWC.js

import { LightningElement, api } from 'lwc';

export default class UtilityLWC extends LightningElement {

  @api
  foo() {
    console.log('foo from UtilityLWC');
  }

  @api
  bar() {
    console.log('bar from UtilityLWC');
  }

}

ConsumerLWC.js


import { LightningElement, api, track, wire } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import UtilityLWC from 'c/UtilityLWC'; // doesn't work
// import * as util from c/UtilityLWC; // also doesn't work
// import { foo, bar } from c/UtilityLWC; // nope

export default class ConsumerLWC extends NavigationMixin(LightningElement) {

  connectedCallback() {
    // None of these work
    UtilityLWC.foo();
    UtilityLWC.bar();
    // util.foo();
    // util.bar();
    // foo();
    // bar();
  }

}

So the easy way out is to add <c:utilityLWC> inside ConsumerLWC.html and use standard querySelector to reach inside an call foo() / bar() but is that the only way to do this?

Edit: I am aware of the general JS code re-use patterns (example in my lwc-utils repo). This is more a question of, is it possible to have that kind of pattern but also, for example, have reusable wires?

  • As of Spring 19, this seems to infeasible. I will revisit this every now and then because I would prefer not to use inheritance (custom mixins or extensions) OR templating to supply reusable wires. It would be really nice to import pre-made wires from a service module. Some of my wires use some JS to post-process. – tsalb Mar 7 at 20:40
8

I am not ES6+ or JS expert, but there's a section for this on the documentation - Share JavaScript Code and also there are examples on lwc-recipe for this. In general, the export works as documented for ES6 module.

From LWC documentation:

A module can export a single default function or variable.

A module can also export named functions or variables.

So it doesn't seem that you can actually export the whole class. You will need to modify your shared JS based on the approach as mentioned in the documentation, i.e., you will need to export the functions as named functions. For further details, refer to the links mentioned above.


As a quick example, below is an approach for exporting and using a named function.

Let's say I have a shared JS as below:

sharedJS.js

const mySharedHelloWorld = () => {
     return 'Hello Shared World!'; 
}

export {mySharedHelloWorld};

And that I want to use this on another component, I will go doing as below in the component.

helloWorldJS.js

import { mySharedHelloWorld} from 'c/sharedJS';

export default class MyHelloWorld extends LightningElement {

    myHelloWorld = mySharedHelloWorld();
}

helloWorld.html

{myHelloWorld}
  • This pattern I'm aware of (and currently use). It's a bit abstracted in my example, but it would be the first step to leveraging re-usable wires (of our own design) that don't need to be exposed as an element on the template. I have some other things in design, such as an Aura Message Broker (LWC to aura without a wrapper) that would benefit from this pattern. – tsalb Mar 6 at 22:01
  • Well with the way it is as of today, it doesn't seem that there's a direct way of exporting a class other than named functions. – Jayant Das Mar 6 at 22:12
  • Yeah that seems to be the case - I'm trying to figure out if this is a limitation of ES6 or LWC. I'll keep this thread open for a bit to see if anyone can shed insight on that. – tsalb Mar 6 at 22:19
  • Well, it seems to be enforced by LWC primarily because if you look at the export documentation for ES6 modules, it does mention a class. But in LWC, from the docs the only available options mentioned are as quoted in the answer. – Jayant Das Mar 6 at 22:20
  • 2
    It does seem to be the case this behavior is LWC specific. How unfortunate! – tsalb Mar 7 at 20:38
1

Okay, so, I was thinking about this and I couldn't find a way to expose a whole Javascript class in another component, but I did come up with a clever little bit of "syntactic sugar" to make it work like we all want. Since the import only allows methods or variables, I simply created a const variable which was an object, whose properties are the class methods. Then, I only had to export/import that variable and voila! Now we can access them like they're classes.

someService.js

const foo = () => {
     return 'foo'; 
}
const bar = () => {
     return 'bar';
}
const SomeService = {
      foo: foo,
      bar: bar,
}

export {SomeService};

app.js

import { SomeService} from 'c/someService';

export default class App extends LightningElement {

    myFoo = SomeService.foo();
    myBar = SomeService.bar();
}

app.html

<template>
{myFoo} {myBar}
</template>

Now, it's not exactly the same thing as importing a whole Javascript Class, but, it works in similar ways. I tested it and methods in the Service class can call on each other, so "Private" methods are simply methods that aren't included in the "SomeService" object. Also, you can add other vars as you like, to that object, and now you can use it just like an External Service Class.

Please feel free to improve on my answer, here's the Playground Link: https://developer.salesforce.com/docs/component-library/tools/playground/bT9aPFeTv

  • Ah yes, this is a known pattern when you export regular functions. I'm specifically after exporting an entire LightningElement class, because I want to import @api, @wire etc. – tsalb Jul 4 at 23:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.