LWC – Importing Wire Without Extending LightningElement

lightning-web-componentslwc-wire-adapterwire

Am I allowed to use the wire service in an ES module, i.e. a 'headless' utility class?
Or, must I extend LightningElement also?

import {wire} from 'lwc';
import sapLogin from '@salesforce/apex/SAPService.login';//cachable=true

export class SapSession
{
    //sessionInfo = undefined;//EDIT oops don't need to declare this
    //the wire below does that

    @wire(sapLogin)sessionInfo

    get Cookie() {
        return sessionInfo.data.Cookies;//delimited string of cookies returned        
    }
    get Error() {
        return sessionInfo.error;//wire failure returns error poperty      
    }
}

When I deploy the above class, there are no compilation issues. But, it also doesn't seem to be working in the consuming lwc, either, e.g.

import { LightningElement } from 'lwc';
import { SapSession } from 'c/sapSession';

export default class someUiComponent extends LightningElement {

    session = undefined;

    // One of the standard lifecycle callbacks you can include in an LWC
    connectedCallback() {
        this.session = SapSession;
    }

}

someUiComponent.html

<template>
    <div>
        little test:
      
        <div if:true={session}>
            <div if:true={session.Cookie}> {session.Cookie}</div>
            <div if:true={session.Error}> {session.Error}</div>
        </div>
    </div>
</template>

Best Answer

In LWC OSS, you can freely use wire wherever you want. In Salesforce, though, you can only use wire methods inside the current LightningElement. Even if you made the service class extend LightningElement, you'd still find it won't work without adding the component to the DOM.

That said, you can add it to the DOM and have it work:

<c-sap-session></c-sap-session>

...

const this.template.querySelector('c-sap-session').Cookie;

Also make sure that you use the @api decorator:

import {api, wire, LightningElement } from 'lwc';
import sapLogin from '@salesforce/apex/SAPService.login';//cachable=true

export class SapSession extends LightningElement
{
    @wire(sapLogin, {})sessionInfo

    @api get Cookie() {
        return sessionInfo.data.Cookies;//delimited string of cookies returned        
    }
    @api get Error() {
        return sessionInfo.error;//wire failure returns error poperty      
    }
}

Finally, note that wire methods are not provisioned until after connectedCallback and the initial renderedCallback. I'd recommend setting up an onload handler to let the parent know when the data is ready:

import {api, wire, LightningElement } from 'lwc';
import sapLogin from '@salesforce/apex/SAPService.login';//cachable=true

export class SapSession extends LightningElement
{
    sessionInfo;
    @wire(sapLogin, {})sessionInfoHandler(result) {
      this.sessionInfo = result;
      if(result.data) {
        this.dispatchEvent(new CustomEvent('load', { detail: result.data }))
      }
    }
    @api get Cookie() {
        return sessionInfo.data.Cookies;//delimited string of cookies returned        
    }
    @api get Error() {
        return sessionInfo.error;//wire failure returns error poperty      
    }
}

Or something similar.


Alternatively, you can call the method imperatively, which avoids the need for all this extra work:

import { api } from 'lwc'
import sapLogin from '@salesforce/apex/SAPService.login';//cachable=true

export class SapSession {
    sessionInfo = {}
    async constructor() {
        this.sessionInfo = await sapLogin({})
    }

    @api get Cookie() {
        return sessionInfo.data.Cookies;//delimited string of cookies returned        
    }
    @api get Error() {
        return sessionInfo.error;//wire failure returns error poperty      
    }
}

However, even though we call this in the constructor, it is guaranteed to not be available during the connectedCallback of components that use it.

Some additional work may be necessary, but any of these designs should get you close.

tl;dr wire only works in a DOM-connected LightningElement, so making a service class won't help; call the method imperatively, or accept that you need to embed the component in every component that needs this data.