Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Matthew Reinbold
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Matthew Reinbold@libel_vox )

Dependency-Injection With Sub-Classed Services In Angular 2 Beta 3

By Ben Nadel on

Yesterday, I was experimenting with the use of class delegates and class proxies in Angular 2 Beta 3. The example that I happened to be using could have also been implemented using prototypal inheritance. Which, is an interesting topic on its own, especially in the context of a dependency-injection (DI) framework. As such, I wanted to revisit the theme from yesterday, but using sub-classing instead of proxies and delegates.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

In this exploration, we're going to be generating greetings for a given name (such as "Sarah") using a given prefix (such as "Hello"). We'll have three different greeting services:

  • Greeter - this is the base greeter which uses the provided default prefix.
  • FriendlyGreeter - this is a sub-class of Greeter which also uses the provided default prefix.
  • FlirtyGreeter - this is a sub-class of FriendlyGreeter which uses a hard-coded prefix.

When thinking about instantiation, in the context of a dependency-injection framework, it's important remember that the dependency-injection framework only cares about your concrete class constructor - the lowest one on the prototype chain. The DI framework doesn't need to know and doesn't need to care about your prototypes and super constructors and what kinds of arguments they require - that is the responsibility of the concrete class. The concrete class has to require the necessary constructor arguments and then the concrete class - alone - has to transform its constructor arguments into super constructor arguments in any way that it deems appropriate.

To drive home this point, I purposefully created a subclass - FlirtyGreeter - that has a different constructor signature than its super constructors. In this way, there's nothing that the dependency-injection can even give the constructor during instantiation. As such, it's entirely up to the subclass to figure out how to best invoke the super class constructor.

Ok, let's look at some code. In this demo, I'm injecting all three instances of the greeter in the root component and then outputting a greeting from each service:

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Dependency-Injection With Sub-Classed Services In Angular 2 Beta 3
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Dependency-Injection With Sub-Classed Services In Angular 2 Beta 3
  • </h1>
  •  
  • <my-app>
  • Loading...
  • </my-app>
  •  
  • <!-- Load demo scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/3/es6-shim.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/3/Rx.umd.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/3/angular2-polyfills.min.js"></script>
  • <!-- CAUTION: Some features do not work with the minified UMD code. -->
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/3/angular2-all.umd.js"></script>
  • <!-- AlmondJS - minimal implementation of RequireJS. -->
  • <script type="text/javascript" src="../../vendor/angularjs-2-beta/3/almond.js"></script>
  • <script type="text/javascript">
  •  
  • // Defer bootstrapping until all of the components have been declared.
  • // --
  • // NOTE: Not all components have to be required here since they will be
  • // implicitly required by other components.
  • requirejs(
  • [ "AppComponent", "GREETER_PREFIX", "Greeter", "FriendlyGreeter", "FlirtyGreeter" ],
  • function run( AppComponent, GREETER_PREFIX, Greeter, FriendlyGreeter, FlirtyGreeter ) {
  •  
  • ng.platform.browser.bootstrap(
  • AppComponent,
  • [
  • ng.core.provide(
  • GREETER_PREFIX,
  • {
  • useValue: GREETER_PREFIX
  •  
  • // We could have provided any simple value here:
  • // --
  • // useValue: "Whaaaazuuuuuuuuup"
  • }
  • ),
  • Greeter,
  • FriendlyGreeter,
  • FlirtyGreeter
  • ]
  • );
  •  
  • }
  • );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I provide the root App component.
  • define(
  • "AppComponent",
  • function registerAppComponent() {
  •  
  • var Greeter = require( "Greeter" );
  • var FriendlyGreeter = require( "FriendlyGreeter" );
  • var FlirtyGreeter = require( "FlirtyGreeter" );
  •  
  • // Configure the App component definition.
  • ng.core
  • .Component({
  • selector: "my-app",
  • template:
  • `
  • <p>
  • <strong>Greeting</strong>: {{ greeting }}
  • </p>
  •  
  • <p>
  • <strong>Friendly Greeting</strong>: {{ friendlyGreeting }}
  • </p>
  •  
  • <p>
  • <strong>Flirty Greeting</strong>: {{ flirtyGreeting }}
  • </p>
  • `
  • })
  • .Class({
  • constructor: AppController
  • })
  • ;
  •  
  • // For this demo, we are going to inject an instance of each type of
  • // greeter so we can compare the various greetings. Remember that:
  • // --
  • // Greeter -> the base class.
  • // FriendlyGreeter -> extends -> Greeter.
  • // FlirtyGreeter -> extends -> FriendlyGreeter -> extends -> Greeter.
  • // --
  • AppController.parameters = [
  • new ng.core.Inject( Greeter ),
  • new ng.core.Inject( FriendlyGreeter ),
  • new ng.core.Inject( FlirtyGreeter )
  • ];
  •  
  • return( AppController );
  •  
  •  
  • // I control the App component.
  • function AppController( greeter, friendlyGreeter, flirtyGreeter ) {
  •  
  • var vm = this;
  •  
  • // Get a greeting from all three of the injected greeters.
  • vm.greeting = greeter.sayHello( "Sarah" );
  • vm.friendlyGreeting = friendlyGreeter.sayHello( "Sarah" );
  • vm.flirtyGreeting = flirtyGreeter.sayHello( "Sarah" );
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I provide the default prefix that can be used by the various greeter classes.
  • define(
  • "GREETER_PREFIX",
  • function registerGreeterPrefix() {
  •  
  • return( "Hello" );
  •  
  • }
  • );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I provide a greeter that can generate greetings.
  • define(
  • "Greeter",
  • function registerGreeter() {
  •  
  • var GREETER_PREFIX = require( "GREETER_PREFIX" );
  •  
  • Greeter.parameters = [ new ng.core.Inject( GREETER_PREFIX ) ];
  •  
  •  
  • // I provide an API for greeting people with the given prefix (ex, Hello).
  • function Greeter( prefix ) {
  •  
  • this.prefix = prefix;
  •  
  • }
  •  
  • // Define the instance methods.
  • Greeter.prototype = {
  •  
  • // I generate a greeting for the given name.
  • sayHello: function( name ) {
  •  
  • return( this.prefix + " " + name + "." );
  •  
  • }
  •  
  • };
  •  
  • return( Greeter );
  •  
  • }
  • );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I provide a friendly greeter that sub-classes the Greeter.
  • define(
  • "FriendlyGreeter",
  • function registerFriendlyGreeter() {
  •  
  • var GREETER_PREFIX = require( "GREETER_PREFIX" );
  • var Greeter = require( "Greeter" );
  •  
  • FriendlyGreeter.parameters = [ new ng.core.Inject( GREETER_PREFIX ) ];
  •  
  • // Extend the Greeter class.
  • FriendlyGreeter.prototype = Object.create( Greeter.prototype );
  •  
  •  
  • // I provide an API for greeting people with the given prefix (ex, Hello).
  • function FriendlyGreeter( prefix ) {
  •  
  • // When we are implementing a subclass, we always have to call the
  • // super-constructor in order to initialize the inherited portion of
  • // the class. In the context of dependency-injection, this is no
  • // different. And, regardless of the dependency-injection on the
  • // current instance, we still have to tell the super-constructor
  • // about the appropriate constructor arguments.
  • Greeter.call( this, prefix );
  •  
  • }
  •  
  • // Define the instance methods.
  • FriendlyGreeter.prototype = {
  •  
  • // I generate a friendly greeting for the given name.
  • sayHello: function( name ) {
  •  
  • // In this subclass, we want to build on top of the core
  • // functionality. As such, we have to invoke the super method in
  • // order to get the base greeting that we are going to augment.
  • var superResult = Greeter.prototype.sayHello.call( this, name );
  •  
  • // Return the "super" greeting PLUS our variation.
  • return( superResult + " " + "I hope you are doing well." );
  •  
  • }
  •  
  • };
  •  
  • return( FriendlyGreeter );
  •  
  • }
  • );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I provide a flirty greeter that sub-classes the FriendlyGreeter.
  • define(
  • "FlirtyGreeter",
  • function registerFlirtyGreeter() {
  •  
  • var FriendlyGreeter = require( "FriendlyGreeter" );
  •  
  • // Extend the FriendlyGreeter class.
  • FlirtyGreeter.prototype = Object.create( FriendlyGreeter.prototype );
  •  
  •  
  • // I provide an API for greeting people.
  • // --
  • // NOTE: This version of the subclass does not accept a prefix.
  • function FlirtyGreeter() {
  •  
  • // When we are implementing a subclass, we always have to call the
  • // super-constructor in order to initialize the inherited portion of
  • // the class. In this context, the subclass didn't define any
  • // constructor arguments and, therefore, the dependency-injection
  • // framework didn't provide any. However, we STILL HAVE TO INVOKE the
  • // super constructor with the correct argument list. In this case,
  • // the prefix is hard-coded.
  • // --
  • // NOTE: I'm using a different method signature here to drive home
  • // the point that the dependency-injection framework DOES NOT CARE
  • // about your inheritance tree - it only cares about the current
  • // constructor on the required class.
  • FriendlyGreeter.call( this, "What a lovely morning" );
  •  
  • }
  •  
  • // Define the instance methods.
  • FlirtyGreeter.prototype = {
  •  
  • // I generate a flirty greeting for the given name.
  • sayHello: function( name ) {
  •  
  • // In this subclass, we want to build on top of the core
  • // functionality. As such, we have to invoke the super method in
  • // order to get the base greeting that we are going to augment.
  • // --
  • // ASIDE: We are calling the super method. But, the super method
  • // (in FriendlyGreeter) is also calling its super method. So, we
  • // are going to end up "walking" up two different prototypes.
  • var superResult = FriendlyGreeter.prototype.sayHello.call( this, name );
  •  
  • // Return the "super" greeting PLUS our variation.
  • return( superResult + " " + "Gosh, you sure look nice today." );
  •  
  • }
  •  
  • };
  •  
  • return( FlirtyGreeter );
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, each greeter subclass is responsible for the way in which it interacts with its super class - this is not the responsibility of the dependency-injection framework. In fact, the FlirtyGreeter doesn't use any class meta-data at all because it doesn't require any injection. And yet, it is able to function two prototypes deep in a dependency-injection application.

Wen we run the above code, you can see the class inheritance at work:


 
 
 

 
 Sub-classing servics with dependency-injection in Angular 2. 
 
 
 

In the end, prototypal inheritance and sub-classing of services is no different in a dependency-injection framework, such as the one provided by Angular 2. The dependency-injection framework only cares about your concrete classes - it is not concerned with inheritance at all; all interplay with the super class is the sole responsibility of the subclass.




Reader Comments

@Sean,

Great question. It does take care of the cyclic dependency because we are not trying to inject the base-class into the sub-class. As such, the sub-class can just include / require the base-class and use its definition (rather than an instance of it). Meaning, we are not depending on the dependency-injector to manage the relationship between the classes - only the individual instances.

That probably didn't clear it up any better :D Let me see if I can articulate this a bit better.

In the previous post, we had something like this in the provider:

[
( A , useClass: B )
]

... and then B had a constructor like:

class B { constructor( coreClass : A ) }

... so, Angular went to create A using B, but then had to use A to create B:

--> A (needs B)
----> B (needs A)
------> A (needs B)
--------> B (needs A)
.... and so on.

In the current post, we get around this because we are not using constructor injection. Meaning, while we might have the same looking provider:

[
( A , useClass: B )
]

... B implementation looks like:

class B extends A { constructor() }

This time, since A is explicitly extended by class B, it's not up to Angular to inject A. So, when someone asks for A:

--> A (needs B)
----> B (does NOT depend on anything else)

... and everything works smoothly.

Hopefully that helped a bit more.

Reply to this Comment

Hi Ben
tx for the explanation... appreciate it...
so I tried to reproduce your work in TS using manual injection via:

private factoryService(){
if (this.service)
return;
var injector = Injector.resolveAndCreate(
[
TodosService,
TodoAction,
HTTP_PROVIDERS,
TodoStatsModel,
provide(AppStore, {useValue: this.appStore})
]);

this.service = injector.get(TodosService);
service.sayHello('Sean');
}

so I have cyclic dependency but instead of having the ng2 DI via constructor, I call my factoryService INSIDE the constructor, and sure enough I get stack overflow... which is what I would expect even though I am instantiating manually... maybe works for you since it's not TS and the injection through prototyipcal inheritance is diff... I am not sure..

Regards,

Sean
================
Checkout the Ultimate Angular 2 Boorstrap App: @ http://ng2.javascriptninja.io
Source@ https://github.com/born2net/ng2Boilerplate

Reply to this Comment

@Sean,

Yeah, I think we just have to avoid cyclic dependencies. Already, I am missing the $delegate concept from AngularJS 1.x - I think it has some really valuable use-cases.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.