aurelia-templatingaurelia

Aurelia - multiple Enhance statements


Updated with solution (28.03.2017):

http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/app-configuration-and-startup/8
Have updated Aurelia docs with solution (scroll down a little).
Special thanks to Charleh for hint.

Question:

Aurelia has this nice feature calls enhance, which can help you enhancing specific parts of your application with Aurelia functional.
But can we have multiple enhance statements on the same page? It seems problematical.

Example:
Task: enhance first component on the page, then get some data from the server and enhance second component on the page with server data as binding context

HTML

<!DOCTYPE html>
<html>
  <head>
    <title>Title</title>
  </head>
  <body>
    <my-component1></my-component1>
    <my-component2></my-component2>
  </body>
</html>

JS

import { bootstrap } from 'aurelia-bootstrapper-webpack';

bootstrap(function(aurelia) {
  aurelia.use
    .standardConfiguration()
    .globalResources("my-component1", "my-component2");

  aurelia.start().then((app) => {

    // Enhance first element
    app.enhance(null, document.querySelector('my-component1'));

    // Get some data from server and then enhance second element with binding context
    getSomeDataFromServer().then((data) => {
      app.enhance(data, document.querySelector('my-component2'));
    });
  });
});

Result:
In the result we will enhance first component, but when it's time for the second one, Aurelia will try to enhance first component one more time!
It happens because of aurelia-framework.js _configureHost method.
So basically when you start enhance it starts this method with your element as an application host:

Aurelia.prototype.enhance = function enhance() {
  var _this2 = this;

  var bindingContext = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
  var applicationHost = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];

  this._configureHost(applicationHost || _aureliaPal.DOM.querySelectorAll('body')[0]);

  return new Promise(function (resolve) {
    var engine = _this2.container.get(_aureliaTemplating.TemplatingEngine);
    _this2.root = engine.enhance({ container: _this2.container, element: _this2.host, resources: _this2.resources, bindingContext: bindingContext });
    _this2.root.attached();
    _this2._onAureliaComposed();
    resolve(_this2);
  });
};

And inside the _configureHost we can see this if statement which is just checking if our app instance is already host configured then do nothing.

Aurelia.prototype._configureHost = function _configureHost(applicationHost) {
  if (this.hostConfigured) {
    return;
  }
  ...

Problem So the actual problem here is that any enhanced element automatically became an application host (root) and when you try to enhance another element with the same aurelia instance you will just end up enhancing the first element always.

Question Is this some way around for the cases when I want to enhance several elements on the page?


Solution

  • There's a clue here:

    this.root = engine.enhance({container: this.container, element: this.host, resources: this.resources, bindingContext: bindingContext});
    this.root.attached();
    

    The aurelia.enhance just wraps the TemplatingEngine instance's .enhance method.

    You could just pull TemplatingEngine from the container and call .enhance on it passing the bindingContext since aurelia.enhance does just that (but adds the additional "host configure" step that you've already done via your first .enhance call).

    So that bit might look like:

    import { Container } from 'aurelia-dependency-injection';
    
    let engine = Container.instance.get(TemplatingEngine);
    
    engine.enhance({ container: Container.instance, element: document.querySelect('my-component2'), resources: (you might need to inject these too), bindingContext: someContext });
    

    (disclaimer: I didn't test the above so it may not be accurate - also you probably need to pass the resources object in - you can inject it or pull it from the container - I believe the type is just Resources)

    However - something to note: your my-component2 won't actually be a child of your host element my-component1. I'm not sure if that will cause issues further down the line but it's just a thought.

    I'm still curious as to why you'd want to bootstrap an Aurelia instance and then have it enhance multiple elements on the same page instead of just wrapping all that server response logic inside the component's viewmodel itself?

    Maybe you can give a bit more context to the reason behind this?