I need to integrate a UI library in an Ember app which uses Glimmer components.
The construction and teardown of the components happens using “did-insert” and “will-destroy” modifiers. This is clean enough. In general, it would be even cleaner if the HTML construction happens purely by binding but given the nature of the UI library I need to use javascript to create and destroy components. The problem is when I need to react to changes in component arguments. There are cases in which I need to detect such changes and again use javascript to apply the corresponding changes to the component using the library’s API. Previously I used “addObserver” method of the classic Ember components. Now migrating to Glimmer components there is no such “addObserver” method.
Any ideas how to solve this problem in a relatively clean way are welcome!
I am adding some sample code below:
Component:
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class Tabs extends Component {
tabs = null;
@action
didInsertEl() {
this.tabs = $('#my-tabs').tabs();
this.onTabChange();
}
// What is the best way to register this method as a listener on "args.tabIndex" changes?
// The approach chosen should work with detecting changes in nested properties inside input arguments as well.
// It needs to be as powerful as the tracking mechanism of computed properties.
// For example, it should be able to detect changes like "args.items.@each.name".
onTabChange() {
if (this.tabs) {
this.tabs.tabs('option', 'active', this.args.tabIndex);
}
}
@action
willDestroyEl() {
this.tabs.tabs('destroy');
}
}
Template:
<div
{{did-insert this.didInsertEl}}
{{will-destroy this.willDestroyEl}}
>
<div id="my-tabs">
<!-- My tabs go here -->
</div>
</div>
Thanks!
Hard to say if there is a better way without knowing the rest of the code and what the library is or does, but if you are already using did-insert
and will-destroy
, there is also did-update
in ember-render-modifier where those came from that does pretty much exactly what you want.
If it’s important that you only call the imperative API when the value absolutely have changed, then you may need to store and compare the value yourself, since that is not guaranteed. Otherwise, if the imperative API on the library already does nothing/the correct thing when setting it to the same value, you can skip all of that.
Thanks @chancancode for the feedback!
You are right - “did-update” could be applied. The limitation though is that it cannot be used to detect changes like “args.items.@each.name” i.e. changes in nested properties.
For the time being my “solution” to this dilemma is to use computed properties.
Say I need to register a listener to changes like “args.items.@each.name”.
I add a computed property like this
@computed('args.items.@each.name')
get nameChangedCP() {
this.onNameChanged();
}
and then I have to add a reference in the template to this property so the body of the computed property gets executed:
<div
data-name-changed-cp="{{this.nameChangedCP}}"
>
This does the work but it is not pretty. In the component code I abuse the notion of computed properties to add a side effect i.e. I call the listener. Then I add pollution to the templates to add not-needed computed properties.
Seems to me that the framework is a bit too restrictive by removing support for observers when using Glimmer components. I get it that ideally one would not use observers but in a situation like mine (integrating a UI library/component which is created/destroyed/updated using JS API) then the application developer is kind of stuck. For some projects there could be very strong reasons to pick a library/component which can only be used via JS API.
Wouldn’t something along these lines work … Glimmer components remain as is. Ember offers an observer service which offers registering listeners. There could be a polite warning when this service is used that for most scenarios there is a better way which does not involve listeners. The warnings can be disabled with a flag.
you can just write a modifier. You don’t need the computed, or an observer. This is exactly what modifiers are for. Pass the state the modifier needs into the modifier and it will update appropriately.
Sorry for the late reply.
Thanks @runspired and @chancancode! Your comments made me think in the right direction.
I use “did-update” modifier now and it does the job.
This is a codebase written some years ago and it gets modernized these days.
I appreciate the evolution of the framework which has happened the last several years!
After this modernization effort is over the codebase will be simpler, more robust and easier to maintain!