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.
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:
- Pick a partial to convert.
- Duplicate partial as a template-only component.
- Update any property and action used to be an argument.
- Convert invocation to pass in necessary arguments.
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.
- 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.
- 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.
- 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.
- 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.
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:
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:
We convert that to use a named argument, and add it to the invocation, ending up with the following in the new component:
And the following in the invocation:
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.
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:
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:
- 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.
-
Open
pending.tmp.json
, copy the array insidepending:
. -
Open
pending.json
and replace the array with the copied one. -
Run
yarn lint:hbs --quiet
to confirm that the file was properly updated and linting passes. -
You're done!
Hope this guide proves useful. Contact me for any questions related to the article or to converting partials!