Au 2.0: Downstream error after resolving a factory with a string

Hello,

Is it permitted to use strings when resolving a factory?

When I do this, I don’t get an error, at least not at this point, but I do downstream:

this.documentFactory = resolve(factory('ItbDocumentFactory'));

My registration in main.js is this:

Registration.transient('ItbDocumentFactory', ItbDocumentFactory),

I then use the factory like this (at the moment, I’m not passing arguments, just to test by slowly building up):

const doc = this.documentFactory();

This is the error caused by the line above:

When I resolve using the actual service, not a string, I don’t get the error. This is fine downstream:

this.documentFactory = resolve(factory(ItbDocumentFactory));

The factory resolver creates a resolver to lazily construct instances of a constructable function (/class). It’s not a resolver to retrieve your registration in this case.

If you want to follow with the pattern described here, you’ll need to do something like this

this.documentFactory = resolve('ItbDocumentFactory') as ItbDocumentFactory;

ItbDocumentFactory is already a factory. Unless the actual code of it tells a completely different story.

(Please note that I do not, as of yet, use ts, only js).

Sorry about that @bigopon . I had created a separate project in preparation for a repro, and I didn’t keep it up to the minute as I was wrestling with this in my main project. I understand why the naming conventions threw you. I just got into the office, and updated the isolated project.

Nevertheless, the problem persists. I use Factory.of all over the place in my Au 1.0 app (the one I’m migrating). I’m the one who posted this over 4 years ago ( Will Factory.of() be supported in Aurelia 2? ):slightly_smiling_face: !

I should have written this in the post:

...
Registration.transient('ItbDocument', ItbDocument)
...

and where the factory is resolved:

this.documentFactory = resolve(factory('ItbDocument'));

and where the factory is used:

this.document = this.documentFactory();
1 Like

ahh, that seems to be a missing capability of the current implementation. If it works in v1 then we probably should support it in v2. Currently factory resolution only understands constructables, not a key that resolves to a constructable. Maybe let’s track this with a bug report as well?

@bigopon You know, I don’t know if I ever could register with a string in Au 1.0. So I don’t want to mislead here.

It just seemed natural based on what you guys have done.

In migrating to 2.0, I’m taking advantage of the opportunity to improve my tabbed viewer. My tabbed viewer stores durable session data in jsonb columns on the backend, which includes metadata that tells me how to rehydrate upon retrieval into a business model (and then layer on the notion of “document” on top of that).

I seem to remember not being able to do this in 1.0, where even Rob himself might have been the one to tell me that (or perhaps Ashley). I just don’t recall:

...register('SomeService', SomeService)...

Perhaps later on, 1.0 was enhanced to allow this. Maybe I missed it in the documentation. I never did revisit the issue. In 1.0, we were taught to stay away from the DI container except in very specific circumstances.

So I did…

It would be quite useful to be able to rehydrate right from string-based metadata (rather than set up a giant switch statement, or write a lot of specific code for each document type). I could store ‘Itb’ as a meta key, and resolve as metaKey + ‘Document’, for example.

there’s nothing wrong with having a string as a key to resolve some instances. What we have in this thread is the ability to resolve a key to a factory via factory resolver. I think v1 allows it, but I’m not able to replicate the scenario you have. I’ve prepared some basic repro here https://gist.dumber.app/?gist=43495e4c30d304b065c79c5318568388, can you help fork and make it similar to the code you got?

I’m a little confused. Your repro is Au 1.0. I’m migrating to 2.0.

What you’ve shown, though, confirms what I said in my last post that I didn’t even know this was available in Au 1.0. For example, I didn’t know until now (after all these years) that we could use Factory.of at the point of global registration. I used it only in constructor-based injection (@inject(Factory.of(...)).

(I wish I had known that we could do this in 1.0. That would’ve allowed me to streamline my code).

So, is the conclusion here that 2.0 has no analog for this:

aurelia.container.registerResolver('ItbFactory', Factory.of(MyFactory));

Are you able to reproduce the error I’m getting in 2.0 with the approach I gave earlier in this thread?

in v2, the code you had earlier won’t work, so it’s guaranteed an error. What I was asking for is the code you got with v1 that enables a similar capability in v1. For v2, the API would be like this once it’s supported:

container.register(Registration.singleton('ItbFactory', MyFactory))

...

factory = resolve(factory('ItbFactory'))

Ah, sorry about that. I misunderstood what you were after.

I would adjust your example to this:

container.register(Registration.transient('ItbDocument', ItbDocument))

...

documentFactory = resolve(factory('ItbDocument'))

If you recall, at the outset this morning, I confused both of us with naming conventions I hadn’t adjusted :face_with_open_eyes_and_hand_over_mouth: .

Looking at the last line above, if we could do that, call factory on a string, then my problem would be solved (as it would be for others looking to leverage runtime hydration and composition).

The only problem I see with your 1.0 repro is this:

static inject = ['ItbDocument'];

This, too, in 1.0, if possible, would be a problem:

@inject(Factory.of('ItbDocument'))

In the cases above, the string can’t be parameterized. It has to be hard-coded, which is not good.

The 2.0 idiom is definitely superior in this case.

I just noticed a subtlety in your example. You registered a singleton; I’m thinking in terms of a transient…perhaps erroneously.

To my mind, we would do this:

container.register(Registration.transient('ItbDocument', ItbDocument))
documentFactory = resolve(factory('ItbDocument'))

But, then, if factory resolves a new instance each time, I can understand why you showed me this:

container.register(Registration.singleton('ItbDocument', ItbDocument))
documentFactory = resolve(factory('ItbDocument'))

This would be the difference between property-based resolution and constructor-based resolution (or static injection), if I understand 2.0 correctly.

Thank you @bigopon for implementing string-based resolution of factories!

Once I installed RC, I modified my test harness in this way:

@customElement('test-module')
export class TestModule {
  constructor() {
    ...
    this.myWorkModelFactory = resolve(factory('PipelineMyWorkModel'));
  }

  bound() {
    if (!this.myWorkMgr.myWorkCount) {
      const myWork = [];

      for (let _i = 9; _i >= 0; _i--) {
        const myWorkModel = this.myWorkModelFactory(
          {
            name: 'Project with Long Name ' + _i,
            storeNumber: Math.floor(Math.random() * 4000),
            jobsiteProfile: 1,
          },
          undefined,
          { validatePartial: false },
        );

        // const myWorkModel = new PipelineMyWorkModel().init(
        //   {
        //     name: 'Project with Long Name ' + _i,
        //     storeNumber: Math.floor(Math.random() * 4000),
        //     jobsiteProfile: 1,
        //   },
        //   undefined,
        //   { validatePartial: false },
        // );

        myWork.push(myWorkModel);
      }

      this.myWorkMgr.addMyWork(myWork);
      this.myWorkMgr.loadFirstMyWork();
    }
  }
}

Worked like a charm! I can now abandon the “init” pattern. Also, this will greatly facilitate rehydrating session.

Thanks, again!

2 Likes

not compatible with Aurelia 2.0.
Factory() does not accept a string key; it only takes a constructable (class). The container fails when you actually call the factory because it cannot decide what to instantiate when you supply a string.

This is true:
this.documentFactory = resolve(factory(ItbDocument));
this.document = this.documentFactory();

resolve(factory(‘ItbDocument’));
String keys are not used for factory construction, but rather for container lookup. that the Factory.of() functionality of au1 does not extend to au2.

I’m not sure I understand what you mean.

What, exactly, is not compatible with Aurelia 2.0? With Aurelia 2.0 RC, we can now resolve a factory using string keys.

Or are you saying that Factory.of() in Aurelia 1.0 doesn’t accept strings, only a constructible class?

In Aurelia 2.0, there is a decorator-based approach to factories:

@inject(factory(MyService))
export class MyClass {}

I’m not sure if string-based keys work in Aurelia 2.0 with this approach.

Feels like a bot answer. I could be wrong. I just had a similar style response on my thread

1 Like

That would be a shame. I got excited there for a minute when I saw someone was posting for the first time.

in Aurelia 2, factory() doesn’t work with string keys like Au 1 did. it expects a class/constructable, not 'ItbDocument'.

so this:

resolve(factory('ItbDocument'))

is wrong.

use:

resolve(factory(ItbDocument))

or skip factory() and just:

() => resolve('ItbDocument')

factory() in Aurelia 2 is class-based. that’s why you’re getting the downstream error.