16

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?

1
  • 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, 2019 at 20:40

3 Answers 3

12

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}
5
  • 1
    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, 2019 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, 2019 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, 2019 at 22:19
  • 1
    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, 2019 at 22:20
  • 2
    It does seem to be the case this behavior is LWC specific. How unfortunate!
    – tsalb
    Mar 7, 2019 at 20:38
5

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

1
  • 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, 2019 at 23:01
0

If you are extending the LightningElement, you can use the new class as a replacement.

import { NavigationMixin } from 'lightning/navigation';
import UtilityLWC from 'c/UtilityLWC';

export default class ConsumerLWC extends NavigationMixin(UtilityLWC) {
    connectedCallback() {
        this.foo();
        this.bar();
    }
}

The exception are any decorators (e.g. api, track, wire) you can export * from 'lwc' in your UtilityLWC and import them - though I believe this has more to do with eslint/lwr than technical reasons (I get a console error stating that api isn't being imported from 'lwc').

Though, this might not be the outcome you're expecting as all LightningElements are new instances - the foo and bar properties of component A are not the same as foo and bar of component B.

For me, a typical use case is supplying shared CSS for components without resorting to a synthetic dom:

import { LightningElement } from 'lwc';
import LoadCSS from 'utils/css';

export class LightningElementSlds extends LightningElement {

    constructor() {
        super();

        LoadCSS(
            '/lib/slds/styles/salesforce-lightning-design-system.min.css',
            this
        );
    }
}

All components that extend this LightingElementSlds will get the extra constructor as presented. As a point of caution, I wouldn't recommend connectedCallback, disconnectedCallback, renderedCallbacks in this new class. The documentation provided does not instruct one to call super in these children classes so any code shared would need proper documentation to call super.renderedCallback() at the top of each.

Best to handle it in the constructor OR as custom methods as you are doing in your example.

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.