Ember applications are built around an MVC model that prescribes Models, Views, Controllers, and Routes for managing persistence, DOM, application state, and URLs. In an ambitious enough app, that model may fail to cover the whole problem space. Fear not, for in this presentation you will learn how to use Ember’s container and dependency features to move beyond MVC, as well as learning how they tie Ember internals together.
Video: http://www.youtube.com/watch?v=iCZUKFNXA0k&feature=youtu.be&t=1h45m59s
12. What makes a
router different from a template?
• maps routes to states
• only one router
• instance of a class
• App.Router
• Has access to store (ED)
13. What makes a
router different from a template?
• maps routes to states
• only one router
• instance of a class
• App.Router
• Has access to store (ED)
• presents html
• many templates
• just functions
• on Ember.templates
22. What makes things similar?
• Shared Interface (superclass?)
App.IndexView = Ember.View.extend({ // ...
• SHARED DEPENDENCIES
this.get('router'); // In a route
23. What makes things similar?
• Shared Interface (superclass?)
App.IndexView = Ember.View.extend({ // ...
• SHARED DEPENDENCIES
this.get('router'); // In a route
• SHAREd USAGE
28. What makes things similar?
• Shared Interface (superclass?)
App.IndexView = Ember.View.extend({ // ...
• SHARED DEPENDENCIES
this.get('router'); // In a route
• SHAREd USAGE
Instantiate all the controllers, but return the same one each time
29. What makes things similar?
• Shared Interface (superclass?)
App.IndexView = Ember.View.extend({ // ...
• SHARED DEPENDENCIES
this.get('router'); // In a route
• SHAREd USAGE
Instantiate all the controllers, but return the same one each time
• SHARED DISCOVERY
30. What makes things similar?
• Shared Interface (superclass?)
App.IndexView = Ember.View.extend({ // ...
• SHARED DEPENDENCIES
this.get('router'); // In a route
• SHAREd USAGE
Instantiate all the controllers, but return the same one each time
• SHARED DISCOVERY
App.ColorPickerComponent
Ember.TEMPLATES['index']
App.Car
31. What makes things similar?
• Shared Interface (superclass?)
App.IndexView = Ember.View.extend({ // ...
• SHARED DEPENDENCIES
this.get('router'); // In a route
• SHAREd USAGE
Instantiate all the controllers, but return the same one each time
• SHARED DISCOVERY
App.ColorPickerComponent
Ember.TEMPLATES['index']
App.Car
32. What makes things similar?
• Shared Interface (superclass?)
App.IndexView = Ember.View.extend({ // ...
• SHARED DEPENDENCIES
this.get('router'); // In a route
• SHAREd USAGE
Instantiate all the controllers, but return the same one each time
• SHARED DISCOVERY
App.ColorPickerComponent
Ember.TEMPLATES['index']
App.Car
38. register a factory into a container
1
2
3
4
5
6
7
8
9
10
11
12
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: /* take a job and run it, add workers if needed */
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.lookup('workerPool:main'); // -> Instance of WorkerPool
container.lookup('workerPool:main'); // -> Same instance of WorkerPool
39. register a factory into a container
1
2
3
4
5
6
7
8
9
10
11
12
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: /* take a job and run it, add workers if needed */
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.lookup('workerPool:main'); // -> Instance of WorkerPool
container.lookup('workerPool:main'); // -> Same instance of WorkerPool
singleton (always the same instance)
40. register like factories into a container
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: /* take a job and run it, add workers if needed */
});
var BigPool = WorkerPool.extend({ limit: 10 });
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('workerPool:big', BigPool);
container.lookup('workerPool:main'); // -> Instance of WorkerPool
container.lookup('workerPool:big'); // -> Instance of BigPool
41. register like factories into a container
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: /* take a job and run it, add workers if needed */
});
var BigPool = WorkerPool.extend({ limit: 10 });
var container = new Ember.Container();
container.register('workerPool:main', BigPool);
container.register('workerPool:big', BigPool);
container.lookup('workerPool:main'); // -> Instance of BigPool
container.lookup('workerPool:big'); // -> Different instance of BigPool
42. register non-singleton factories into a container
1
2
3
4
5
6
7
8
9
10
11
var Worker = Ember.Object.extend({
run: function(){ /* run a job */ }
});
var container = new Ember.Container();
container.register('worker:sync', Worker, {singleton: false});
container.lookup('worker:sync'); // -> Instance of Worker
container.lookup('worker:sync'); // -> New instance of Worker
container.lookup('worker:sync'); // -> New instance of Worker!
43. register non-class factories into a container
1
2
3
4
5
6
7
8
9
10
11
var container = new Ember.Container();
container.register('logger:alert', window.alert, {instantiate: false});
container.register('logger:console', function(msg){
window.console.log(msg);
}, {instantiate: false});
container.lookup('logger:alert'); // -> alert function
container.lookup('logger:console'); // -> console.log function
container.lookup('logger:console')('Howdy!'); // -> "Howdy!"
45. a wild dependency appears
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
46. a wild dependency appears
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
Need a logger
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
47. Inject a dependency on a factory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('logger:alert', window.alert, {instantiate: false});
container.injection('workerPool:main', 'logger', 'logger:alert');
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
48. Inject a dependency on a factory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('logger:alert', window.alert, {instantiate: false});
container.injection('workerPool:main', 'logger', 'logger:alert');
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
49. Inject a dependency on a factory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('logger:alert', window.alert, {instantiate: false});
container.injection('workerPool:main', 'logger', 'logger:alert');
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
control over the dependency
50. Inject a dependency on a type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
var BigPool = WorkerPool.extend({ limit: 10 });
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('workerPool:big', BigPool);
container.register('logger:alert', window.alert, {instantiate: false});
container.injection('workerPool', 'logger', 'logger:alert');
var pool = container.lookup('workerPool:big');
pool.submitJob(job);
51. Inject a dependency on a type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
var BigPool = WorkerPool.extend({ limit: 10 });
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('workerPool:big', BigPool);
container.register('logger:alert', window.alert, {instantiate: false});
container.injection('workerPool', 'logger', 'logger:alert');
var pool = container.lookup('workerPool:big');
pool.submitJob(job);
52. Inject a dependency on a type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
var BigPool = WorkerPool.extend({ limit: 10 });
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('workerPool:big', BigPool);
container.register('logger:alert', window.alert, {instantiate: false});
container.injection('workerPool', 'logger', 'logger:alert');
var pool = container.lookup('workerPool:big');
pool.submitJob(job);
control over the dependency
53. a wild dependency appears
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
limit: 3,
submitJob: function(job){
this.get('logger')('Began job.')
/* take a job and run it, add workers if needed */
}
});
Need worker
var BigPool = WorkerPool.extend({ limit: 10 });
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('workerPool:big', BigPool);
container.register('logger:alert', window.alert, {instantiate: false});
container.injection('workerPool', 'logger', 'logger:alert');
var pool = container.lookup('workerPool:big');
pool.submitJob(job);
54. Lookup a dependency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var Worker = Ember.Object.extend({
run: function(){ /* run a job */ }
});
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
noWorkerAvailable: /* check for a free worker */,
limit: 3,
submitJob: function(job){
if (this.get('noWorkerAvailable')) {
var worker = this.container.lookup('worker:sync');
worker.run(job);
this.get('workers').pushObject(worker);
} // else find a free worker in the pool and run
}
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('worker:sync', Worker, {singleton: false});
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
55. Lookup a dependency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var Worker = Ember.Object.extend({
run: function(){ /* run a job */ }
});
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
noWorkerAvailable: /* check for a free worker */,
limit: 3,
submitJob: function(job){
if (this.get('noWorkerAvailable')) {
var worker = this.container.lookup('worker:sync');
worker.run(job);
this.get('workers').pushObject(worker);
} // else find a free worker in the pool and run
}
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('worker:sync', Worker, {singleton: false});
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
56. Lookup a dependency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var Worker = Ember.Object.extend({
run: function(){ /* run a job */ }
});
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
noWorkerAvailable: /* check for a free worker */,
limit: 3,
submitJob: function(job){
if (this.get('noWorkerAvailable')) {
var worker = this.container.lookup('worker:sync');
worker.run(job);
this.get('workers').pushObject(worker);
} // else find a free worker in the pool and run
}
});
control over the dependency
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('worker:sync', Worker, {singleton: false});
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
57. dynamically Lookup a dependency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var WorkerPool = Ember.Object.extend({
workers: /* an array of workers */,
noWorkerAvailable: /* check for a free worker */,
limit: 3,
submitJob: function(job){
if (this.get('noWorkerAvailable')) {
var worker = this.container.lookup(
'worker:'+(job.get('isAsync') ? 'async' : 'sync')
);
worker.run(job);
this.get('workers').pushObject(worker);
} // else find a free worker in the pool and run
}
});
var container = new Ember.Container();
container.register('workerPool:main', WorkerPool);
container.register('worker:sync', Worker, {singleton: false});
container.register('worker:async', AsyncWorker, {singleton: false});
var pool = container.lookup('workerPool:main');
pool.submitJob(job);
64. Several ways to access it
• Everything from a container has this.container
1 App.IndexView = Ember.View.extend({
2
router: function(){
3
return this.container.lookup('router:main');
4
}.property()
5 });
65. Several ways to access it
• Everything from a container has this.container
router: function(){ return this.container.lookup('router:main'); }.property()!
• App.register, App.Inject
App.register('analytics:google', App.GoogleAnalytics);!
App.inject('controller', 'analytics', 'analytics:google');!
66. Several ways to access it
• Everything from a container has this.container
router: function(){ return this.container.lookup('router:main'); }.property()!
• App.register, App.Inject
App.register('analytics:google', App.GoogleAnalytics);!
App.inject('controller', 'analytics', 'analytics:google');!
• In Initializers
App.initializer({
name: 'analytics',
/* before: 'optionallyBeforeThis' */
initialize: function(container, application){
application.register('analytics:google', App.GoogleAnalytics);
container.injection('controller', 'analytics', 'analytics:google');
}
})
67. Several ways to access it
• Everything from a container has this.container
router: function(){ return this.container.lookup('router:main'); }.property()!
• App.register, App.Inject
App.register('analytics:google', App.GoogleAnalytics);!
App.inject('controller', 'analytics', 'analytics:google');!
• In Initializers
App.initializer({ initialize: function(container, application){ // ...
• needs
App.AnalyticsController = Ember.Controller.extend();
App.IndexController = Ember.Controller.extend({
needs: ['analytics'],
analytics: Ember.computed.alias('controllers.analytics')
});
!
68. Several ways to access it
• Everything from a container has this.container
router: function(){ return this.container.lookup('router:main'); }.property()!
• App.register, App.Inject
App.register('analytics:google', App.GoogleAnalytics);!
App.inject('controller', 'analytics', 'analytics:google');!
• In Initializers
App.initializer({ initialize: function(container, application){ // ...
• needs
App.AnalyticsController = Ember.Controller.extend();
App.IndexController = Ember.Controller.extend({
needs: ['analytics'],
analytics: Ember.computed.alias('controllers.analytics')
});
!
69. …and one unsafe way.
// Never, ever use this in application code
App.__container__