Compose from compositionEngine.compose does not trigger attached method on viewModel

I changed the title to hopefully get some help.

I’m creating components dynamically with code that looks like this. The activate method is never called on my viewModels. What am I doing wrong.

import { bindable, observable, autoinject, CompositionEngine, CompositionContext, Container, ViewSlot } from "aurelia-framework";

@autoinject
export class ComponentRenderer {
  @bindable
  public model: any = null;
  @bindable
  public viewModel: any = null;

  @observable
  element: HTMLDivElement = null;

  constructor(private compositionEngine: CompositionEngine, private container: Container) {

  }


  elementChanged() {
    if (this.element !== null) {
      let context = this.createContext(this.viewModel, this.element, this.model);
      let _this = this;
      this.compositionEngine.compose(context).then((view: any) => {
        console.log("rendered")
      });
    }
  }

  createContext(viewModel: object, host: Element, model: object): CompositionContext {
    return {
      container: this.container.createChild(),
      viewModel: viewModel,
      model: model,
      host: host,
      bindingContext: null,
      viewResources: null,
      viewSlot: new ViewSlot(host, true)
    };
  }

  detached() {
    this.element = null;
    this.viewModel = null;
    this.model = null;
  }
}

1 Like

No one?? :frowning: It might be that I just can’t read the documentation on how to trigger the lifecycle this way

1 Like

Looks ok to me - I’ve tested in Gist and this works, did you forget to set this.compositionEngine and this.container?

Is the idea that this targets an element on the page to use as the host for a VM (and the VM resolves the view as per the usual conventions?)

I pointed element at a div that I ref’d on the page and it rendered the VM/View into that div correctly.

I did have to do this though (I don’t think autoinject does this for you, and I’m not sure if this is just an omission by accident)

constructor(compositionEngine, container) { this.container = container; this.compositionEngine = compositionEngine; }

1 Like

Thanks for taking the time to assist. It will render properly for me too, if you put an activate method on your vm, is it getting called? I see that I wasn’t clear as to what my issue really is. Everything will render. Bind will get called on the VM, but not Activate

1 Like

Yep, I’d post the Gist but for some reason it won’t let me save - activate is definitely called:

  • can you show more code - maybe the view that does the compositing?
1 Like

Ah turns out I wasn’t signed in…

2 Likes

Thanks ,
I’m hoping i can get back into this within the next day or so. I had to move onto a different task. Thanks for taking the time to work this up. I’ll try and get more code online so that someone can hopefully see what breaks in my lifecycle

1 Like

Well i see that your solution works, I’m not sure why it works. I also tried a similar approach on my end and I still cannot get attached to fire. I’m trying to keep the usage of this to only requiring the <component-renderer view-model.bind="itemTemplateViewModel" model.bind="model"> </component-renderer> This is very useful in a custom listbox I built that accepts a view/viewModel as the template.

If I use the compose tag passing in the path to the viewModel, all works as expected. Pretty much all I’m trying to do is build another version of the compose tag that accept a function instead of the module path. I’m really confused why all rendering is okay, but the activate method is not being called

View TestPage

<template>
  <component-renderer view-model.bind="itemTemplateViewModel" model.bind="model">
  </component-renderer>
</template>

ViewModel: TestPage

import { ListBoxItemViewModel } from "listboxItemViewModel";

export class ListboxTest {
  itemTemplateViewModel = ListBoxItemViewModel;
  model = { text: "Item 1", canClick: false }; 
}

ViewModel: ComponentRenderer

import { bindable, autoinject, CompositionEngine, CompositionContext, Container, ViewSlot } from "aurelia-framework";

@autoinject

export class ComponentRenderer {
  @bindable
  public model: any = null;
  @bindable
  public viewModel: any = null;

  rendererContainer: HTMLDivElement = null;

  constructor(private compositionEngine: CompositionEngine, private container: Container) {

  }


  createContext(viewModel: object, host: Element, model: object): CompositionContext {
    return {
      container: this.container.createChild(),
      viewModel: viewModel,
      model: model,
      host: host,
      bindingContext: null,
      viewResources: null,
      viewSlot: new ViewSlot(host, true)
    };
  }

  attached() {
    console.log("component rendered attached");
    if (this.rendererContainer !== null) {
      let context = this.createContext(new this.viewModel(), this.rendererContainer, this.model);

      this.compositionEngine.compose(context).then((view: any) => {
        console.log("rendered")
      });
    }
  }

  detached() {
    this.rendererContainer = null;
    this.viewModel = null;
    this.model = null;
  }
}

View: ComponentRenderer

<template>
  <div element.ref="rendererContainer">
  </div>
</template>

ViewModel: ListBoxItemViewModel

export class LisboxItemViewModelBase {
  public item: any = null;

  activate(model) {
    this.item = model;
    console.log("LisboxItemViewModelBase activate");
  }

  attached() {
    console.log("item template attached");
  }
}

export class ListBoxItemViewModel extends LisboxItemViewModelBase {

  attached() {
    console.log("item template attached");
  }
}

View: ListBoxItemViewModel

<template>
  ${item.text}
</template>
1 Like

Solved… I’m not sure how to handle trigging unbind. It doesn’t work as the others. I don’t necessarily care about unbind, i never use it my code, but I’d hate to start using this then need it in the future

I needed to add context.viewSlot.attached(); and detached in the proper method


  attached() {
    if (this.rendererContainer !== null) {
      this.context = this.createContext(new this.viewModel(), this.rendererContainer, this.model);

      this.compositionEngine.compose(this.context).then((view: any) => {
        this.view = view;
        this.context.viewSlot.attached();
      });
    }
  }

  detached() {
    this.context.viewSlot.detached();
    this.rendererContainer = null;
    this.viewModel = null;
    this.model = null;
  }
1 Like