The documentation covers this explicitly:
To compute a value for a property, use a JavaScript getter. For example, to convert the name to all uppercase letters, use a getter function in the JavaScript class, not an expression in the template.
Getters are much more powerful than expressions because they’re JavaScript functions. Getters also enable unit testing, which reduces bugs and increases fun.
Your example code would look like this:
<!-- MyComponent.html -->
<div class={tabClass}>
// MyComponent.js
get tabClass() {
return this.active ? 'slds-tabs--path dimmed' : 'slds-tabs--path';
}
Your concerns about separating UI from controller logic do not apply here as this is not a "controller". That MVC pattern is an Aura-ism. This is the code which drives your component's functionality so it makes sense that your JS would know about class names.
Edit:
After you changed your question, I think the best way to do what you're trying to do is to have a custom "contact" component which when clicked, becomes active, and has an
@api active;
property, which you can call from the parent. In your comments below you say this is too much overhead, but this is the proper way to do this if you wish to manage the state of the contact. In your code example you are only toggling one class, but in a real world scenario there are many different things which you might want to change about a contact and having many expressions makes the code harder to read and harder to test.
For most list items the suggestion is to have an individual element for each list item like so:
<c-list>
<c-list-item></c-list-item>
<c-list-item></c-list-item>
<c-list-item></c-list-item>
</c-list>
You can not do it as of now. The developer guide clearly mentions it.
Migrate Interfaces
Implementing an Aura interface enables you to receive context data or
to surface your custom component in different contexts, such as in the
Lightning App Builder or Community Builder.
To receive context data in a Lightning web component, import the corresponding module. To surface a component in different contexts, use the targets metadata in your *.js-meta.xml configuration file.
Aura interfaces that are not listed don’t currently have an equivalent
in Lightning Web Components.
these are the interfaces which lightning Web Component has an alternative:-
lightning:hasPageReference
flexipage:availableForAllPageTypes
flexipage:availableForRecordHome
force:hasRecordId
force:hasSObjectName
forceCommunity:availableForAllPageTypes
lightningcommunity:allowInRelaxedCSP
Unsupported Experiences and Tools
Lightning Web Components doesn’t currently support these Salesforce
experiences and tools. To use a Lightning web component with these
experiences and tools, wrap the component in an Aura component.
Lightning Out
Lightning Components for Visualforce
Standalone Apps
Salesforce Console (Navigation Item API, Workspace API, UtilityBar API)
Utility Bars
URL Addressable Tabs
Flows
Snap-ins Chat
Lightning for Gmail, Outlook Integration
EMP API, Conversation Toolkit API, Omni Toolkit API, Quick Action API
Standard Action Overrides, Custom Actions, Global Actions, List View Actions, Related List View Actions
Chatter Extensions
Reference:- Supported Salesforce Experiences and Tools and Migrate Interfaces
Best Answer
I have found the LWC docs to offload some of this to the MDN docs, so bringing this one layer higher (in the actual LWC docs would be nice) would have made it easier for us.
In scenarios where there are two components of the same type on your template, the equivalent to
aura:id
is to use adata-id
tag and select it like this:this.template.querySelector('[data-id="userform"]')