diff --git a/data/pages.yml b/data/pages.yml index 886c8bac1..5ec5a5688 100644 --- a/data/pages.yml +++ b/data/pages.yml @@ -127,15 +127,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' diff --git a/source/applications/applications-and-instances.md b/source/applications/applications-and-instances.md new file mode 100644 index 000000000..70587bba3 --- /dev/null +++ b/source/applications/applications-and-instances.md @@ -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). diff --git a/source/applications/dependency-injection.md b/source/applications/dependency-injection.md new file mode 100644 index 000000000..ec8e2aa13 --- /dev/null +++ b/source/applications/dependency-injection.md @@ -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 +}; +``` diff --git a/source/applications/initializers.md b/source/applications/initializers.md new file mode 100644 index 000000000..edc005ced --- /dev/null +++ b/source/applications/initializers.md @@ -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). + +Operations performed in initializers should be kept as lightweight as possible +to minimize delays in loading your application. Although advanced techniques +exist for allowing asynchrony in application initializers (i.e. `deferReadiness` +and `advanceReadiness`), these techniques should generally be avoided. Any +asynchronous loading conditions (e.g. user authorization) are almost always +better handled 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. diff --git a/source/services-and-initializers/services.md b/source/applications/services.md similarity index 71% rename from source/services-and-initializers/services.md rename to source/applications/services.md index fcad9f36f..3670eeb0c 100644 --- a/source/services-and-initializers/services.md +++ b/source/applications/services.md @@ -13,18 +13,31 @@ Example uses of services include: ### Defining Services -To define a service, extend the `Ember.Service` base class: +Services can be generated using Ember CLI's `service` generator. For example, +the following command will create the `ShoppingCart` service: + +```bash +ember generate service shopping-cart +``` + +Services must extend the `Ember.Service` base class: ```app/services/shopping-cart.js export default Ember.Service.extend({ }); ``` -Like any Ember object, a service can have properties and methods of its own. +Like any Ember object, a service is initialized and can have properties and +methods of its own. ```app/services/shopping-cart.js export default Ember.Service.extend({ - items: [], + items: null, + + init() { + this._super(...arguments); + this.set('items', []); + }, add(item) { this.get('items').pushObject(item); @@ -42,7 +55,7 @@ export default Ember.Service.extend({ ### Accessing Services -To access a service, inject it with `Ember.inject`: +To access a service, inject it either in an initializer or with `Ember.inject`: ```app/components/cart-contents.js export default Ember.Component.extend({ @@ -50,7 +63,8 @@ export default Ember.Component.extend({ }); ``` -This injects the shopping cart service into the component and makes it available as the `cart` property. +This injects the shopping cart service into the component and makes it available +as the `cart` property. You can then access properties and methods on the service: @@ -77,9 +91,11 @@ export default Ember.Component.extend({ ``` -The injected property is lazy; the service will not be instantiated until the property is explicitly called. It will then persist until the application exits. +The injected property is lazy; the service will not be instantiated until the +property is explicitly called. It will then persist until the application exits. -If no argument is provided to `service()`, Ember will use the dasherized version of the property name: +If no argument is provided to `service()`, Ember will use the dasherized version +of the property name: ```app/components/cart-contents.js export default Ember.Component.extend({ diff --git a/source/routing/redirection.md b/source/routing/redirection.md index 044c8bada..4e6bb5927 100644 --- a/source/routing/redirection.md +++ b/source/routing/redirection.md @@ -26,7 +26,7 @@ export default Ember.Route.extend({ ``` If you need to examine some application state to figure out where to redirect, -you might use a [service](../../services-and-initializers/services). +you might use a [service](../../applications/services). ## Transitioning After the Model is Known diff --git a/source/services-and-initializers/container.md b/source/services-and-initializers/container.md deleted file mode 100644 index 61ff6a3b9..000000000 --- a/source/services-and-initializers/container.md +++ /dev/null @@ -1,95 +0,0 @@ -When an Ember application starts running, it will create and use a single -instance of the `Ember.Container` object. This container object is responsible -for managing factories and the dependencies between them. At the level of the -container, a factory can be any part of the framework, like _route_ or -_template_. For example, the index template is a factory with the name -`template:index`, and the application route is a factory with the name -`route:application`. The container understands how to use these factories -(Are they singleton? Should they be instantiated?) and manages their -dependencies. - -Factory names have two parts segmented by a `:`. The first segment is the -framework factory type, and the second is the name of the factory requested. -Hence, the `index` template would be named `template:index`. Ember has several -built-in factory types, and you can also create your own by simply naming your -factory appropriately. For example, to create a `user` type, you'd simply -register your factory with `application.register('user:user-to-register')`. - -If the container does not already have a requested factory, it uses a -resolver to discover that factory. For example, the resolver is responsible for -mapping the name of `component:show-posts` to the JavaScript module -located in the filesystem at `app/components/show-posts.js`. After -optionally adding dependencies to the requested factory, that factory is -cached and returned. - -Ember's container should be viewed as an implementation detail, and is not -part of the supported public API. Instead, you should use initializers to -register factories on the container: - -```app/initializers/logger.js -export function initialize(container, application) { - var Logger = Ember.Object.extend({ - log(m) { - console.log(m); - } - }); - - application.register('logger:main', Logger); -} -``` - -The `register` function adds the factory (`logger`) into the container. It adds -it with the full name of `logger:main`. - -By default, Ember will instantiate the object being injected. If you'd prefer -not to have it instantiated (such as when working with a plain JavaScript -object), you can tell Ember not to instantiate it: - -```app/initializers/logger.js -export function initialize(container, application) { - var logger = { - log(m) { - console.log(m); - } - }; - - application.register('logger:main', logger, { instantiate: false }); -} -``` - -Once a factory is registered, it can be injected: - -```app/initializers/logger.js -export function initialize(container, application) { - var Logger = Ember.Object.extend({ - log(m) { - console.log(m); - } - }); - - application.register('logger:main', logger, { instantiate: false }); - application.inject('route', 'logger', 'logger:main'); -} -``` - -This is an example of a *type injection*. Onto all factories of the type -`route`, the property, `logger` will be injected with the factory named -`logger:main`. Routes in this example application can now access the logger: - -```app/routes/index.js -export default Ember.Route.extend({ - activate() { - // The logger property is injected into all routes - this.logger.log('Entered the index route!'); - } -}); -``` - -Injections can also be made on a specific factory by using its full name: - -```js -application.inject('route:index', 'logger', 'logger:main'); -``` - -Injections can be made onto all of Ember's major framework classes including -components, routes, and the router. diff --git a/source/services-and-initializers/initializers.md b/source/services-and-initializers/initializers.md deleted file mode 100644 index 84ba84d28..000000000 --- a/source/services-and-initializers/initializers.md +++ /dev/null @@ -1,80 +0,0 @@ -Sometimes you'll want to have certain things happen as your application boots, -like starting services that should be available before the rest of your -application loads. For example, Ember Data uses this to give routes access to -the shared store. - -This can be accomplished with an initializer. For example, say you have a -service that sets up a shopping cart, and you want to inject that cart into all -the routes in your application. You could do something like this: - -```app/initializers/shopping-cart.js -export default { - name: 'shopping-cart', - initialize: function (container, application) { - application.inject('route', 'cart', 'service:shopping-cart'); - } -}; -``` - -The `shopping-cart` service will be injected as the `cart` property into every -`route` in your application, which they can access via -`this.get('shopping-cart')`. - -You can also inject a service into a specific class: - -```app/initializers/shopping-cart.js -application.inject('route:checkout', 'cart', 'service:shopping-cart'); -``` - -## Specifying Initializer Order - -If the order in which initializers load is important, you can use the `before` -and/or `after` options: - -```app/initializers/config-reader.js -export default { - name: "configReader", - before: "websocketInit", - - initialize: function(container, application) { - ... your code ... - } -}; -``` - - -```app/initializers/websocket-init.js -export default { - name: "websocketInit", - after: "configReader", - - initialize: function(container, application) { - ... your code ... - } -}; -``` - -## Pausing the Boot Process - -If you need to make sure that the rest of your application doesn't load until -an initializer has finished loading, you can use the `deferReadiness` and -`advanceReadiness` methods, which will wait until all of the promises -are resolved until continuing. - -For example, if you wanted to keep your application from booting until the -current user was set, you could do something like this: - -```app/initializers/current-user.js -export default { - name: "currentUserLoader", - after: "store", - - initialize: function(container, application) { - application.deferReadiness(); - - container.lookup('store:main').find('user', 'current').then(function(user) { - application.inject('route', 'currentUser', 'user:current'); - application.advanceReadiness(); - }); - } -};