Skip to content

Instantly share code, notes, and snippets.

@locks
Created November 9, 2020 13:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save locks/d72a4983b7ff086132e1a4a46053bc61 to your computer and use it in GitHub Desktop.
Save locks/d72a4983b7ff086132e1a4a46053bc61 to your computer and use it in GitHub Desktop.

Converting partials to template-only components

Why partials?

For quite some time, partials were a popular way to share a chunk of HTML as all the developer had to do was to extract the HTML into a file, and then call {{partial}} where they want it to be included. No need to fuss about with passing arguments and callbacks.

However, this very fact also makes partials a liability for your codebase. The more you use partials, especially if nested, the harder it is to inspect your templates and data-flow just by reading your templates. Changes are hard to make because you are never sure if a property might be used somewhere in the partial.

As partials are now deprecated and we have clearer template constructs, we should convert them to components.

Converting partials to template-only components

Since partials are inserted where they are invoked and do not have a controller-type class, the more direct conversion is to turn them into template-only components.

To do that we will need to take a couple steps:

  1. Pick a partial to convert.
  2. Duplicate partial as a template-only component.
  3. Update any property and action used to be an argument.
  4. Convert invocation to pass in necessary arguments.

Picking a partial to convert

There are two ways I usually go about choosing with partial to convert. Either I am working on some code that happens to use a partial, or I'm deliberately converting partials and will follow a couple of questions. These questions are meant to help the developer pick the partial that will have the smaller change, in order to maximize progress and stability.

  1. Does it have nested partials?

If a partial (partial-a) is itself calling a partial (partial-b), then partial-b should be converted first. This makes it easier to see which arguments the partials use.

  1. How big is the partial?

This is more about number of expressions ({{}}) than about source code lines. If a partial has many lines of code, but only a handful of expressions, that makes it much easier to convert.

  1. How many templates use the partial?

Keeping with the stability theme, the fewer places a partial is used, the fewer chances the app will break when we convert it.

  1. Is the partial invocation static?

What I mean by this question is, is the partial being invoked with a static valid, like {{partial 'wizard-name'}}, or is there some interpolation to render one of many possible partials, like {{partial (concat 'wizard-' stepName)}}.

Is the latter case, converting could be more complicated, as the different possible partials could receive different values. All of the values need to be present in the template context though, so you can pass all arguments to the component even if they are unused.

Updating the template-only component

Recently I converted some partials into template-only components ( https://bitbucket.org/camilyoplatform/camilyo-web/pull-requests/7421 ) and thought I'd write down my process for doing it.

The first step in the conversion is to duplicate the partial into the components' templates folder.

Let's take -cim-import-success.hbs. I took the opportunity to use a nested path to give a sense of structure, so I duplicated the file into components/cim/import/success.hbs.

After that I replaced the invocation of the partial with an invocation of the component. From {{partial "cim_import_success"}} to <Cim::Import::Success />. This will not work until we pass in the necessary arguments, so it's time for the next step.

Then I went through dynamic invocations (any {{}}) to see any argument or action handler that the partial might be using, converting it to a named argument and adding it to the invocation of the component.

The easiest variables to identify are ones prefixed by this, like in -cim-import-success.hbs, where we can find {{#if this.showSucceeded}} in the second line. So we turn that into {{#if @showSucceeded}} and update the component invocation:

<Cim::Import::Success
  @showSucceeded={{this.showSucceeded}}
/>

You might encounter partials and templates that do not prefix variables with this (properties) or @ (arguments), making it harder to disambiguate. In these cases, you need to check the controller (or component class) where the partial is being called–biy-import-csv.hbs in the PR mentioned,–to see if a variable matches one of the properties

Converting actions might be a bit confusing if there are any string actions. Referring to -cim-import-success.hbs again, we can see an action on line 37:

<div {{action "goToCRM"}} class="continue">{{t "continue"}}</div>

We convert that to use a named argument, and add it to the invocation, ending up with the following in the new component:

<div {{action @goToCRM}} class="continue">{{t "continue"}}</div>

And the following in the invocation:

<Cim::Import::Success
  @showSucceeded={{this.showSucceeded}}
  @goToCRM={{action "goToCRM"}}
/>

This could be further improved to use {{on}}, but we are focusing on doing as direct a conversion as possible to minimize the risk of breaking something.

Updating a dynamic partial invocation

As mentioned, you might encounter something like {{partial (concat 'wizard-' stepName)}}, where the partial name is not a static string, but is instead constructed from a variable.

To convert this, you first follow the instructions from "Updating the template-only component" for each of the possible partials. Remember to keep a list of the arguments for all of the partials so you can pass them to the component.

To convert the invocation, you can use the let and component helpers:

{{#let (component (concat "wizard-" stepName)) as |WizardStep}}
  <WizardStep
    @step={{this.step}}
    @goToStep={{action "goToStep"}}
  />
{{/let}}

Updating pending template lint rules

In order to make gradual adoption of ember-template-lint in your project, this library provides a "pending lint" feature where you can define which rules are ignored for which modules. As partials are a linter error by default, we will need to update the pending.json file to account for the converted components.

To do this, run the following steps:

  1. Run yarn lint:hbs --quiet --print-pending > pending.tmp.json in the terminal to get the new list of pending rules.

You can name pending.tmp.json whatever you want, we will be deleting it afterwards, but we need to capture all of the output.

  1. Open pending.tmp.json, copy the array inside pending:.

  2. Open pending.json and replace the array with the copied one.

  3. Run yarn lint:hbs --quiet to confirm that the file was properly updated and linting passes.

  4. You're done!

Hope this guide proves useful. Contact me for any questions related to the article or to converting partials!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment