Fresh via EmberFest:

Ember has an amazing testing story built upon the fundamentals of encapsulation and OO programming central to the framework. Despite this, every web application will be dependent upon and need to interact with native and network APIs that are hostile: Unreliable, slow, under-documented, and not to be trusted. The challenge in testing Ember applications lies with how to handle these external entities.

In this talk, we distinguish the domain of an application from its implementation dependencies. The dependency is contained such that it avoids leaking into application code, unit tests, and acceptance tests.

As a addendum exercise, lets consider how to wrap animation frames in a service that can be mocked.

First, consider a component that schedules the animation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// in ember-cli, app/components/animate-in.js
App.AnimateInComponent = Ember.Component.extend({
  
  animateIn: function(){
    var element = this.$();
    var position = -100;
    element.css({
      'margin-left': ''+position+'px'
    });
    function animate(){
      position++;
      element.css('margin-left', ''+position+'px');  
      if (position !== 0) {
        window.requestAnimationFrame(animate);
      }
    }
    window.requestAnimationFrame(animate);
  }.on('didInsertElement', 'click')
  
});

Though functional, this component is leaking knowledge about a browser API (requestAnimationFrame) and is difficult to disable or isolate in tests. If in the animation a component property was modified, it might also require the addition of an explicit runloop.

Lets create a service to contain the request animation API and make it easier to mock in tests. A service in Ember.js has two parts, the service itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// in ember-cli, app/services/animation.js
App.AnimationService = Ember.Object.extend({
  init: function(){
    this.frames = [];
  },
  scheduleFrame: function(frame){
    this.frames.push(frame);
    Ember.run.scheduleOnce('afterRender', this, this.scheduleAnimationFrame);
  },
  scheduleAnimationFrame: function(){
    window.requestAnimationFrame(Ember.run.bind(this, this._animateFrame));
  },
  _animateFrame: function(){
    var framesLength = this.frames.length;
    while (framesLength--) {
      var frame = this.frames.shift();
      frame();
    }
    if (this.frames.length) {
      Ember.run.scheduleOnce('afterRender', this, this.scheduleAnimationFrame);
    }
  }
});

And an initializer. In this example, I’m using the globals style of Ember. In ember-cli this would look different (again, see the slides):

1
2
3
// in ember-cli, app/initializers/animation.js
App.register('service:animation', App.AnimationService);
App.inject('component', 'animation', 'service:animation');

And now the component itself can be refactored to use this service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// in ember-cli, app/components/animate-in.js
App.AnimateInComponent = Ember.Component.extend({
  
  animateIn: function(){
    var element = this.$();
    var animation = this.animation;
    var position = -100;
    element.css({
      'margin-left': ''+position+'px'
    });
    function animate(){
      position++;
      element.css('margin-left', ''+position+'px');  
      if (position !== 0) {
        animation.scheduleFrame(animate);
      }
    }
    animation.scheduleFrame(animate);
  }.on('didInsertElement', 'click')
  
});

A unit test for this component could now easily assert that animation begins, without needing to reference the native requestAnimationFrame itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
// in ember-cli, tests/components/animate-in-test.js
moduleForComponent('component:animate-in');

test('schedules an animation', function(){
  expect(1);
  var component = this.subject({
    scheduleFrame: function(){
      ok(true, 'animation was scheduled!');
    }
  });

  this.append();
});

Additionally, other components can now push frames to our service, conserving how many animation frames the browser needs to manage. This is a great example of how striving to move our technology dependency into a module apart from our domain code can improve the architecture of an application.

Try out the example codebase in this JSBin.

If you joined us in Barcelona, thank you for being part of a great Ember conference. Let me know if you have any thoughts or comments, and hope to see you again!

This week Vestorly, a client of my consulting partnership 201 Created, is sharing Torii.

Torii is a set of abstractions for handling authentication and session management in Ember.js applications. The name comes from a traditional Japanese gate whose design you likely recognize:

Let’s discuss how we build authentication for single page applications, and how we can build it better.

Continue reading →

Ember is fast. Ember Core is working hard to make Ember even faster. So why does your app drag?

The performance of a single-page app is impacted by the performance characteristics of its foundational parts: Network, Rendering, and JavaScript. Ember provides tools to manage these cornerstones, but with the tradeoff of introducing its own characteristics.

In this talk, we will use the source of real, shipped Ember apps (and of Ember itself) to diagnose, understand, and improve slow interactions. The Chrome developer tools will help us understand slow code paths and identify opportunities for improvement. Along the way, we will learn how parts of Ember work at the macro and micro level and learn how the framework helps us manage performance challenges in a browser environment.

At EmberConf, I spoke on web application performance and Ember.js. The message of this talk is that application performance is bigger than Ember performance. As authors of many of the more ambitious single-page web applications out there, we need to understand the tools and techniques used in analyzing and bettering the a user’s experience.

The slides above are accompanied by two live-coding sessions:

I also mentioned several tools in the slides and videos:

Additionally there are a few documentation resources I strongly encourage you to read:

Let me know if you have feedback on the presentation, and thanks to those who attended. In such a short talk it would have been difficult to comprehensively cover all the possible performance hangups in an Ember application, so instead these slides focus on giving you a new and better understanding of the tools available for gathering data, analyzing issues, and asserting the success of a change. With some practice, you will be using them every day.

Thanks to everyone at EmberConf for a fantastic week!

Correction: setProperties does not cause observers to coalesce

In my presentation at EmberConf, I stated that setting multiple properties with setProperties would cause an observer to only fire once. This is incorrect. The observer will fire for each property changed. See an illustration of the behavior in this jsbin. Thanks to @krisselden for the catch.


← All Articles