Thursday, December 27, 2018

Lightning Web Components - Conditional formatting and iterators

One of the really elegant parts of Lightning Components was the ability to conditionally apply classes based on data.

This is something that is no longer available to us, as the expressions we are allowed to put into templates are now limited to either:

  • A property of the Javascript class (or a sub-property that can be referenced through a top level property).
  • A getter, that accepts no parameters.

I’ve already mentioned a change in this area in this blog post about building re-usable components, but I felt it was time to drill into this just a little further...

Update - 28/12/2018

As seems to be increasingly the case, as soon as I posted this I was corrected on a couple of points. I've since updated the post to include that feedback. Hopefully the result isn't too confusing!

The scenario

Let’s say we want to render a list of objects. There’s a flag 'isSelected' on each of the records, and if that flag is set we want to change the rendering for that particular record.

JSON data:

[ { "id" : 1
  , "name" : "Anne"
  , "isSelected" : false },
  { "id" : 2
  , "name" : "Bob"
  , "isSelected" : true },
  { "id" : 3
  , "name" : "Carla"
  , "isSelected" : true }
]

Required Output:

<ul>
  <li class="record">Anne</li>
  <li class="selected record">Bob</li>
  <li class="selected record">Carla</li>
</ul>

Lightning Component

In a Lightning Component, this would be near trivial, as we could use a ternary operator in the template to render the inclusion of the 'selected' class conditionally.

<ul>
  <aura:iteration items="{!v.records}" var="thisRecord">
    <li class="{!(thisRecord.isSelected?'selected':'' ) + ' record' }">{!thisRecord.name}
  </aura>
</ul>

The reason this is so simple, is that we could put proper expressions into our replacements in Lightning Components, giving us fantastic flexibility in the output for each individual attribute.

Unfortunately (for this case), this isn't possible in Lightning Web Components...

Lightning Web Component

First up, let's just clarify what we mean when we say we can no longer do the string concatenation, or the ternary operator in an attribute expression, as I detailed in my earlier post.

What we mean is, we can’t do the following:

<ul>
  <template for:each={records} for:item="thisRecord">
    <li class="{thisRecord.isSelected?'selected':'' + ' record' }">{thisRecord.name}</li>
  </template>
</ul>

All we can do is reference a single value from our data held against our Javascript object, or call a getter against it. E.g. (not that this template is of much use to us right now)

<ul>
  <template for:each={records} for:item="thisRecord">
    <li class={thisRecord.isSelected}>{thisRecord.name}</li>
  </template>
</ul>

OK - so what other options do we have?

Option 1 - Build your class lists in your data:

So, we could build up the list of classes that we want to render against each record in our data - once the data is populated from where-ever, we can loop over the records and update the data so we end up with something like:

JSON data:
[ { "id" : 1
  , "name" : "Anne"
  , "isSelected" : false
  , "classes" : "record" },
  { "id" : 2
  , "name" : "Bob"
  , "isSelected" : true
  , "classes" : "selected record" },
  { "id" : 3
  , "name" : "Carla"
  , "isSelected" : true
  , "classes" : "selected record" }
]

We can then render the required output like this:

<ul>
  <template for:each={records} for:item="thisRecord">
    <li key={thisRecord.id} class={thisRecord.classes}>{thisRecord.name}</li>
  </template>
</ul>

Pros:

  • The template is simple.
  • Since we're processing in Javascript, we can draw on any information and make whatever complex rules we want.

Cons:

  • We need to process the data after we’ve built it. This means we can no longer use a wired property, and need to use either a wired function or imperatively called Apex. This may not be a big deal, but it's worth noting.
  • Data retrieved from Apex is immutable, so if this is the only thing we need to add to the data, then may find that we need to copy data into new objects, or add a new data structure in order to get the classes property added - that can be a pain.
  • The logic for the classes that each record should have assigned is held in Javascript. I recognise that this is the direction we seem to be going, but I still like to avoid this where possible.

Option 2 - Use a template 'if' and repeat the li tag.

If we want to avoid doing anything complex in our Javascript, we can add template 'if's into the markup, and conditionally render the <li> tag in its two different forms.

For example, we could do the following:

<ul>
  <template for:each={records} for:item="thisRecord">
    <template if:true={thisRecord.isSelected}>
      <li key={thisRecord.id} class="selected record">{thisRecord.name}</li>
    </template>
    <template if:false={thisRecord.isSelected}>
      <li key={thisRecord.id} class="record">{thisRecord.name}</li>
    </template>
  </template>
</ul>

Pros:

  • The Javascript doesn't contain any of the logic for the conditional rendering.

Cons:

  • We're breaking the "Don't repeat yourself" (DRY) principle, and repeating the structure of the <li> tag in each side of the IF condition. In this simple case this may not seem like a big deal, but still - any change to that rendering now needs to be made in 2 places, instead of 1. And let's be honest, how often is the case this simple? We'll probably find that we have to copy a LOT of the template to work like this.

Option 3 - Use a template 'if', and change our CSS.

Another alternative is to use the template if, but to isolate the part that changes from the part that doesn't. That is, we introduce HTML inside our 'if:true' that only exists in order to apply the formatting that should be assigned to the 'isSelected' records.

That is, we do the following in our template, to introduce a new, standalone, div that has the 'selected' class applied, and then wrap the content of the <li> in another div.

<ul>
  <template for:each={records} for:item="thisRecord">
    <li key={thisRecord.id} class="record">
        <template if:true={thisRecord.isSelected}>
            <div class="selected"></div>
        </template>
        <div>{thisRecord.name}</div>
    </li>
</ul>

Having done this, we can use more advanced CSS selectors to apply our 'selected' style to the div that follows the div with 'selected' as its class.

For example, let's say our 'selected' records should have a green border:

.selected+div {
    border: 1px solid green;
}

The selector '.selected+div' means 'The div that follows the tag with the class 'selected'.

You can read about CSS Selectors here.

Pros:

  • We conditionally render only the addition of the class in the template - nothing is repeated.
  • The Javascript doesn't contain any of the logic for the conditional rendering.

Cons:

  • We need to introduce additional structure into the HTML that exists purely to apply classes to other elements. This isn't ideal and can change the behaviour of other classes further down the structure (e.g. we have introduced a div here - what impact does that have?)

Option 4 - Introduce a sub component.

It would be good if we could call a getter function at run time in order to get the list of classes, along the lines of the component described in the earlier post.

The problem is that we can't call a function and pass it the context of the individual record that we are rendering.

So does that mean we can't call a function?

No, it just means that we need to narrow the context of the component down into each record before we call the function - and we can do that with a sub-component that just renders the <li>.

We can call our sub-component (recordRenderer) with something like this:

<ul>
  <template for:each={records} for:item="thisRecord">
    <c-record-renderer key={thisRecord.id} record={thisRecord}></c-record-renderer>
  <template>
</ul>

Our sub-component template can be:

  <li class={classes}>{record.name}</li>

And our sub-component javascript can be:

    import { LightningElement, api } from 'lwc';

    export default class RecordRenderer extends LightningElement {

        @api record;

        get classes() {
            if ( this.record.isSelected ) {
                return 'selected record';
            }
            return 'record'
        }
    }

Pros:

  • Both the template for the parent and sub component are very simple and focused on small aspects of the rendering - no IFs in the templates is a good thing IMO.
  • It's very possible that there will be more complex behaviour required in the future, and having the context set to be the individual record could make that behaviour much simpler to implement in the future. For example, passing data into events due to a 'click' event could be well served if we have that distinction between the parent and child components and context.

Cons:

  • OK, we have the classes logic inside the Javascript, much like in Option 1, but we don't have to process the data - the getter is called at render time. And, presumably the code in the renderer is near trivial, so maybe that's not such a big problem.
  • Update - 28/12/2018 - Unfortunately though, in this case, we generated non-valid HTML. Since we have introduced a new component, we have also introduced a new tag into our generated HTML. The result is something along the lines of ul -> c-record-renderer -> li, and this is not strictly valid. We should certainly bear this in mind when looking at individual situations.

Conclusions

Update 28/12/2018

The conclusions were re-written after the feedback that you can find in the comments below, and some more thought on the topic.

The limitations of the expressions allowed in templates makes for a less elegant solution to this kind of problem. However, that's not an altogether terrible thing. Sometimes you can have too much power and too many choices. Limitations can allow us to define recognised best-practices that are harder to break away from.

Even with those limitations, there are many different ways in which we can implement any required functionality, and even this simple case has 4 options of unequal merit

So which way should we go?

Well, that depends on the precise scenario, and in this case we have defined something precise, but potentially a little too simplistic to define a clear best-practice.

I would find it hard to argue that Option 2 (repetition in the template) is ever worth the pain - repeating chunks of template or code is rarely a good idea, even in the simplest of cases.

Option 1 (pre-process the data) has its merits, and as Caridy has responded below, is the recommended approach from Salesforce - that's not to be sniffed at. However, if the only reason you would be processing the data is to add a list of classes to it, I'd suggest that it shouldn't be your immediate 'go-to' solution.

My initial reaction to Option 3 (advanced CSS selectors) was that it was a decent one with few drawbacks, although the more I look at it, the more I dislike the fact that we are breaking the semantics of the HTML document we are generating. We introduce structure who's only purpose is to define the rendering behaviour of another component. That's not really how HTML is supposed to work. Ideally we have meaning in our structure, and this structure expresses no meaning.

So, even though Option 3 may often be straightforward, I'd probably shy away from it now. Just because CSS selectors can be used in this way, doesn't mean that they should.

Option 4 (decompose the component) doesn't really work in this case - it generates non-valid HTML. But that doesn't mean it never works. There will be plenty of similar scenarios where the resulting HTML is perfectly valid. In fact, I'm sure we'll often find scenarios where the sub-component we produce will be a re-usable component that we can drop into other scenarios easily. In that situation we'll surely want to decompose instead?

So where does that leave us?

Simply put - there's no clear answer, as is generally the case - the technique we use should be appropriate for the scenario we're implementing - though options 2 and 3 will rarely be appropriate..

  • If there are other good reasons to split some of the rendering into a sub-component, then splitting it out is probably a good idea - but if that results in non-valid HTML, then it's a bad idea.
  • If there are other reasons to pre-process and re-shape the data, then adding the class definition to that processing is probably a good idea - but if it forces you to clone records and re-shape them just to add a class, then maybe it's not the best idea.

I definitely take the point from Salesforce that the latter is their preferred route, but my natural instinct is pretty much always to reduce processing and make smaller things. It'll be interesting to see how much my opinion changes as I build real applications with Lightning Web Components. Based on how much my opinion has changed in the last 24 hours - I expect it to change a lot!

Thursday, December 20, 2018

Lightning Web Components - Events, and listening to your children

Update - 22/12/2018

Thanks to an anonymous comments poster from inside Salesforce, I've come to learn that this post contained some inaccuracies - a typo in some code, a forgotten parameter to 'bind' that made an example more complex than it needed to be, and a lack of experience with Shadow DOMs that led to a failure in event propagation.

I'm massively grateful to the poster for pointing out my errors, and helping to learn a little more about the capabilities. I've left the original text intact and annotated where appropriate, to correct my errors - except the typo in the example - I've fixed that.

Another fantastic inclusion in Lightning Web Components is the completely reworked events model.

De-composing functionality and building smaller, and more generic building blocks has become much simpler and much more intuitive.

In the world of Lightning Components I never got on with events. The idea of adding a Salesforce configuration for an event, registering events on the dispatcher template, and then registering listeners on the receiving template seemed really cumbersome. And then added onto that was the differences in syntax between component and application events. They just felt really unnatural.

In Lightning Web Components all this has become significantly simpler, and much more in-keeping with the standard HTML / Javascript model.

We've already seen how we can use @api allow state to be passed into our components. Now we're talking about notifying our parents when events occur.

I could go into deep detail on how this is done, but the documentation on this area is spot on, and there's no need to repeat it - follow the guide in the docs and you can't go far wrong. It's particularly well written and introduces the concept brilliantly.

That said, there has to be something to say, right?

Well, yes, and before I go into some of the less obvious limitations, let's just present a simple example:

  • In the child component, we create and dispatch an event.
  • When you include the child component, specify the handler for the event

Something along the lines of:

Child component's Javascript

import { LightningElement, track } from 'lwc';

export default class ChildComponent extends LightningElement {

    @track value;

    // Called from the onchange handler on an input
    handleValueChanged( event ) {
        this.value = event.target.value;
        this.dispatchEvent( new CustomEvent( 'valuechanged', { detail: this.value } ) );
    }
}

Parent component's template

    <c-child-component onvaluechanged={handleOnValueChanged}>

Parent component's Javascript

import { LightningElement, track } from 'lwc';

export default class ParentComponent extends LightningElement {

    @track updatedValue;

    handleOnValueChanged( event ) {
        this.updatedValue = event.detail;
    }
}

OK. So how simple is that? No Salesforce configuration to create, nice simple syntax, event handlers defined in the template, exactly the same way you would if it was a standard HTML tag

Without wanting to repeat the documentation from Salesforce, it's worth calling out a few important points:

  • dispatchEvent and CustomEvent are standard Javascript.
  • When you include the child component, you specify the handler for the event in the template.
    • The event should not start with 'on', and the attribute you assign the handler to will have 'on' appended to the start.
    • The fact we can specify the handler as 'onvaluechanged' when we create the tag is LWC specific, and for very good reason (explained later). You cannot do this with standard Web Components.
  • We can pass data from the child component in the event, by passing an object as the second parameter.
    • Note that the data can only be in the 'detail' property. If you add data to any other property you may accidentally overwrite a standard property, and if you don't use another standard property it won't be visible in the event anyway - you put data into 'detail', and that's all you have. Live with it.
    • You can pass an object, but if you do you should construct it there and then. But you probably shouldn't.

OK, that's all well and good - but where are the limitations?

Well, the main one I've found was a real surprise to me - to the point that I'm worried that I've misunderstood something.

Update - 22/12/2018

I did misunderstand something, so the next few statements are wrong. This isn't a limitation of LWC, nor even a Salesforce specific behaviour - this was me not understanding the shadow DOM model, and missing a vital piece of documentation. I've left it here for completion's sake only.

In the standard Javascript events model - all events propagate to all levels.

For example, if I have the following HTML:

    <div id="grandparent" onchange="handleChange();">
        <div id="parent">
            <div id="child">
                <input onchange="handleChange();"/>
            </div>
        </div>
    </div>

When the value of the input changes, the onchange event is handled by both the onchange handler on the input and the 'grandparent' div. Events propagate through the whole DOM, unless a handler stops it by calling 'stopPropogation' against the event.

It's generally recognised that this is a good thing, and that events should not be stopped unless there's very good reason.

However, as far as I can see, this is not true when you cross boundaries between LWCs. (Update 22/12/2018 - it IS true, but different, and explained later).

For example, if I had the above example for a child component, and included it in a parent as such:

Parent component's template

    <c-child-component onvaluechanged={handleOnValueChanged}>

And then included that in the grandparent as such:

Grandparent component's template

    <c-parent-component onvaluechanged={handleOnValueChanged}>

Assuming that the parent component does not raise a 'valuechanged' event of its own, the 'onvaluechanged' handler on the grandparent component will never get called.

It seems that you can only handle a component's event in its parent's scope. (Update 22/12/2018 - Not true, you can push events through the boundary, and how is explained later)

Note: these are actually slightly different scenarios I'm explaining, but I think it's worthwhile in order to illustrate the point. Also, there is a 'bubbles' property that you can set on the CustomEvent when you create it, although I didn't see a change in behaviour when I did that.

As I've said, I'm surprised by this behaviour, so am happy to be told I'm wrong, and learn where my mistake is.

Update - 22/12/2018

And it turns out, I HAVE been told where my mistake was, and so the following section has now been added.

It turns out that this behaviour is the default for events, but that it can be overridden. The mechanism for that is detailed here, and again, this is standard Javascript behaviour as detailed here.

In essence, what we have to do is define the event as one that both 'bubbles' and is 'composed'. The former means that it will travel upwards through the DOM, a resulting effect being that multiple event sources can be handled by the same handler. The latter means that it will cross the boundary between the Shadow DOM (the component that raised it), and into the next (the component that included the one that raised it), and then keep going all the way to the document root.

Child component's Javascript becomes

import { LightningElement, track } from 'lwc';

export default class ChildComponent extends LightningElement {

    @track value;

    // Called from the onchange handler on an input
    handleValueChanged( event ) {
        this.value = event.target.value;
        this.dispatchEvent( new CustomEvent( 'valuechanged', { detail: this.value, bubbles: true, composed: true } ) );
    }
}

This is all very well explained in the documentation, and so you should read it carefully to understand it.

Bubbling and Composed Events are extremely powerful elements, and allow you to perform some complex event management and (for example) react in many different ways to the same single event being fired. However, with that power comes great responsibility. The more 'global' in nature your events are, the harder they are to debug and the more care you need to take in naming them. I think I'm with Salesforce on this one - decompose your components and keep your events as local as possible.

Adding an event handler via Javascript

So what of the 'on' behaviour? Why is this such a cool addition?

Well, that's best explained by illustrating what we would need to do if this wasn't available to us.

Let's go back to our child component

Child component's Javascript

import { LightningElement, track } from 'lwc';

export default class ChildComponent extends LightningElement {

    @track value;

    // Called from the onchange handler on an input
    handleValueChanged( event ) {
        this.value = event.target.value;
        this.dispatchEvent( new CustomEvent( 'valuechanged', { detail: this.value } ) );
    }
}

It dispatches a 'valuechanged' event that we can handle in a parent component.

We include the child component with a simple node:

Parent component's template

    <c-child-component></c-child-component>

Note we are no longer setting onvaluechanged because, in our hypothetical scenario, this is not possible.

Now, in order to handle the event we need to attach a handler to the component in our parent component's Javascript.

First we need to find it, so we set a property on the component that we can use to retrieve it. You may default to setting an 'id', but it turns out that Salesforce will adjust the ids on nodes, so we can't rely on that. Instead, we decide to set a class:

Parent component's template

    <c-child-component class="child"></c-child-component>

Now, the parent component's Javascript. We need to hook into one of the lifecycle callbacks in order to attach our handler

You can see the docs for those functions here.

From there we find:

  • We can't use the constructor, as the component hasn't been added to the DOM yet.
  • We can't use the connectedCallback, as the component's children haven't been rendered yet.
  • We can use the renderedCallback, but this gets called multiple times - whenever any reactive properties change, so we need to protect against multiple adds.

So, maybe we can do this:

Update - 22/12/2018

In the following section I missed an important parameter that could be passed into the 'bind' onto the event listener registration. This led to a slightly convoluted solution using fat arrows. I'm leaving that text here, as I think it's an interesting aside, although at the end I'll present a simpler solution.

    allocatedEventListeners = false;

    renderedCallback() {
        if ( ! this.allocatedEventListeners ) {
            this.template.querySelector('.child').addEventListener( this.handleOnValueChanged.bind() );
            this.allocatedEventListeners = true;
        }
    }

That is a bit clunky, but it looks like it should work. We 'bind' the 'handleOnValueChanged' function to the event listener.

Unfortunately, it doesn't. Because of a fundamental capability of Javascript - it appears that the event handler doesn’t have access to ‘this’. And if you’re not an experienced Javascript developer then that’s when things start to get a bit crazy (actually, even if you ARE an experienced Javascript developer, I suspect it STILL gets a little messed up).

Basically, 'this' isn’t guaranteed to be what you think it is. If you write code that behaves in a procedural way, then it will generally be the object in which the method is defined. But as soon as you add in callbacks, Promises and asynchronous behaviour, it isn't guaranteed to be.

'this' can be simply the context in which the function runs, rather than the object or class in which the function is defined. This is an incredibly powerful aspect of Javascript that is very difficult to get to grips with unless you’re used to seeing it.

In Lightning Components you can see the effect of this in code such as Apex callouts in helpers where you end up with:

    let self = this;

In our particular case, you could use an alternative - the fat arrow notation for defining functions.

 event => { this.handleOnValueChanged( event ) }

Which is *would* transpile to (or is synonymous with) this:

    function handleEvent(event) {
        var _this = this;
        ( function (event) { _this.handleOnValueChanged(event); });
    }

Look familiar?

The resulting code for adding the event handler could end up like this:

    allocatedEventListeners = false;

    renderedCallback() {
        if ( ! this.allocatedEventListeners ) {
            this.template.querySelector('.child')
                          .addEventListener( 'valuechanged',
                                            ( ( event ) => { this.handleOnValueChanged( event ) } ).bind() );
            this.allocatedEventListeners = true;
        }
    }

In the end, this would work. But no-one would suggest it was elegant. And in order to get it working we had to brush up against some advanced behaviour of 'this'. Now, I admit that people are going to have to learn how 'this' and its binding behaves in order to write reliable Lightning Web Components - but just to add an event handler?

Update - 22/12/2018

A simpler solution than a fat arrow, would be to use 'bind' for the purpose it was designed and pass in the context into the call. I.E. to do the following.

    allocatedEventListeners = false;

    renderedCallback() {
        if ( ! this.allocatedEventListeners ) {
            this.template.querySelector('.child').addEventListener( this.handleOnValueChanged.bind( this ) );
            this.allocatedEventListeners = true;
        }
    }

This has the same effect as the fat-arrow function, in that is ensures that when 'handleOnValueChanged' is executed, it is done so with the context of 'this' being that of the component's object.

However, even though this is slightly easier to read, I still do not recommend registering event listeners using Javascript. Use the template notation instead.

The reality is that we don't have to think about it - Salesforce have given us a very usable shorthand for it, and we should be extremely grateful for it!

Wednesday, December 19, 2018

Lightning Web Components - Unit Testing immediate impression.

One of the things I’m most excited about in Lightning Web Components is the ability to write Unit Tests for the components.

The unit testing framework of choice is Jest, and it looks well suited. Not least it’s the framework of choice for Facebook, and describes itself as well suited to React apps. Why should that matter? Well, React is a 1-way bound Javascript framework - and so is LWC.

So I was looking forward to get into Unit Testing, following the documentation for testing wired components

Unfortunately, the documentation didn’t work out for me, and it looked like there’s a couple of mistakes in there:

The example ‘getRecord.json’ file isn’t valid JSON.

In order for the file to work, the field names need to be surrounded in double quotes.

I.E. Instead of:

    // getRecord.json
    {
       fields: {
           Name: {
               value: "DYNAMO X1"
           }
       }
    }

The file should be:

    // getRecord.json
    {
       "fields": {
           "Name": {
               "value": "DYNAMO X1"
           }
       }
    }

Interrogating the ‘element’ for its state does not seem to work.

Instead, I found that I needed to get data from the document object.

I.E. The following does not work:

    // Resolve a promise to wait for a rerender of the new content.
       return Promise.resolve().then(() => {
           const content = element.querySelector('.content');
           expect(content.textContent).toBe("Name:DYNAMO X1");
       });

But the following does:

    // Resolve a promise to wait for a rerender of the new content.
       return Promise.resolve().then(() => {
           const content = document.body.querySelector('.content');
           expect(content.textContent).toBe("Name:DYNAMO X1");
       });

Mocking doesn't seem to work for Apex, only LDS

From the quick test I did, I could get the mocking framework to work for the Lightning Data Service, once my implementation of the example was tweaked. However, I couldn't get it to work with an imported Apex method

I didn't see a reference to this being missing, though I guess I may have just missed that, and I know that the recommendation is to use LDS whenever you can. I just worry that there's a really important use case out there - it seems natural to me that components that use custom Apex are likely to be more complex than ones that use LDS. And with that in mind, it feels like missing Apex will be a big loss to the testing framework.

Hopefully the last part is already known about, is a bit of missing documentation, or is simply that I misunderstood something.

Whatever the case, I plan on doing more investigations into the Unit Testing capabilities, and will obviously blog my findings - but I have to admit that I found the initial experience a little disappointing after the extremely polished experience up to now.

I sincerely hope that it isn’t an indicator that Unit Testing is bit of an after-thought.

Update - 20/12/18

It felt like a good avenue for exploring testing would be to put together a test for the "message" component that I put together for the blog post on re-usable components

Unfortunately, I stumbled on this block. I was able to pretty quickly write a test that proved that @api properties were rendered properly in the resulting HTML, but I couldn't find a way of setting the value the slot. Adding a textNode as a child of the built node is blocked (seemingly by LWC), with an error that appears to suggest it can be worked around. But with no substantial documentation yet available, it feels like I'm just shooting in the dark

When I couple that with the fact that the git repo for the ebikes app seems to only contain one set of tests for the productFilter component, I'm stating to take the hint that unit testing hasn't been explored by the team fully yet.

I think it's entirely understandable, and I still hope that this area will be fleshed out significantly in the coming weeks, but the first impression remains - not yet

Lightning Web Components - @api, slots and getters

I've blogged about a few of the behaviours of Lightning Web Components, but the proof is really in building useful bits. What happens when you actually try to make a re-usable component?

For our example, we'll rebuild 'ui:message'. A now (seemingly) defunct base component that would render a message in a box that is coloured based on the 'severity' of the message being shown. In the original it could be set to 'closable', although we're going to ignore that and focus on just the rendering of it.

In a Lightning component we would use it like this:

Original usage - Lightning Component

    <ui:message title="Error" severity="error" >{!v.errorMessages}</ui:message>

Ideally, the version we will create, would be used like this:

Desired usage

    <c-message title="Error" severity="error" >{errorMessages}</c-message>

Looks pretty straightforward, and actually - it is. Just as long as we know about a few simple concepts.

Before we go into them, let's see what a working example could look like:

Javascript component

    import { LightningElement, api } from 'lwc';

    export default class Message extends LightningElement {

        @api title;
        @api severity;

        get classes() {
            return this.severity + ' uiMessage';
        }
    }

HTML Template

    <template>
        <div class={classes} role="alert" >
            <div class="uiBlock" >
                <div class="bBody" >
                    <h4>{title}</h4><slot></slot>
                </div>
            </div>
        </div>
    </template>

OK then, let's pick a few of these bits apart, and hopefully we'll explain a few little behaviours along the way.

First up, let's take a look at the '@api' declarations.

@api

The @api property lines are pretty simple to understand - they define that 'title' and 'severity' are publicly available properties of the component. In the context of Lightning Web Components, public and private mean 'available outside of the component, and invisible to the outside of the component'. It's tempting to think that this is to do with the scope of the Javascript, but it's not.

That is, every property of the Javascript component is available to be referenced in the HTML template - whether it is 'public' or 'private'. One way of thinking about it is that the HTML template forms part of the component, and so it can see private properties.

Another (probably more accurate) way of thinking about it is that the template is processed by the Javascript component (that code it's immediately obvious, but it's almost certainly in LightningComponent - which this class extends), and the Javascript can see its own properties, so the private ones are available.

However, other components (like ones that include this in their templates) can only see public properties. @api is how you make them public. Doing so means that they are available as attributes on the tag you use to include the component (hence <c-message title="Error"... is possible)

Not only that, but every @api decorated property is also 'reactive'. That is, whenever its value changes the component is re-rendered. The documentation is pretty clear on that point - and is presented as a fundamental property of a public property:

Public Properties

To expose a public property, decorate it with @api. Public properties define the API for a component. An owner component that uses the component in its markup can access the component’s public properties. Public properties are reactive. If the value of a reactive property changes, the component’s template rerenders any content that references the property.


Why would a public property be reactive?

Put simply, if we change the value of one of those properties in a parent component, we want the component to re-render - and it's pretty much guaranteed that we ALWAYS want the component to re-render.

For example, we may do the following:

    <c-message title="{title}" severity="{severity}" >{messages}</c-message>

When the value of 'title' or 'severity' changes, we would always want the message box to re-render to show our new values. And so the framework takes care of that and makes EVERY public property of the component reactive

So that takes care of the attributes we need to pass in, what about the content?

Slots

Lightning Components had facets. And they weren't intuitive. I mean they weren't complex, but they weren't in keeping with HTML - they always felt unnatural - especially in the simplest of cases.

Lightning Web Components fixes that, with slots. And in the simple case they are trivial. The documentation isn't long, and doesn't need to be.

All we need to do, in this simple case, is add <slot></slot> into our component, and the body of any tag that instantiates the component will be rendered in that slot.

Now something that's missing from the documentation, which is a fairly obvious behaviour once you see it in action, is that slots are effectively reactive.

That is, if you change the content of the tag, that content is immediately reflected in the component's rendered output.

So, in our example:

    <c-message title="Error" severity="error" >{errorMessages}</c-message>

Whenever the value of 'errorMessages' changes, the slot inside the 'message' component is re-rendered to include the new content.

I admit, I had assumed that this would be the case, but I didn't immediately realise that it was an assumption. So I thought it was worth calling out

Getters

The final part of the example that I want to explain is the use of the 'getter':

    get classes() {
        return this.severity + ' uiMessage';
    }

What we're doing here is building a list of CSS classes for a node in the component that includes one of the passed in attributes plus a standard class that must be applied

The use of the getter illustrates an important difference between the behaviour of the templates in Lightning Components (LC) and Lightning Web Components (LWC), as well a reminder of the behaviour of properties.

That is, in LC we could have done the following in our template:

    <div class="{!v.severity + ' uiMessage'}" role="alert" >

In LC, our replacements could include expressions, so we could build up strings in the template. In LWC, we can't do this, we can only reference properties or getters.

Not only that, but we can't build up the strings in the attribute assignment.

I.E. We can't do this:

    <div class="{severity} uiMessage" role="alert" >

In LWC we don't assign properties to attributes in this way, the framework takes care of the wrapping in double quotes, escaping the strings, and other such things, so we can only assign the property, and that's it.

I.E. This is what is allowed:

    <div class={severity} role="alert" >

So, if we want to assign more than just the value of 'severity' to the class attribute, we need to build that string up outside of the template.

Your first reaction might be - OK, we can create a trackable property to store it, right?

    @track classes = this.severity + ' uiMessage';

But this doesn't work. You'll end up with the classes property defined as 'undefined uiMessage', and it won't change. Why is that?

Well, it's tempting to think that 'track' and 'api' mean that Javascript will re-run when things change, but that's not what they do - nor what the documentation says they'll do

Rather, if a property is reactive it means that the component will be re-rendered when the property changes. That says nothing about running Javascript.

So when we look at the above, what happens is the property 'classes' is set when the Javascript object is constructed. At this point the property 'severity' is undefined. When the 'severity' is updated via the attribute, the component is re-rendered and the 'classes' property is re-injected into the template, but the Javascript that sets the classes property is not re-run - that is only executed when the object is instantiated.

So, instead of setting the 'classes' property directly, we set up a getter for it:

Javascript component

    get classes() {
        return this.severity + ' uiMessage';
    }

Now, when the 'severity' property changes, the 'classes' property is re-injected. In order to get the value for 'classes', the getter is executed - this is the only way the property can be retrieved. In doing so, the string concatenation is re-evaluated and the new value is retrieved.

Summary

None of the concepts here are particularly difficult, or really that earth shattering, but building even the simplest of re-usable components starts to shed some light on what the parts do any why.

The framework has been very thoughtfully put together, and some of the techniques will be different to what people are used to, having cut their Javascript teeth with Lightning Components, but the changes are for very good reasons. An example like this really shows how those changes make for simple components.

Tuesday, December 18, 2018

Lightning Web Components - The subtleties of Tracking and Wiring

Following on from yesterday's investigations into the behaviour of '@track', and its effect on untracked properties, I figured I should expand my remit to '@wire'.

My main reason for that was the statement in the documentation:

In the wire adapter’s configuration object, prefix a property with $ to reference a property of the component instance. The $ prefix tells the wire service to treat it as a property of the class and evaluate it as this.propertyName. The property is reactive. If the property’s value changes, new data is provisioned and the component rerenders.

This comment relates to code along the lines of:

    @api recordId;

    @wire(getRecord, { recordId: '$recordId', fields })
    contact;

The bit that really piqued my interest was 'The property is reactive'.

In all the examples, it looked like the property being referenced was always tracked or set as an api field (the latter implying the former)

That's the case in the above example - the property passed as a parameter 'recordId' as referenced by '$recordId' is defined as an api field, and is therefore tracked.

There's also that recurring point 'the component rerenders', which we saw in my previous post. Recalling that, it essentially meant that a change to a tracked property caused all untracked properties in the same component (and I am assured, but yet to prove, child components) to be re-rendered

So, what is the actual behaviour in this case? Are the implications the same?

You can code that illustrates the below examples, as well as the points from yesterday's post in this bitbucket repository if you want to explore the behaviours further. I suggest you take a look.

Tracked property used as a parameter to a wired property

The natural first example is much like the one exampled above. We have an Apex method that takes a parameter. We wire that parameter to a javascript property, and reference a single parameter.

Javascript component

    @track searchString;

    @wire(searchContacts, { searchString: '$searchString' })
    contacts;

When the searchString property is updated:

  • The searchString's change causes the wired function to be re-evaluated.
  • Therefore the value of 'contacts' changes.
  • The component treats contacts as if it was tracked and causes the component to be re-rendered.

OK. That's pretty decent, and what the documentation says will happen. No surprises there!

But then I got to thinking: So, what happens if the property isn't tracked?

My first guess was that maybe the Apex method wouldn't get re-executed, but I wasn't entirely convinced - after all, the documentation only really makes a big deal about rendering. So I changed the code and tried again...

Javascript component

    searchString;

    @wire(searchContacts, { searchString: '$searchString' })
    contacts;

This time, when the searchString property is updated:

  • The searchString's change causes the wired function to be re-evaluated.
  • Therefore the value of 'contacts' changes.
  • The component treats contacts as if it was tracked and causes the component to be re-rendered.

Erm. OK. It's the same. Hmmm....

Looking at what's actually going on, that does make sense. It's the '$searchString' reference that tells the framework that searchString is reactive, as respect to the wiring, so it's that causing the Apex to re-execute. And once the method is re-evaluated, the value of 'contacts' changes, and that causes the component to re-render.

That got me to thinking - so what does the '@track' do against the searchString in the original example. Experience tells me that these things generally will have an effect.

So I added something else to my example...

    renderedCallback() {
        console.log( 'renderedCallback was called' );
    }

This hooks into the component's lifecycle, as described here, and will tell us when the component gets re-rendered.

It turns out that my initial understanding of the first example was slightly wrong, though not in a way that would generally have much of an impact.

That is, if the parameter is tracked, you end up with the following:

  • The searchString's change causes:
    • The component to be re-rendered (since it is tracked).
    • The wired function to be re-evaluated (since it is referenced as a '$' parameter).
  • The execution of the wired function causes the value of 'contacts' to change.
  • The component treats contacts as if it was tracked and causes the component to be re-rendered.

The result is that the component is re-rendered twice!

And sure enough, if you take the tracking off the searchString parameter, the component is only re-rendered once.

So, this does reinforce another point that the documentation makes here:

Don’t overuse @track. Track a property only if you need the component to rerender when the property’s value changes.

Personally, I think I'd call out this particular behaviour and remind people - you don't need to 'track' a property in order for the wired method to re-execute - but maybe it's just my lack of attention that missed that little point.

And I'd also state that you should only track a property that is being used as a reactive wired parameter if you need the property to be re-rendered before the Apex you are calling returns with its result.