Using a container
Working with static instances of stores makes you inflexible regarding the reuse of the stores.
Therefore, rfluxx provides an integrated container that works good for its purposes.
The container has a very simple but powerful structure. You register functions that will create an instance of a type when required. Let's call this a creation rule. A creation rule gets the container as an argument and can resolve all other registrations in this container. In this way you can easily inject dependencies into your classes. This approach automatically solves problems like the ordering of creating instances and only creating required instances.
The power of the container then mostly comes from managing instances created from registered creation rules.
It provides several features to do so:
- register creation rules under one or multiple string keys
- register creation rules to be added to one or multiple collections of creation rules
- resolving default/named instances of a registered creation rule (only one instance is created per rule and name)
- resolving collections of default/named instances created through different creation rules (the collection contains all the default instances or instances with the specified name of each relevant rule)
There are reasons for this very specific feature set.
We use the container to control that stores which hold the state of our app are kept in memory. Therefore, the container creates and remembers all created instances, so that they can be resolved again later. This is important if we use react navigation frameworks that take components referencing a store completely out of memory. This would mean we loose our state if the stores are not referenced anymore in some other component. Keeping all stores in the container avoids this problem.
Second there are cases when you want to use several instances of a store for different components in the UI. For this case you can either register multiple creation rules or resolve named instances of the same rule. Both strategies have the effect that you get different instances of the same store type back.
Container workflow
Container usage is a three step process:
- Register creation rules
- Build container
- Resolve instances from container
const builder = new SimpleContainerBuilder();
builder.register(...).as(...);
const container = builder.build();
const instance = container.resolve<...>(...);
How to
Atomic creation rule
Register creation of class without dependencies
builder.register(c => new MyClassWithoutDependencies())
Dependent creation rule
Register creation of class with dependencies
// register atomic dependency
// ...
// register dependent type
builder.register(c => new MyClassWithDependencies(
c.resolve<MyClassWithoutDependencies>("MyClassWithoutDependencies")))
Static creation rule
Register an already existing instance
const existingInstance = // ...
builder.register(c => existingInstance)
Coded creation rule
Register some complex piece of code that creates an instance
builder.register(c => {
// fancy code to create instance
return myInstance;
})
Register under key
Register a creation rule under one or multiple string keys
builder.register(c => /* ... */).as("IMyType");
By convention the key could be the interface that is implemented by the returned instance.
Register in collection
Register a creation rule into one or several collections
builder.register(c => /* ... */).in("IDoStuffInCollection[]");
If you resolve such a collection you will get back one instance of all registrations in the collection.
By convention the name of a collection should be the type of interface named followed by an array, e.g. IDoStuffInCollection[]
.
Mix and match registration
You can mix and match all of the above techniques:
builder.register(c => /* ... */)
.in("IDoStuffInCollection[]")
.as("IMyType")
.as("ISecondInterface");
The above registration can be resolved by the names IMyType
or ISecondInterface
. Additonally when the collection IDoStuffInCollection[]
is resolved the same instance resolved through IMyType
or ISecondInterface
will be part of the collection.
Resolve a default instance
Resolve the default instance from the container
container.resolve<IMyType>("IMyType");
The returned instance is always the same and you will get an exception if it was not registered.
Resolve an optional default instance
Resolve the default instance from the container or get null if no instance was registered under the given key.
// returns null if the key IMyType was not registered
container.resolveOptional<IMyType>("IMyType");
The returned instance is always the same and you will get null if it was not registered.
Resolve named instance
container.resolve<IMyType>("IMyType", "instance1");
The returned instance for one instance name, in this case instance1
is always the same and you will get an exception if IMyType
was not registered.
Resolve collection of default instances
// returns an IDoStuffInCollection[]
container.resolveMultiple<IDoStuffInCollection>("IDoStuffInCollection[]");
Resolve collection of named instances
// returns an IDoStuffInCollection[]
container.resolveMultiple<IDoStuffInCollection>("IDoStuffInCollection[]", "list1");