cujojs / wire

A light, fast, flexible Javascript IOC container
Other
861 stars 68 forks source link

Context Usage #158

Closed jeffrose closed 10 years ago

jeffrose commented 10 years ago

I've read through the Context documentation and I have a few questions.

Aside from creating child contexts from it, is there anything else you can do with the context object?

// app.js
define( {
    fooWidget: {
        create: 'foo'
    },
    barWidget: {
        create: 'bar'
    }
} );

require( [ 'wire!app' ], function( appContext ){
    //...
} );

// foo.js a.k.a. fooWidget
define( [ 'lookup' ], function( lookup ){
    var bar = lookup( 'barWidget' );
} );

Thanks.

briancavalier commented 10 years ago

Good questions.

Can you inject new components into it and have it re-evaluate?

No. Once a context has finished wiring, it effectively becomes immutable, with no way to "refresh" it (ie like a Spring AppContext). From within a plugin, you can add components programmatically, but only during wiring, not after.

Can you cause a context to inherit from another context?

No. Since there can be dependencies between parent and child, the new parent (and its ancestors) would have to satisfy all dependencies of the child, which is probably doable. It's also not clear what this would mean for the child in many cases. For example, there could be various types of connections to components higher in the hierarchy, or child components that have been AOP'd, so we'd probably have to tear all of that down, and then re-wire the child from zero.

I'm interested to hear what your use case is for this, though. Could you simply wire a new Context using the same input spec, but from a different parent? And destroy the previous child? Something like:

var child = parent.wire(theChildSpec);

// destroy it, then create another, inheriting from a different parent
child = child
    .then(function(child1) { return child1.destroy(); })
    .then(function() { return anotherParent.wire(theChildSpec); });

You wouldn't necessarily have to wait for child to finish being destroyed before wiring another, but that'd be an application level decision, I think ... ie is it safe to do that based on the components and interconnections in the application.

Can you programmatically access the context from within it?

Yes. You can programmatically get at the components in a Context. Because everything is highly async, wiring returns a Promise immediately, so resolving components programmatically always returns a promise as well. Here's an example:

wire(spec).then(function(context) {
    var promiseForA = context.resolve('componentA');
});

Hope that helps!

jeffrose commented 10 years ago

Questions 1 and 2 were regarding approaches on providing default behavior.

In order to have a working app, let's assume your context needs to contain components A, B, C, and D as well any other components unique to your app. Depending on the app you may need to configure A, B, C, or D. In the cases where you do not, I was interested in automatically injecting the components.

For example components B and D are not configured. Instead of explicitly including them they could be injected by an external actor.

define( {
    componentA: {
        create: 'a',
        properties: {
            foo: 'bar'
        }
    },
    componentB: {
        create: 'b'
    },
    componentC: {
        create: 'c',
        properties: {
            qux: 'baz'
        }
    },
    componentD: {
        create: 'c'
    }
} );

Another approach I considered was requiring the inclusion of an injector that would automatically include the components in some way, but I have not really explored that path yet.

define( {
    // Provides A, B, C, and D as-is
    defaultsInjector: {
        create: 'defaultsInjector'
    }
} );

In regards to accessing the context programmatically, is it possible to access it outside of the code that wires the specification? I would like the code within app.js itself be able to potentially access the appContext.

briancavalier commented 10 years ago

you may need to configure A, B, C, or D. In the cases where you do not, I was interested in automatically injecting the components

Another approach I considered was requiring the inclusion of an injector that would automatically include the components in some way, but I have not really explored that path yet.

Ah, ok. Yeah, you can do this kind of programming injection from a plugin, see addInstance and addComponent. Happy to answer questions on those if you need.

Another potential option is to have 2 or more parent specs, and you select the one to use at startup time based on some criteria, or maybe at build time. I've done this in a real app and it worked out well. I think it'd be a good solution as long as the number of combinations is low.

In regards to accessing the context programmatically, is it possible to access it outside of the code that wires the specification? I would like the code within app.js itself be able to potentially access the appContext.

Yep, there are a couple options. When bootstrapping an app with the wire! AMD plugin, the "module" returned by the AMD loader will be the actual context. For example:

curl(['wire!my/spec'], function(context) {
    // Use context here, pass it around, destroy it, etc.
});

Or you can use wire programmatically, in which case it returns a promise for the context:

curl(['wire', 'my/spec'], function(wire, spec) {
    var promiseForContext = wire(spec);
});

Then, once you have the context (or the promise for it) in your hands, you can use it however you need, pass it around, destroy it, etc. Does that help?

jeffrose commented 10 years ago

Thanks for help. That solved my issues.

briancavalier commented 10 years ago

No problem, glad that helped!