Aurelia - multiple Enhance statements
Asked Answered
S

2

8

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?

Suprasegmental answered 3/9, 2016 at 22:43 Comment(1)
Query: do you need both of these elements to share the same Aurelia instance? There are also other ways to make elements become Aurelia instances without using enhance whilst specifying the binding context.Reprehensible
R
3

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?

Reprehensible answered 6/9, 2016 at 12:18 Comment(5)
Of course. I have an application which is written in a code that involves a custom jQuery with a lot of plugins plus Angular, the code structure and logic is pretty much the worst I've seen in my life, but it works somehow... and what I need now is to update existing code gradually (component by component) using Aurelia enhance method in pair with aurelia custom elements. So for example I have one block in the header that I want to enhance right now with aurelia and one in the footer. But except those blocks every element on the page must stay the same.Suprasegmental
Right, difficult situation - I think the original intent for enhance was to give each element it's own Aurelia instance (think web-components). Do you desperately need each component to share the same Aurelia instance/context? I've not tried, but is there any merit in making the whole application an angular app and an Aurelia app at the same time? e.g. just enhance the whole body element and put your code to get the server data in the my-component2 element's viewmodel?Reprehensible
Is it so, that original intent for every enhance statement was to give each element it's own Aurelia instance? That's the question actually. Cause if we create new instance for every enhanced element we can just use setRoot several times after each bootstraping of aurelia instance and it will be the same. I've already wrote an issue on aurelia github about it. Thank you for your response though, I will try to enhance it with engine instance till I have the answer from the Aurelia guys.Suprasegmental
Have you tried the Aurelia gitter channel btw? Always loads of people on there : gitter.im/aurelia/discussReprehensible
Yes, tried it already with no result. The subject is too complex to discuss it in gitter I think, people must understand the intention behind this and see an example, so issue on github is the most convenient way to show the problem I assume.Suprasegmental
S
3

My workaround for this issue for now (thanks to Charleh for the clue):

import { bootstrap } from 'aurelia-bootstrapper-webpack';
import {TemplatingEngine} from "aurelia-framework";

let enhanceNode = function (app, node, bindingContext = null) {
  let engine = app.container.get(TemplatingEngine);
  let component = engine.enhance({container: app.container, element: node, resources: app.resources, bindingContext: bindingContext});
  component.attached();
}

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

  aurelia.start().then((app) => {
    enhanceNode(document.querySelector('my-component1'));
    enhanceNode(document.querySelector('my-component2'));
  });
});

That way you can skip host configuration for the app and can enhance as many custom elements as you want on the page.

Suprasegmental answered 13/9, 2016 at 11:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.