Component Life-Cycle Methods Need To Be Defined On The Prototype In AngularJS 2 Beta 1

With AngularJS 1.x, we had constructors, the $watch() method, and the $destroy event as a way to try and hook into various points in a Controller's life-cycle. With AngularJS 2 Beta 1, we now have official, granular life-cycle events that we can bind to by way of the controller's public methods. These include, but are not limited to, ngOnInit (think constructor-ish), ngOnDestroy (think $destroy event), and ngOnChanges (think $watch handler). As I've been experimenting with AngularJS 2, however, one thing that tripped me up is the fact that these event handlers have to be defined on the controller's prototype otherwise Angular won't invoke them at the appropriate times (meaning, not at all).

Now, when I say that these event-handler methods need to be defined on the Controller's prototype, I don't necessarily mean that they have to be "implemented" on the prototype; they just have to exist there. While I still don't really know how to explore the AngularJS 2 source code yet, it appears that Angular is inspecting the prototype of the class definition when trying to determine if it should invoke the Controller's life-cycle methods. However, when it goes to invoke said methods, it does so on the Controller instance (which, of course, inherits from its own prototype).

To demonstrate this, I've put together a small demo in which three different child components are toggled into and out of existence (ie, not just shown and hidden). Each of the three child components uses a different event-handler binding strategy:

Defined and implemented on the constructor prototype.

Defined and implemented on the instance.

Defined on the constructor prototype but implemented on the instance.

In the following code, I am explicitly defining the constructor prototype instead of using the .Class() method so as to make things a bit more explicit:

<!doctype html>

<html>

<head>

<meta charset="utf-8" />

<title>

Component Life-Cycle Methods Need To Be Defined On The Prototype In AngularJS 2 Beta 1

</title>

<link rel="stylesheet" type="text/css" href="./demo.css"></link>

</head>

<body>

<h1>

Component Life-Cycle Methods Need To Be Defined On The Prototype In AngularJS 2 Beta 1

// I provide a component in which the life-cycle methods are defined on the

// prototype (as an interface) and then on the instance (as an implementation).

define(

"LCBoth",

function() {

// Configure the life-cycle component definition.

var LCBothComponent = ng.core

.Component({

selector: "life-cycle-both",

template:

`

<p>

Life-Cycle methods defined on both <strong>prototype and instance</strong>.

</p>

`

})

.Class({

constructor: LCBothController,

// It doesn't matter what these references are, as long as they

// are Functions. The actual implementation is handled by the

// instance at runtime; but, the instance methods won't be called

// unless there is an existing reference on the prototype.

// --

// NOTE: "noop" stand for "no-op" or "No operation."

ngOnInit: function noop() {},

ngOnDestroy: function noop() {}

})

;

return( LCBothComponent );

// I control the life-cycle demo component.

function LCBothController() {

var bindType = "Defined on both prototype and instance.";

// Define life-cycle method on instance.

this.ngOnInit = function() {

console.log( "ngOnInit:", bindType );

};

// Define life-cycle method on instance.

this.ngOnDestroy = function() {

console.log( "ngOnDestroy:", bindType );

};

}

}

);

</script>

</body>

</html>

As you can see, each life-cycle component implements the ngOnInit and ngOnDestroy event handlers. However, when we run the following code, you will see that not all component bindings act as expected:

As you can see, we have three components, but only two sets of working event bindings. Both components that had something defined on the prototype worked, even when the actual implementation was on the controller instance. The only one that failed to execute was the component in which the event handlers were both defined and implemented on the Controller instance.

If it's unclear why this is happening, take a look at the difference between prototypal inspection vs. method invocation:

At invocation time, all of the event-handlers are bound to the instance, even if they were fully defined and implemented on the prototype. But, if they were only defined on the instance, Angular never bothers invoking the event handlers.

I am sure that there are many people who wonder why I would ever want to try to write AngularJS 2 using ES5. Well, this is exactly why; it's already teaching me stuff about how AngularJS 2 is wired together - finer points that I may have never understood had I just used ES6 classes from the start. I truly believe that building out this mental model will help me understand and debug AngularJS 2 code in the future.

We are doing the same thing, I think. I'm just being more explicit about where the prototype is for the sake of explanation. When you pass additional methods to the .Class() function, you are defining them on the prototype. So:

.Class({ foo: function(){ ... } })

... is really the same thing as:

YourClass.prototype = { foo: function() { ... } };

... at least I *think* that is what is going on. I'm still not good at finding my way around the Angular 2 source code yet. And, even with the NG1 source code, the compile code was still somewhat of a mystery :D

I would agree if only the .Class() example worked. But, both the .Class() example and the LCProto component example work. As such, I think it's just that it examines the prototype, regardless of whether or not I am using .Class() to define the methods.

Here is explanation why it checks for life cycle methods on prototype.

Whole( I mean a lot of ) angular 2 relies on decorators. In class decorators you don't get access to instance types, only yourClassConstructorFunction.prototype are available, so that's the main reasoning behind this.

Thank you good sir. I'm still having a lot of trouble finding my way around the source code. In AngularJS 1.x, it was really easy - it was all just right there, really easy to read. I've been trying to use the *bundle* files as a way to quickly look things up; but, they aren't really meant to be read - just concatenated :D

I am sure that I will eventually move to more ES6 and the use of prototypes. But, for right now, I am finding the ES5 approach as a really good way to peel back the curtain and see what they heck is going on.

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.