Abstract/Concrete Inheritance Model in AngularJS

In a previous post I showed how to create a “classic” inheritance model in AngularJS using directives. This time I will show another inheritance model: the abstract/concrete.

In OOP, an abstract class cannot be instantiated, it serves only to provide common properties and functionality to child classes. In Angular, we have to make an analogy and define that an abstract directive is one that does not have a template or a link function, only a controller. The template is provided by child directives.

In short, we are trying to accomplish something like this:

<body ng-app="myApp">
  <outer outer-complex></outer>
  <outer outer-simple></outer>
  <!-- script tags -->
</body>

So in this case we have three directives, “outer”, “outer-complex” and “outer-simple”. Lets begin by inspecting the outer directive that in our case is the “abstract” directive:

angular.module('myApp')
  .directive('outer', function(){
    return {
      restrict: 'E',
      scope: true,
      controller: function(){
        this.sayHello = function(){
          console.log('outer: hello');
        };
      }
    };
  });

Notice that this is an element directive as can be seen in the property restrict: ‘E’ and that there is no mention to a template. The controller defines a simple method called “sayHello” that will be inherited to “outer-complex” and “outer-simple” directives.

Next, lets define those child concrete directives:

angular.module('myApp')
  .directive('outerSimple', function(){
    return {
      restrict: 'A',
      template: '<p>outer simple</p>',
      require: ['outer', 'outerSimple'],
      scope: true,
      controller: function(){
        this.sayMan = function(){
          console.log('outer-simple: man');
        }
      },
      link: function(scope, element, attrs, ctrls){
        var outerCtrl = ctrls[0];
        var outerSimpleCtrl = ctrls[1];
        
        element.on('click', function(event){
          outerCtrl.sayHello();
          outerSimpleCtrl.sayMan();
        });
      }
    }
  });

angular.module('myApp')
  .directive('outerComplex', function(){
    return {
      restrict: 'A',
      template: '<p>outer complex</p>',
      require: ['outer', 'outerComplex'],
      scope: true,
      controller: function(){
        this.sayWoman = function(){
          console.log('outer-complex: woman');
        };
      },
      link: function(scope, element, attrs, ctrls){
        var outerCtrl = ctrls[0];
        var outerComplexCtrl = ctrls[1];

        element.on('click', function(event){
          outerCtrl.sayHello();
          outerComplexCtrl.sayWoman();
        });
      }
    };
  });

It’s important to notice that, unlike the “outer” directive, these are attribute directives because of  require: ‘A’ and that when requiring the controllers in the property require: [‘outer’, ‘outerComplex’] there is no mention to the symbol “^”. Why? Because both directives are applying to the same HTML element, so there is no “parent” directive from a DOM perspective.

Finally, lets put some CSS to the mix:

outer {
  display: block;
  color: white;
}

outer[outer-complex] {
  background-color: blue;
  border: 5px solid blue;
}

outer[outer-simple] {
  background-color: red;
  border: 5px solid red;
}

The nice thing about this approach is that the CSS syntax resembles the directives inheritance as can be seen in the selectors outer[outer-complex]  and outer[outer-simple] .

The end result is that when we click on the “outer-complex” directive both child and parent methods are called.

Screenshot from 2015-02-09 22:56:29

Likewise, clicking the outer-simple directives generates a similar response from the console.

Screenshot from 2015-02-09 22:57:35

Very cool, don’t you think?

So, what do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.