Migrating Au1's #enhance to Au2's #enhance

Following on from Trouble migrating from Au1 to Au2 where certain technical APIs are being utilized, I have made tremendous progress. The last sticking point, I think, is the difference between the configuration object supplied to Au2’s #enhance and the configuration object supplied to Au1’s #enhance. If I can wrangle this, I’ll be able to do a full write-up here on Discourse that discusses the migration path.

Specifically, how do we migrate

const view = this.templatingEngine.enhance({
    element: element,
    bindingContext: itemBindingContext,
    overrideContext: itemOverrideContext,
    resources: viewResources,
});

to this

this.au.enhance({
    component: { items: this.items },
    host: itemList,
});

Explanation of the references is the former (what follows is Au1 code):

  • element: markup that looks like this: <dx-template name="content">A Button</dx-template>.
  • bindingContext: from scope.bindingContext.
  • overrideContext: from scope.overrideContext.
  • resources: scope.owningView.resources.

The scope, as used above, is passed down from the third-party component wrapper (written by me) and defined as

bind(bindingContext, overrideContext) {
        super.binding();

        const scope = {
            owningView: this.owningView,
            bindingContext: bindingContext,
            overrideContext: overrideContext,
        };
...
}

where this.owningView originates in Aurelia’s created hook of the wrapper. I store it on the wrapper’s instance.

Finally, as you can see from the Au1 code, I need to get back a view. The reason for that is that right after the call to templatingEngine.enhance, I have to do this:

// Register a Dx remove event, dxremove, so that we have an opportunity
// to call the Aurelia "destroy" lifecycle callbacks (careful to call
// #detach first, then #unbind)
dxEventOn(newElement, 'dxremove', () => {
    view.detached();
    view.unbind();
});

where newElement is simply a clone of the dx-template that gets appended to the rendering container provided by the third-party component (the rendering container shouldn’t be relevant in Au2 as it isn’t in Au1, either). I’m already aware that the lifecycle hooks have different names. Most likely, this will become:

// Register a Dx remove event, dxremove, so that we have an opportunity
// to call the Aurelia "destroy" lifecycle callbacks (careful to call
// #detach first, then #unbind)
dxEventOn(newElement, 'dxremove', () => {
    view.detaching();
    view.unbinding();
});

if, indeed, Au2 offers view parity with IEnhancedView.

Maybe you can follow this tutorial: https://docs.aurelia.io/tutorials/synthetic-view
it shows how to manually specify scope with context, override context and parent scope.
But it requires converting your element to template - it will be removed from DOM and new enhanced version will be added to DOM.
The above technique will not work if you want to keep DOM element references and javascript events working, that were added to DOM element before enhancing .

I have asked the same questions here: How to enhance single HTML element without wrapping it with <template> tag or other container?

1 Like

In synthetic view resources are controlled by controller specified in step 5:

 public async add() {
    //...
    // Step#5: Create and activate view
    const view = this.view = factory
///
      .create(controller)  // <- View will use resources defined in this controller
////
      .setLocation(loc); //<-- render location created in Step#2

    // binds the view with the given scope and attaches it to the DOM
    await view.activate(
      view,
      controller,
      LifecycleFlags.none,
      Scope.create(this.bc),
    );
  }

Thank you for your reply! You know, I looked at this about a week ago, when I began this journey, and had no idea really what I was looking at. Now, having done a deep dive on the migration from enhance to the new enhance, it makes much more sense.

I will take a look at this. Superficially, it would appear that this is exactly what I’m looking for. It might be that I was too much trying to shove Au2 into an Au1 mindset. I need to start from scratch on this particular service I’m trying to migrate.

I just finished taking a look at the tutorial and the context of Synthetic View. I’m afraid this has nothing to do with my use case.

Interesting, though…

Here’s an example of how to use enhance + process content Au2 - https://discourse.aurelia.io/t/migrating-au1s-enhance-to-au2s-enhance/6321 - StackBlitz

You’ll see:

  • process content extracting the content of a custom element
  • enhancing with custom override context, based on a bindable
  • lifecycle hooks example (created hook)
  • some lower level interfaces/utilities you may need

Extra resources is not in the example, since you mentioned view resources, but you can either register everything globally, or add a post to request an example of that, that works too :wink:

2 Likes

I was just about to post a question about getting enhance. I’m not sure I’m following what is going on yet as the stackblitz seems to be specific to the original post.

I have a case where I’m using a 3rd party dialog (syncfusion) and I want to inject a component into the body. In my au1 project I was originally doing this, prior to using enhance.

     const compositionContext = {
            container: this.container.createChild(),
            viewModel: DialogRenderer,
            model: model,
            host: element,
            bindingContext: null,
            viewResources: null,
            viewSlot: new ViewSlot(element, true)
        };

        const compositionEngine = this.container.get(CompositionEngine) as CompositionEngine;
        compositionEngine.compose(compositionContext).then((view: any) => {
            compositionContext.viewSlot.attached();
        });
        return compositionContext;

I’ve since started using enhance as it just looks cleaner, but right now I’m not sure what the best path is. I’m not sure if I can just call au-compose and enhance on that?

from the look of your code, maybe you can just use <au-compose>:

<au-compose component.bind="YourSyncFusionComponent">

Or maybe you can put some example here?

I’m sorry I should have been more specific. I am replacing the content of a syncfusion control with my own, so Aurelia doesn’t know about the content. Specifically I am using their dialog control because it gave me a few bonuses back in the day that I didn’t want to deal with. So dialog opens, I set the content and need to aurelify it.

that still sounds like you can use compose, but enhance works too

<au-compose template.bind="myDialogContent">
class MySyncfusionDialog {
  activate(model) {
    this.myDialogContent = model.html;
  }
}

or maybe it’s something else :smile:

I ended up using enhance… Far too simple. Things looked different and I had other things not yet migrated that put me in a weird case.

I’m not sure if there is another way. the syncfusion controls dialog content looks like this . and it’s added when I trigger my factory to create whatever I want. Wouldn’t I still need to call enhance just to be able to use au-compose since it’s just non aurelia html at that point?

<div id="${this.id}-content">
   <dialog-renderer dialog-instance.bind="model"></dialog-renderer>
</div>

I’m enhancing with

 let contentContainer = element.getElementsByTagName("div")[0];

 const appRoot = await this.aurelia.enhance({
            component: {
                model: this
            }, host: contentContainer as any
        });