35

How can expression functions similar to Visualforce and Lightning Aura Components be achieved within lightning web components?

For example, <template if:true={index % 5 == 0}><br></template> expression index % 5 == 0 is not compyling at all.

accountList.html

<template>
    <template if:true={accounts.data}>
        <template for:each={accounts.data} for:item='item' for:index='index'>
            <!--LWC DOES NOT SUPPORT EXPRESSION FUNCTIONS-->
            <!--<template if:true={index % 5 == 0}><br></template>-->
            {item}
        </template>
    </template>
</template>

accountList.js

import {LightningElement, wire} from 'lwc';
import getAccounts from '@salesforce/apex/TestController.getAccounts';

export default class AccountList extends LightningElement {

    @wire(getAccounts) accounts;

}

Since LWC doesn't support Expression Functions, I'd like to know how to implement them.

1
23

There are multiple options. First, note, that all computations are in JavaScript, not in the markup.

Option 1 You implement the display of your {item} (which is currently only the JSON object), into a dedicated component AccountListItem. And implement in there the logic that @POZ suggested.

Something along the lines of:

import {LightningElement, api} from 'lwc';

export default class AccountListItem extends LightningElement {
   @api index;
   @api item;

   get isMod5() {
     return this.index % 5 == 0;
   }
}

Option 2 You use a wired function instead of a wired property. And then do some fun stuff with a changed data model. Although this is hacky.

import {LightningElement, wire, track} from 'lwc';
import getAccounts from '@salesforce/apex/TestController.getAccounts';

export default class AccountList extends LightningElement {

    @wire(getAccounts)
    wiredAccounts({ error, data}) {
       if (error) {
         // do error handling, please
       } else if (data) {
         this.accounts = data.map((acct, index) => {
               return {
                  acct: acct,
                  isMod5: index % 5 == 0 ? true : false
               }
           })
       }
    }
13

EDIT: changed my answer because previous one was wrong:

You have to write a JS expression that paginates your list like this:

get getContacts() {
  return [
    { id: 0, items: ['a', 'b', 'c', 'd', 'e']}, // Page 0, max 5 item
    { id: 1, items: ['f', 'g']} // Page 1
  ];
}

Then use two iterations in your template:

<template for:each={getContacts} for:item="page">
  <div key={page.id}>
    <template for:each={page.items} for:item="contact">
      {contact}
    </template>
  </div>
</template>
4
  • 2
    How does index get passed to this function? – Jayant Das Dec 21 '18 at 17:26
  • Thanks for pointing this out @JayantDas, I've edited my answer. – POZ Dec 21 '18 at 17:41
  • This doesn't work, as the first element inside a for:each must have a 'key' attribute. This makes rendering lists of lists quite tricky in LWC – Rob Baillie Aug 21 '19 at 12:20
  • Yes, you're right Rob, I've updated my answer to include a page id (a simple index) that acts as a key for the first level iteration. – POZ Aug 23 '19 at 7:27
12

Because expressions are not allowed in Lightning Web Component template, you would have required a JavaScript getter function here. Refer to HTML Template Directives documentation for details.

Excerpt below from documentation:

if:true|false={expression}

..... (some text omitted)

The engine doesn’t allow computed expressions. To compute the value of expression, use a getter in the JavaScript class.

But, in your case you need to pass the index var in the iteration, so even having a getter function doesn't seem to do the job here.


I am not completely sure of the use case here as why you want to utilize the index within the iteration, but as I don't see a way to set the index to a JS function, here's an approach that you can take by utilizing Composition.

Even though this approach solves the problem here, but still seems to be an overhead of creating another component to achieve this what could have been achieved inline.

The approach here is:

  • Create a custom LWC
  • Pass the index value to the component within the iteration in the parent component
  • Compute the value of the index within that component and perform your required operation

Let's say I create a custom LWC as c-my-index-component, which looks like as below:

<template>
    <template if:true={indexVar}>
        // do something
    </template>
</template>

In the JS, I have this property defined as:

import { LightningElement, api, track } from 'lwc';
export default class MyCustomIndex extends LightningElement {
    @track myVariable;

    set indexVar(value) {
        this.myVariable = value;
    }

    @api
    get indexVar() {
        return (this.myVariable % 5 === 0)
    }
}

And then finally I use it in my existing component in iteration as below:

<template for:each={accounts.data} for:item='item' for:index='index'>
    <c-my-index-component key={item.Id} index-var={index}></c-my-index-component>
     // my other code
</template>
1
  • Great to know about using this set then get pattern. I couldn't think of a way to get input from the UI and have that affect the get – Brian Miller Feb 23 '20 at 20:03
0

I might get fair bit of criticism for writing this, but here it goes

I remember the days when I used to go through a lot of expressions in Visualforce Pages, and it is REALLY hard to read/debug such inline expressions.

I feel it is quite helpful as LWC forces us to separate logic from HTML by using getters:

get someCondition() { return this.someVar == 5; }

It also helps the framework to get rid of parsing completely different syntax, implementing/maintaining code to keep it working.

By utilizing the getters we get to use Javascript to apply any sort of operations before evaluating the condition output.

BUT

There could be situations/implementations where you might end up writing a lot of getters to get the work done. That's where you'll want to write inline expressions as it adds the ease while implementing.

below example illustrates how I implemented this with also utilizing JS, but this does involve some performance kicks as well:

<c-lwc-if condition="(this.a == this.b) && (this.c == 6 || this.b == 'easypeasy')" scope={scope}>
    <div slot="if">
        Render some HTML
    </div>
    <div slot="else">
        Render some other HTML
    </div>
</c-lwc-if>

The condition property is actually JavaScript code but is limited to access the passed scope.

I don't really use the one mentioned above & lean towards the getters for implementing expressions-like use cases

One of the major use cases involved rendering different components based on a condition, which would require code something like:

<template>
    <template if:true={condition1}>
        component 1
    </template>
    <template if:true={condition2}>
        component 2
    </template>
    <template if:true={condition3}>
        component 3
    </template>
    ... and so on
</template>

Which within Aura and LWC would be easily implemented using expressions and one would not need to write multiple boolean properties/getters to get the job done.

You could also use multiple component templates to implement a solution for this, but wouldn't that require a new implementation for each time you had such requirement.

So I took some inspiration from classic switch-case control flow statements in varous languages and implemented something :

markup:

<c-lwc-switch expression="this.a" scope={scope}>
    <c-lwc-case value="4">
        <div>Print Something</div>
    </c-lwc-case>
    <c-lwc-case value="5">
        <div>Print Something else</div>
    </c-lwc-case>
    <c-lwc-case value="'test'">
        <div>Print test</div>
    </c-lwc-case>
    <c-lwc-case default>
        <div>Print Default</div>
    </c-lwc-case>
</c-lwc-switch>

js:

get scope(){
    return {
        a : this.b
    }
}

Above solution is reusable and solves the problem declarative and helps us with lack of expressions without actually using expressions.

The switch-case reusable solution helps implement the mentioned use case quickly and reduces my efforts, though it still might require some security checks.

here's the implementation: https://github.com/prashantk0001/lwc-if-expressions https://github.com/prashantk0001/lwc-switch-expressions

-1

You can also achieve this via CSS:

  1. First wrap a div around the template you want to conditionally display based on list index.
  2. In your CSS, select the div at corresponding index and switch out the display property:
div:not(:first-of-type) {
  display: none;
}

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.