Skip to content
This repository has been archived by the owner on May 26, 2019. It is now read-only.

Commit

Permalink
Introduce "Application Concerns" section.
Browse files Browse the repository at this point in the history
Replaces the "Services and Initializers" section and expands its
content to include pages explaining Applications and Instances, as
well as Dependency Injection.

All the examples and text have been reviewed and updated for
clarity and compatibility with Ember 2.1.

One caveat is that the Initializers page references the
`instance-initializer` generator, which is still a WIP at the
moment. I'll try to get that into CLI ASAP.
  • Loading branch information
dgeb committed Oct 1, 2015
1 parent 62d08a7 commit 4308b37
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 189 deletions.
14 changes: 8 additions & 6 deletions data/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,17 @@
- title: "Customizing Serializers"
url: "customizing-serializers"

- title: "Services and Initializers"
url: 'services-and-initializers'
- title: "Application Concerns"
url: 'applications'
pages:
- title: "Services"
url: "services"
- title: "Applications and Instances"
url: "applications-and-instances"
- title: "Dependency Injection"
url: "dependency-injection"
- title: "Initializers"
url: "initializers"
- title: "The Container"
url: "container"
- title: "Services"
url: "services"

- title: "Testing"
url: 'testing'
Expand Down
18 changes: 18 additions & 0 deletions source/applications/applications-and-instances.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Every Ember application is represented by a class that extends
`Ember.Application`. This class is used to declare and configure the many
objects that make up your app.

As your application boots, it creates an `Ember.ApplicationInstance` that is
used to manage its stateful aspects. This instance acts as a container for the
objects instantiated for your app.

Essentially, the `Application` *defines your application* while the
`ApplicationInstance` *manages its state*.

This separation of concerns not only clarifies the architecture of your app, it
can also improve its efficiency. This is particularly true when your app needs
to be booted repeatedly during testing and / or server-rendering (e.g. via
[FastBoot](https://github.com/tildeio/ember-cli-fastboot)). The configuration of
a single `Application` can be done once and shared among multiple stateful
`ApplicationInstance` instances. These instances can be discarded once they're
no longer needed (e.g. when a test run or FastBoot request has finished).
208 changes: 208 additions & 0 deletions source/applications/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
Ember applications utilize the [dependency
injection](https://en.wikipedia.org/wiki/Dependency_injection) ("DI") design
pattern to declare and instantiate classes of objects and dependencies between
them. Applications and application instances each serve a role in Ember's DI
implementation.

An `Ember.Application` serves as a "registry" for dependency declarations.
Factories (i.e. classes) are registered with an application, as well as rules
about "injecting" dependencies that are applied when objects are instantiated.

An `Ember.ApplicationInstance` serves as a "container" for objects that are
instantiated from registered factories. Application instances provide a means to
"look up" (i.e. instantiate and / or retrieve) objects.

> _Note: Although an `Application` serves as the primary registry for an app,
each `ApplicationInstance` can also serve as a registry. Instance-level
registrations are useful for providing instance-level customizations, such as
A/B testing of a feature._

## Factory Registrations

A factory can represent any part of your application, like a _route_,
_template_, or custom class. Every factory is registered with a particular key.
For example, the index template is registered with the key `template:index`, and
the application route is registered with the key `route:application`.

Registration keys have two segments split by a colon (`:`). The first segment is
the framework factory type, and the second is the name of the particular
factory. Hence, the `index` template has the key `template:index`. Ember has
several built-in factory types, such as `service`, `route`, `template`, and
`component`.

You can create your own factory type by simply registering a factory with the
new type. For example, to create a `user` type, you'd simply register your
factory with `application.register('user:user-to-register')`.

Factory registrations must be performed either in application or application
instance initializers (with the former being much more common).

For example, an application initializer could register a `Logger` factory with
the key `logger:main`:

```app/initializers/logger.js
export function initialize(application) {
var Logger = Ember.Object.extend({
log(m) {
console.log(m);
}
});

application.register('logger:main', Logger);
}

export default {
name: 'logger',
initialize: initialize
};
```

### Registering Already Instantiated Objects

By default, Ember will attempt to instantiate a registered factory when it is
looked up. When registering an already instantiated object instead of a class,
use the `instantiate: false` option to avoid attempts to re-instantiate it
during lookups.

In the following example, the `logger` is a plain JavaScript object that should
be returned "as is" when it's looked up:

```app/initializers/logger.js
export function initialize(application) {
var logger = {
log(m) {
console.log(m);
}
};

application.register('logger:main', logger, { instantiate: false });
}

export default {
name: 'logger',
initialize: initialize
};
```

### Registering Singletons vs. Non-Singletons

By default, registrations are treated as "singletons". This simply means that
an instance will be created when it is first looked up, and this same instance
will be cached and returned from subsequent lookups.

When you want fresh objects to be created for every lookup, register your
factories as non-singletons using the `singleton: false` option.

In the following example, the `Message` class is registered as a non-singleton:

```app/initializers/logger.js
export function initialize(application) {
var Message = Ember.Object.extend({
text: ''
});

application.register('notification:message', Message, { singleton: false });
}

export default {
name: 'logger',
initialize: initialize
};
```

## Factory Injections

Once a factory is registered, it can be "injected" where it is needed.

Factories can be injected into whole "types" of factories with *type
injections*. For example:

```app/initializers/logger.js
export function initialize(application) {
var Logger = Ember.Object.extend({
log(m) {
console.log(m);
}
});

application.register('logger:main', Logger);
application.inject('route', 'logger', 'logger:main');
}

export default {
name: 'logger',
initialize: initialize
};
```

As a result of this type injection, all factories of the type `route` will be
instantiated with the property `logger` injected. The value of `logger` will
come from the factory named `logger:main`.

Routes in this example application can now access the injected logger:

```app/routes/index.js
export default Ember.Route.extend({
activate() {
// The logger property is injected into all routes
this.get('logger').log('Entered the index route!');
}
});
```

Injections can also be made on a specific factory by using its full key:

```js
application.inject('route:index', 'logger', 'logger:main');
```

In this case, the logger will only be injected on the index route.

Injections can be made onto any class that requires instantiation. This includes
all of Ember's major framework classes, such as components, helpers, routes, and
the router.

### Ad Hoc Injections

Dependency injections can also be declared directly on Ember classes using
`Ember.inject`. Currently, `Ember.inject` supports injecting controllers (via
`Ember.inject.controller`) and services (via `Ember.inject.service`).

The following code injects the `shopping-cart` service on the `cart-contents`
component as the property `cart`:

```app/components/cart-contents.js
export default Ember.Component.extend({
cart: Ember.inject.service('shopping-cart')
});
```

If you'd like to inject a service with the same name as the property, simply
leave off the service name (the dasherized version of the name will be used):

```app/components/cart-contents.js
export default Ember.Component.extend({
shoppingCart: Ember.inject.service()
});
```

## Factory Lookups

The vast majority of Ember registrations and lookups are performed implicitly.

In the rare cases in which you want to perform an explicit lookup of a
registered factory, you can do so on an application instance in its associated
instance initializer. For example:

```app/instance-initializers/logger.js
export function initialize(applicationInstance) {
var logger = applicationInstance.lookup('logger:main');

logger.log('Hello from the instance initializer!');
}

export default {
name: 'logger',
initialize: initialize
};
```
100 changes: 100 additions & 0 deletions source/applications/initializers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Initializers provide an opportunity to configure your application as it boots.

There are two types of initializers: application initializers and application
instance initializers.

Application initializers are run as your application boots, and provide the
primary means to configure [dependency injections](./dependency-injection) in
your application.

Application instance initializers are run as an application instance is loaded.
They provide a way to configure the initial state of your application, as well
as to set up dependency injections that are local to the application instance
(e.g. A/B testing confurations).

Initializers can perform any configuration operations, as long as they are
synchronous. The user experience is degraded by attempting to block the booting
of your application with asynchronous operations.

> _Note: Any asynchronous loading conditions (e.g. user authorization) should be
placed in your application route's hooks, which allows for DOM interaction while
waiting for conditions to resolve._

## Application Initializers

Application initializers can be created with Ember CLI's `initializer`
generator:

```bash
ember generate initializer shopping-cart
```

Let's customize the `shopping-cart` initializer to inject a `cart` property into
all the routes in your application:

```app/initializers/shopping-cart.js
export function initialize(application) {
application.inject('route', 'cart', 'service:shopping-cart');
};

export default {
name: 'shopping-cart',
initialize: initialize
};
```

## Application Instance Initializers

Application instance initializers can be created with Ember CLI's
`instance-initializer` generator:

```bash
ember generate instance-initializer logger
```

Let's add some simple logging to indicate that the instance has booted:

```app/instance-initializers/logger.js
export function initialize(applicationInstance) {
var logger = applicationInstance.lookup('logger:main');
logger.log('Hello from the instance initializer!');
}

export default {
name: 'logger',
initialize: initialize
};
```

## Specifying Initializer Order

If you'd like to control the order in which initializers run, you can use the
`before` and/or `after` options:

```app/initializers/config-reader.js
export function initialize(application) {
// ... your code ...
};

export default {
name: 'configReader',
before: 'websocketInit',
initialize: initialize
};
```

```app/initializers/websocket-init.js
export function initialize(application) {
// ... your code ...
};

export default {
name: 'websocketInit',
after: 'configReader',
initialize: initialize
};
```

Note that ordering only applies to initializers of the same type (i.e.
application or application instance). Application initializers will always run
before application instance initializers.
Loading

0 comments on commit 4308b37

Please sign in to comment.