Can you Precompose a View-Model on Injection?


#1

if I have a view-model that I intend to compose, how can I @inject an instance of that view-model into another control to compose without breaking the @inject(Element) on the injected view-model.

Here is what I would like to do:

// input control
import { inject } from 'aurelia-framework';
@inject(Element)
export class input {
  constructor(element, options) {
    //ref to element should be the input control
  }
  //other stuff
}
//Form control that might need an input
import { inject, Factory, Container } from 'aurelia-framework';
import input from 'control/input';

@inject(Factory.of(input))
export class form {
  constructor(getInput) {
     this.controls = [];
     this.getInput = getInput;
    //when input is needed in this form
   
  }
  build(controls) {
    for (let control in controls) {
       this.controls.push(this.getInput(control));
    }    
  }
}
<!-- Form View -->
<template>
  <form >
    <template repeat.for="control of controls">
      <compose containerless view-model.bind="control"></compose>
    </template>
  </form>
</template>

Now this is a very trimmed down example and it unfortunately does not work the way I would expect. When the input is pre-built using the Factory the Element that is injected into the constructor is not the input document fragment you would get if you composed the control normally and passed the options into the activate method. Now with this example the obvious solution would be to do this:

// input control
import { inject } from 'aurelia-framework';
@inject(Element)
export class input {
  constructor(element) {
    //ref to element should be the input control
  }
  activate(options){
  //other stuff
  }
}
//Form control that might need an input
import { inject, Factory, Container } from 'aurelia-framework';
import input from 'control/input';

export class form {
  constructor(getInput) {
     this.controls = [];
     this.inputVM = input;
   
  }
  build(controls) {
    this.controls = controls;
  }
}
<!-- Form View -->
<template>
  <form >
    <template repeat.for="control of controls">
      <compose containerless view-model.bind="$parent.inputVM"  model.bind="control"></compose>
    </template>
  </form>
</template>

But with this solution we no longer have a reference to the array of control instances which is needed to handle other functionality. I am looking for a way to “pre-compose” a view-model to get an immediate reference to its instance.


#2

https://codesandbox.io/s/ox1wrvm35 Here is an example of what you want to do. I’d suggest doing it in easier way. Pre-creating view model is probably unnecessary.


#3

Really neat example, this will come in handy for some dynamic form stuff I have in mind for my current project, thanks! :+1:


#4

I think I misunderstood you, is this correct that you wanna get the reference to the <compose/> element in each control view model?


#5

@bigopon no I think you understood perfectly and your example is doing what I would expect (and want). Now I have to figure out why my code is not working properly. When I do this the @inject(Element) is referencing the application element.


#6

It typically happens when you invoke the instance too early, and the host (read element) hasnt been registered yet. Or the element yoy would expect is decorated with containerless.


#7

There are a few things that I suspect might be an issue. In your example how is it that your xform and xinput controls are registered as custom elements?


#8

Nevermind, it looks like your example does not do want I thought it was doing.


On construction the element that is passed in is the xForm element. Now I realize that the injected element is the container.element which means that the element I was seeing when I was looking at the composed viewmodel was just the compose tag since the compose binding creates a child container. Thanks, this helps me understand what is going on a bit better.