I'm submitting a bug report
Please tell us about your environment:
Current behavior:
I use Aurelias Custom Elements to augment an exiting web app. I use enhance() whenever there's a new Custom Element to be initialized, however every 2nd call to enhance() fails with:
Unhandled rejection TypeError: Cannot read property 'behaviorInstructions' of undefined
at applyInstructions (http://localhost:9000/jspm_packages/npm/[email protected]/aurelia-templating.js:2152:43)
at ViewFactory.create (http://localhost:9000/jspm_packages/npm/[email protected]/aurelia-templating.js:2413:9)
at TemplatingEngine.enhance (http://localhost:9000/jspm_packages/npm/[email protected]/aurelia-templating.js:4781:26)
at eval (http://localhost:9000/jspm_packages/npm/[email protected]/aurelia-framework.js:167:30)
at i.e._execute (http://localhost:9000/jspm_packages/npm/[email protected]/js/browser/bluebird.min.js:31:18668)
at i._resolveFromExecutor (http://localhost:9000/jspm_packages/npm/[email protected]/js/browser/bluebird.min.js:32:12641)
at new i (http://localhost:9000/jspm_packages/npm/[email protected]/js/browser/bluebird.min.js:32:4993)
at Aurelia.enhance (http://localhost:9000/jspm_packages/npm/[email protected]/aurelia-framework.js:165:14)
at:1:9
I can repeat this for how long as I want:
I've tracked this down to only using a HTML only template. If I exclude that and just solely use HTML&JS Custom Elements, consecutive enhance calls won't fail any more.
It also doesn't matter how I include the HTML only template, both:
<compose repeat.for="relation of data.relations" view="./relation-field.html" view-model.bind="relation"></compose>
and
<relation-field repeat.for="relation of data.relations" relation.bind="relation"></relation-field>
fail with the same error mentioned above.
Here's a runnable gist showcasing the problem, just click on the button to call enhance, will fail every 2nd time:
https://gist.run/?id=2d137c4d2c5aa1080ee2ad130bbae042
Also I noticed that a new au-target is added every single time regardless if one is already there, any specific reason as to why? Here's the code in question from view-compiler.js:
function makeIntoInstructionTarget(element) {
let value = element.getAttribute('class');
let auTargetID = getNextAUTargetID();
element.setAttribute('class', (value ? value += ' au-target' : 'au-target'));
element.setAttribute('au-target-id', auTargetID);
return auTargetID;
}
A fix to only add it 1 time is rather easy, don't know if this validates a pull request?
Expected/desired behavior:
There is a PR related to the au-target class: https://github.com/aurelia/templating/pull/455, but I don't think that fixes this issue.
No it doesn't fix the issue, I changed it locally, it was just something that I noticed in the DOM while I investigated the issue so I thought I would mention it.
@tmkn You cannot enhance the same dom subtree more than once. That would cause problems. Is that what you are doing? Can you confirm?
@EisenbergEffect Yes, that's actually what I do, however the stacktrace that I posted in the opening post only happens if there is a HTML only component somewhere.
If there are no HTML only components (all have a class and template) then they will get re-instantiated just fine by just calling enhance() over and over again. Although if you look at the html, the components all then have multiple 'au-target's. So I don't know if this works by accident or not.
So my problem is this, I want to integrate Aurelia into an existing 2 column webapp via enhance,
left column contains a custom component from the beginning, right column at some point later.
So instead of just calling enhance() everytime I have to use the parameters: enhance(bindingContext: Object = {}, applicationHost: string | Element = null) to explicitly tell what I want to enhance?
What should I pass into the bindingContext?
well, I just tried it with arguments but it seems like on the very first enhance, the dom subtree is saved and used for every other enhance call, so if there's something like this:
<div>
<div id='sub1'><custom-component1></custom-component1></div>
<div id='sub2'><custom-component2></custom-component2></div>
<div>
and the first enhance call specifies sub1 to instantiate custom-component1 then any enhance call for sub2 will silently fail as sub2 is not part of the sub1 dom sub tree and is ignored.
I guess it has something to do with the _configureHost function as it checks if it was already configured
_configureHost(applicationHost) {
if (this.hostConfigured) {
return;
}
...
this.hostConfigured = true;
...
}
If I call enhance for the first time with no parameters and then specifically with
enhance({}, document.getElementById('sub1'))
and
enhance({}, document.getElementById('sub2'))
then both components(dom subtrees) get reinstated on enhance regardless of the supplied dom element.
Which makes sense as the first enhance call was with no parameters and thus defaulted to the body but I don't think that's how it should work?
Please try using the templating engine's enhance method. The aurelia enhance method is designed to be called oncej per application vs. the templating engine, which can be called multiple times. In either case though, you cannot enhance html that has always been enhanced because that will cause the dom node to live inside multiple view instances simultaneously and those views will then conflict over who has control of the dom.
Isn't then the enhance functionality very limited if you're only supposed to fire it off one time?
I think the vast majority of people who would want to go this route want to instantiate their components one at a time because they already have some spa/ajax mechanism in place and want to use aurelia components in multiple places?
I mean I wanted to when I read about the enhance functionality in the docs.
Are there plans to address this? I don't know the code so no guarantees but I'm willing to take a look.
I would expect Aurelia to keep track of the enhanced dom subtrees and only enhance new ones?
You can use enhance as many times as you want, you just need to use the right version. The api on the Aurelia object is for a single application enhance event, in place of using the setRoot api. You use one or the other, but not both. If you need to manually enhance parts of the dom, and do it multiple times, then you use the enhance api on the templating engine itself. But you don't want to "re-enhance" the same dom over and over. You can enhance individual components or subtrees though.
@tmkn would you mind putting together a gist that demonstrates what is not working: https://gist.run/?id=7542e061bc940cde506b
This will enable us to get to the bottom of this very quickly, and can be the basis for the unit tests we'll write if there's a bug-fix required.
@jdanyow,
@tmkn would you mind putting together a gist that demonstrates what is not working
I think he did exactly that and mentioned it in the first isssue comment:
Here's a runnable gist showcasing the problem, just click on the button to call enhance, will fail every 2nd time:
https://gist.run/?id=2d137c4d2c5aa1080ee2ad130bbae042
@EisenbergEffect, out of curiosity I tried to follow Your suggestion with this gist, that uses
var templatingEngine = aurelia.container.get(aureliaTemplating.TemplatingEngine);
templatingEngine.enhance(document.getElementById("enhanceElem"));
instead of aurelia.enhance(), but I experience pretty-much the same as described by @tmkn .
In this case every second time instruction is undefined in applyInstructions and it causes the failure to re-enhance (because ViewFactory.create iterates over all elements under re-enhanceable custom element (including a tag with "label 1" that has au-target-id="1", butinstructionsdoesn't contain value for 1).
The workaround (that seems to work in this simple case) would be to manually remove au-target classes from enhanceable element before calling templatingEngine.enhance(enhanceableElement) - @EisenbergEffect, @jdanyow, I'm not sure this is how it should be, so without deeper knowledge, I'd still consider this a bug, even if my workaround works as expected.
@atsu85 Thanks a lot for your insight atsu85 and your gist. I hit this when trying to write my own bootstrapping tool for unit testing via Cypress.
I'm gonna raise another Github issue for this as this is quite nasty.