Search code examples
web-componentfast-ui

What are the differences between @customElement and composition-based component definitions in FAST?


As I understand there are basically two(+) ways of definining components in FAST.

I will provide my own answer to the question below, what I want to verify is whether I am correctly understanding the differences and if there is anything to add.

What I've found out so far:

🐢 One - Immediate Template & Style Resolution

  • class definition extending FASTElement from @microsoft/fast-element
  • uses @customElement decorator from @microsoft/fast-element
  • it must contain - in its name
  • its template and style (passed through the @customElement decorator) are immediately resolved
  • it cannot extend its PartialFASTElementDefinition
  • it cannot access ElementDefinitionContext when defining its template and style

Example pseudocode (parts from official docs):

const counterStyles = css`/* ... */`;

const counterTemplate = html<NameTag>`<!-- ... -->`;

@customElement({
    name: 'name-tag',
    counterTemplate,
    counterStyles,
    shadowOptions:
})
export class NameTag extends FASTElement {
    @attr name = "Sally";

}

🐅 Two - Extendible and Lazy Template & Style Resolution

  • class definition extending Foundationelement from @microsoft/fast-foundation
  • it doesn't have to contain - in its name (since it will be prepended with the prefix of a DesignSystem that will register it)
  • its template and style are resolved through the compose method that is made available to the component through its FoundationElement base class
  • both template and style can be lazily resolved

Example pseudocode (parts from official docs):

export class Counter extends FoundationElement {
    @attr count = 0;

    increment() {
        this.count++;
    }
}

/* if CounterDefinition wouldny be defined then expressions below would use FoundationElementDefinition instead*/
interface CounterDefinition extends FoundationElementDefinition {
    defaultButtonContent?: string;
    defaultButtonColour?: string;
}

const counterStyles = (
    context: ElementDefinitionContext,
    definition: CounterDefinition
) => css`/* ... */`;

const counterTemplate = (
    context: ElementDefinitionContext,
    definition: CounterDefinition
) => html`<!-- ... -->`;

export const counter = Counter.compose<CounterDefinition>({
    baseName: 'counter',
    counterTemplate,
    counterStyles,
    defaultButtonContent: "Count!",
    defaultButtonColour: "pink"
});

🦆 Three/One+ - Kindof One but without decorators?

  • as I understand it is basically like method One but without decorators, meaning it cannot lazily resolve its styles and templates

Solution

  • The distinctions you call out are correct for the most part.

    The first mechanism is intended to be what folks use most commonly when building components in their application. It immediately associates the template and styles with the element and then immediately registers the component with the web platform itself.

    The second mechanism was intended primarily for those building design systems where they want to provide the system to others and enable those consumers to customize parts of the system before using it. So, it allows for lazily providing parts of the template, altering styles, etc.

    This second mechanism is deprecated in the upcoming 2.0/3.0 set of release. The reasons for this are:

    • We found that having these two different approaches was confusing to our community
    • It made component registration much more complex
    • It incurred an unnecessary performance cost at startup time.

    Please let me encourage you to read this issue: https://github.com/microsoft/fast/issues/5901 I go into greater depth on the problems and motivations related to this question as well as paint a picture of the future we are working towards with the upcoming new versions.

    Related to defining and registering componnets, you'll want to keep a few considerations in mind:

    • Consider whether the component is a one-off or intended to be re-used.
    • If the component is intended to be re-used, is it going to be re-used outside of the current application? If so, will it need to be customized?
    • Consider whether the component should immediately register itself with the platform, or whether the consumer should control the timing of registration. This likely relates to the previous considerations.