If your use case is really slower in a 3.1.1 production build than a 2.14.1 production build, that is interesting and if you can share a working reproduction it would be worth filing an issue about it.
But in terms of solving your immediate problem, there are several possible paths.
Best option: make vertical-collection accessible
I think the very best option would be to come up with a strategy for making vertical-collection itself accessible. This would benefit a huge number of users while also solving your problem.
My hunch is that we could provide more traditional pagination controls that only screenreaders see. Sighted users would get the current behavior of vertical-collection. Screen-reader users would hear the first page of results, and also hear links for next page, jump to page N/M, etc. Those links could programmatically jump the vertical-collection to the right places. After it rerenders with new rows in DOM, you might need to give a hint to screenreaders to re-read the changed content (this is already an issue for plain old {{outlet}}
too, so you can reuse whatever strategy youāre using there. ember-a11y uses focus manipulation).
I would suspect that these added pagination links would have accessibility benefits for keyboard users too, not just screenreader users, since you could tab over to the links and activate them all by keyboard. That may be an argument for showing them to everyone, at least as an option for people installing vertical-collection. But a more elegant solution to that part of the problem is probably to give the vertical-collection its own native keyboard controls so that the table can be focused and scrolled with up and down keys.
It would not surprise me if this strategy is actually better than dumping all the rows into the DOM, because that strategy doesnāt give screen reader or keyboard users an especially good way to navigate thousands of rows. I am not an accessibility expert, all of this deserves real user testing.
Second best: optimize your giant table
But assuming you really need to keep all the rows in DOM, that does cut out the most accessible prepackaged optimization strategies, which are all about limiting the work to things that are on screen. Youāre setting yourself a hard task, which is rendering vastly more stuff than could even all appear on screen at once (or even be read productively by a screen reader ā because really, whoās going to listen patiently through thousands of cells?).
The most impactful thing is probably to have less components.
Your code snippet doesnāt make it clear how many you already have, because we canāt see how table.column
is implemented. If it has a component per cell, yeah, that is probably pretty expensive. You will get a boost by not doing that.
Often you can do this kind of refactor without losing any features. If the cell component has computed properties, move that work into helpers. If it has actions, move the actions up to a parent component, pass the actions down, and parameterize the arguments to the action so that you can still operate on the correct cell data.
You can still give each cell customized content by yielding, just as you do in your example. Though keep in mind that youāll keep your component count lower if you inline some DOM like
{{#table.column name="Column 3" property="dataAttribute" as |column|}}
<div class="some-special" {{action "mogrifyTheData" column}}>column.data</div>
{{/table.column}}
rather than invoke a component per cell:
{{#table.column name="Column 3" property="dataAttribute" as |column|}}
{{some-component column.data}}
{{/table.column}}
There is a tradeoff here between developer convenience and runtime speed. Itās a tradefoff we are constantly working to bend, and is has gotten steadily better. But if you want to cheat the tradeoff, Miguel Camba has a library that helps you do compile-time components so that you can author with components but not pay their cost at runtime. And hereās a video about how it works and how to use it. This is using nonstable APIs, so if it breaks you get to keep both pieces. How practical it is for you depends on your willingness to step in and learn how it works and fix it if it needs updating.
Sticking to safer public APIs, another option is to write a helper that builds up some DOM Elements and returns them. You can do whatever you want to construct optimized DOM in the helper. This can be cheaper alternative to a component for reusable bits that donāt need a lot of fancy behavior. Just be aware that if any of the inputs to the helper change, itās going to rerun completely, and rebuild all of the DOM that it built, so anything stateful in there like <input>
could get blown away.
Another option that is all public API, though new, is to enable the template-only-glimmer-components feature, and refactor your most common components to be template-only glimmer components. Youād do that by:
- Use Ember 3.1
- Add the
@ember/optional-features
package to your project.
- Run
ember feature:enable template-only-glimmer-components
.
- Refactor your most used components (like per-cell ones) to have no Javascript files, only templates files.
- Adjust their templates to the new glimmer component semantics, which means:
- they will no longer get an automatic wrapping div ā you get to put the outermost div directly in the template.
- they should refer to their arguments like
{{@whatever}}
instead of {{whatever}}
.
I havenāt measured the impact of this, but it allows Glimmer to skip a bunch of work, so it seems like it could measurably help your situation. To avoid needing a javascript file, you would do many of the same things I suggested earlier, like refactoring actions into parent components and refactoring computed properties into helpers.