Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheriting AngularJS directives to create reusable components

I have been working on AngularJS for a while and have researched quite a lot. I am working on building reusable custom components/widgets using AngularJS directives. I have been quite successful at it. However, I want to adhere to inheritance while doing the same. Let me explain with an example.

I have created a directive myButton that creates a button with all the styles & functionality. Now I would like to extend/inherit this myButton to create a myToggleButton with some added features & functionality. I do not wish to rewrite myButton features again.

I have explored various options.

  1. As suggested in https://gist.github.com/BrainCrumbz/5832057, I created a factory/service and injected it into the directive. But this is not allowing me to take full benefit of the inheritance. I am still having to rewrite most of the properties.

  2. I tried using plain object-oriented JavaScript for inheritance but in that case I would not be using AngulrJS directives. I want to follow Angular concepts strictly.

So any suggestions would be most welcome.

like image 279
swathis Avatar asked Sep 19 '13 06:09

swathis


2 Answers

I have also found most inheritance examples less than ideal but I have come up with a solution I think is clean and allows for fully inheritance.

As services and directives do not have prototype information available in them and extending Object directly is not good you will want to create a high level base class which could contain constants or very simple generic logic.

var BaseService = function() {};
BaseService.prototype.toast = "french";
BaseService.prototype.halloween = "scary";

Next lets create an abstract service (same logic for a directive) that can be extended.

module.factory('AbstractDirective', function(
    $http, $q, $rootScope, $compile, $timeout) {
    $.extend(this, new BaseService);

    // Additional logic and methods should be appended onto 'this'
    this.doStuff = function() {
        alert("abstract function called");
    };

    this.halloween = 'fun';
    // If adding a variable to the prototype of this extended class is desired
    //     then this function would need to be extracted to its own variable
    //     where the prototype values can be set before the function
    //     is passed to the factory. 

    return this;
}

Now lets create an actual implementation:

module.directive('DirectiveImpl', ['AbstractDirective', function(AbstractDirective) {
    $.extend(this, AbstractDirective);
    // A great part about this implementation pattern is that
    //   DirectiveImpl does not need to pass anything to construct AbstractDirective.
    // Meaning changes to AbstractDirective will have less impacts
    //   on implementing classes.

    this.doStuff = function () {
        // Call
        AbstractDirective.doStuff();
        // Implement some logic additional
        alert(this.toast + "Toast\nHalloween is " + this.halloween );
    }

    return this;
}]);

for services use

module.factory

instead of

module.directive

When the doStuff function is call for DirectiveImpl you will get 2 alerts:

abstract function called

then

French Toast
Halloween is fun

A similar pattern can be followed to allow full inheritance for controllers as well but there is a bit more to get that to work.

like image 154
Enzey Avatar answered Nov 19 '22 21:11

Enzey


I used this implementation (based on Enzey's model) to get my directive to work as intended.

module.directive('DirectiveImpl', ['AbstractDirective', function(AbstractDirective) {
    return {
        controller: ['$scope','$element', function( $scope, $element ) {
            $.extend($scope, AbstractDirective);
            // A great part about this implementation pattern is that
            //   DirectiveImpl does not need to pass anything to construct 
            //   AbstractDirective.
            // Meaning changes to AbstractDirective will have less impacts
            //   on implementing classes.

            $scope.doStuff = function () {
                // Call
                AbstractDirective.doStuff();

                // Implement some logic additional
                alert($scope.toast + "Toast\nHalloween is " + $scope.halloween );
            } 
        },
        link: function( scope, element, opts ) {
            scope.doStuff();
        }
    }
}]);
like image 38
b.kelley Avatar answered Nov 19 '22 21:11

b.kelley