Flask is a tiny yet powerful IOC Container for your projects based on javascript, including nodejs. It resolves dependencies for you automaticly and improves your development flow tidying up your code.
It offers support for services
, singletons
, parameters
and much more... let's dive in!
- Installation
- Parameters
- Services & Singletons
- Tagging
- Decorators
- Calling & Wrapping
- Eventing
- Config
- Setting up Flask on bootstrap
- Merging different Flask configuration objects
bower install flask-container --save
npm install flask-container --save
git clone https://github.com/Aferz/flask
And reference it in your html:
<script src="path/of/my/libs/flask/dist/flask.min.js"></script>
Parameters are simple values that are resolved for the container. It can store any kind of value.
It registers a parameter into the container:
var flask = new Flask(); // Creates our awesome container instance
flask.parameter('heroe', 'Superman');
Returns registered parameter out the container:
var heroe = flask.value('heroe'); // Superman
Such impressive, isn't it? Let's step it further. With Flask you can inject parameters by reference just enclosing the name of one registered parameter between the symbol %:
flask.parameter('heroe', 'Superman');
flask.parameter('heroe2', '%heroe%');
flask.parameter('heroe3', '%heroe2%');
var heroe = flask.value('heroe3'); // Superman
Not bad. By the way, the order of registering doesn't matter.
Services are a fancy word to describe super cool reusable objects. That's all. Flask helps you building this objects for you and resolving its dependencies. This way you allways know your objects are created in the same way, making your code less complex, more secure and reliable.
Important note: all services are created with the new keyword. Don't say I didn't tell you.
Register a service. Returns new instance on every call:
function Villain(weapon) {
this.weapon = weapon;
}
var heroe = flask.service('Villain', Villain, ['lazer']);
Now we can create a Villain instance when we need it.
Of course, there is only one superman so we'll use a singleton for this case. A singleton returns the same instance once resolved for first time.
function Superman(superPower1, superPower2) {
this.superPower1 = superPower1;
this.superPower2 = superPower2;
}
var heroe = flask.singleton('Superman', Superman, ['xRayVision', 'fly']);
It's time to create some Villains and Heroes
var villain1 = flask.make('Villain'); // Villain instance
var villain2 = flask.make('Villain'); // Another Villain instance
console.log(villain1 === villain2) // false
var superman1 = flask.make('Superman'); // Superman instance
var superman2 = flask.make('Superman'); // The same Superman instance
console.log(superman1 === superman2) // true, I promise
Flask can resolve any dependency either strings, booleans, objects, functions, numbers ... even services using @, parameters using % and tags using #:
function Superman(Batman, superPower) {
this.Batman = Batman;
this.superPower = superPower;
}
function Batman(superPower) {
this.superPower = superPower
}
flask.parameter('superPower.superman', 'X-rays');
flask.parameter('superPower.batman', 'Rich');
flask.service('Superman', Superman, ['@Batman@', '%superPower.superman%']);
flask.service('Batman', Batman, ['%superPower.batman%']);
var superman = flask.make('Superman');
console.log(superman.Batman); // Instance of Batman
console.log(superman.superPower); // X-Rays
console.log(superman.Batman.superPower); // Rich
This function support infinite nested dependencies. (Not really, javascript will crash when it reach the maximum call stack size.)
Note: Don't try to inject a service into a parameter with '@' syntax, it just won't work and it'll return the string. That's not the purpose of parameters.
Tagging it's the way Flask offers to classify and resolve multiple primitive types, services, parameters or even tags at once.
function Superman() {}
function WonderWoman() {}
function Batman() {}
flask.parameter('Aquaman', 'Ocean'); // Dummy parameter, ok
flask.service('Superman', Superman);
flask.service('WonderWoman', WonderWoman);
flask.service('Batman', Batman);
flask.tag('JusticeLeague', ['@Superman@', '@WonderWoman@', '@Batman@', '%Aquaman%']);
With this method we'll retrieve the registered tag out the container:
var justiceLeague = flask.tagged('JusticeLeague')
console.log(justiceLeague[0]); // Instance of Superman
console.log(justiceLeague[1]); // Instance of WonderWoman
console.log(justiceLeague[2]); // Instance of Batman
console.log(justiceLeague[3]); // 'Ocean'
Of course, Flask offers support for resolving tags by reference:
flask.tag('JusticeLeague', ['Superman', 'WonderWoman']);
flask.tag('Avengers', ['IronMan', 'CaptainAmerica']);
flask.tag('Superheroes', ['#JusticeLeague#', '#Avengers#']);
var superHeroes = flask.tagged('Superheroes');
console.log(justiceLeague[0][0]); // Superman
console.log(justiceLeague[0][1]); // WonderWoman
console.log(justiceLeague[1][0]); // IronMan
console.log(justiceLeague[1][1]); // CaptainAmerica
Note: Don't try to inject a tag into a parameter with '#' syntax, it just won't work and it'll return the string. That's not the purpose of parameters.
Flask offers functionality for decorate your services/singletons and parameters. A decorator its to add functionality to an object without touch its original source.
This is the proper way to register decorators and it can be used on two ways:
flask.decorate(function (instance, flask) {
// this will be executed before instance is returned for EVERY service/parameter resolved
});
flask.decorate('ServiceA', function (instance, flask) {
// this will be executed before instance is returned ONLY for ServiceA
});
Note: Decorators are executed in order they were registered.
Sometimes we'll need to call or create a function injecting directly any dependency. Flask give us two methods that will help us on this task.
This method will call given function injecting specified dependencies as second argument. Let see an example:
function SaveTheWorld(Superman, Batman) {
console.log(Superman);
console.log(Batman);
}
flask.service('Superman', Superman);
flask.service('Batman', Batman);
flask.call(SaveTheWorld, ['@Superman@' '@Batman@']); // Function will be inmediately called
// Log: Instance of Superman
// Log: Instance of Batman
As you can see, this method will call inmediately the passed definition, but ... What if you just want to resolve the arguments of the definition and store the result function in a variable to call it later? Flask to the rescue again:
// We will use the previous example
var resolvedFunc = flask.wrap(SaveTheWorld, ['@Superman@' '@Batman@']);
// Function hasn't been called automaticly
resolvedFunc()
// Log: Instance of Superman
// Log: Instance of Batman
Note: Both, call and wrap accept a third parameter to bind a context into the given function.
Wouldn't be nice if you could listen events emitted by Flask? Well, i have good news for you. There are two ways when registering listeners:
flask.<eventName>(function (instance, flask) {
// this will be executed before instance is returned for EVERY service/parameter resolved
});
flask.<eventName>('ServiceA', function (instance, flask) {
// this will be executed before instance is returned ONLY for ServiceA
});
Here we have a list of currently supported events:
This method will register a listener that will be fired after service/parameter is resolved and instantiated:
flask.onResolved(function (instance, flask) {
// this will be executed before instance is returned for EVERY service/parameter resolved
});
We can modify the internals of our Flask container through configuration variables. Here is a list of variables that can be modified:
Name | Default value | What it does |
---|---|---|
serviceDelimiter | @ | It delimites the reference of a service when resolving arguments injection. |
parameterDelimiter | % | It delimites the reference of a parameter when resolving arguments injection. |
parameterDelimiter | % | It delimites the reference of a parameter when resolving arguments injection. |
Set a config value:
flask.setConfigValue('serviceDelimiter', '#');
Get config value by its key:
var serviceDelimiter = flask.cfg('serviceDelimiter'); // '@'
In order to mantain good separation of concerns, Flask can be configured during its instantiation using a configuration object. This will allow to have our dependencies in one place (or many, with diferent config files). Let's see a full example.
function serviceA () {}
function serviceB () {}
function serviceC () {}
function aliasCListener () {}
function globalListener () {}
function param3Decorator () {}
function aliasADecorator () {}
function globalDecorator () {}
var config = {
config: {
serviceDelimiter: '#',
paramDelimiter: '~'
},
parameters: {
param1: 'Parameter 1',
param2: {
value: '%param1%',
tags: ['tag1']
},
param3: {
value: 'Parameter 3',
decorators: [param3Decorator]
}
},
services: {
aliasA: {
service: serviceA,
arguments: ['@aliasB@'],
tags: 'tag1',
decorators: [aliasADecorator]
},
aliasB: {
service: serviceB,
singleton: true,
tags: ['tag1', 'tag2']
},
aliasC: {
service: serviceC,
listeners: {
resolved: [aliasCListener]
}
}
},
listeners: {
resolved: [globalListener]
},
decorators: [globalDecorator]
}
(WIP)
Consider contributing opening an issue or opening a pull request.
Flask is open-sourced software licensed under the MIT license
To Symfony and its IOC Container, that it obviously inspired me to build this.