Skip to content

Instantly share code, notes, and snippets.

@BrainCrumbz
Last active May 12, 2017 03:57
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save BrainCrumbz/5832057 to your computer and use it in GitHub Desktop.
Save BrainCrumbz/5832057 to your computer and use it in GitHub Desktop.
Reusing an AngularJS Directive core code. Please see 'Directives Inheritance' file for an extended description. (Text here comes out unformatted). For a working sample, please see plunkr at http://plnkr.co/edit/6kzpuSGPFfXe6eSH5bc2
angular.module('com.namespace.directives').directive('baseDir', ['$compile', 'baseDirNamespace', baseDirFactory]);
function baseDirFactory($compile, baseDirNamespace) {
return new baseDirNamespace.BaseDirCore($compile);
}
angular.module('com.namespace.directives').factory('baseDirNamespace', baseDirNamespace);
function baseDirNamespace() {
function BaseDirCore($compile) {
// Class constructor function
//...
}
$.extend(true, BaseDirCore.prototype, {
// Shared members
//...
});
return {
BaseDirCore: BaseDirCore
};
}
angular.module('com.namespace.directives').directive('derivedDir', ['$compile', 'derivedDirNamespace', derivedDirFactory]);
function derivedDirFactory($compile, derivedDirNamespace) {
return new derivedDirNamespace.DerivedDirCore($compile);
}
angular.module('com.namespace.directives').factory('derivedDirNamespace', ['baseDirNamespace', derivedDirNamespace]);
function derivedDirNamespace(baseDirNamespace) {
var BaseDirCore = baseDirNamespace.BaseDirCore; // alias
function DerivedDirCore($compile) {
// Class constructor function
//...
}
// setup inheritance chain, with one of the many helpers found on the web
DerivedDirCore.inheritsFrom(BaseDirCore);
$.extend(true, DerivedDirCore.prototype, {
// Shared members
//...
});
return {
DerivedDirCore: DerivedDirCore
};
}
Reusing an AngularJS Directive core code. The idea is to:
** 1. conceal the actual directive behaviour in a separate JS "class",
** 2. store that in an Angular service,
** 3. inject that service into an Angular directive,
** 4. just have the directive factory function return the constructed instance of the "class".
That is to separate the directive core code from the actual directive factory.
Then one actually wants to inherit a directive core "class" from another. It's not really a matter of JS inheritance, but more on how to inject one into the other by means of Angular modules, without actually instantiating none of them until needed,
Here are the steps to put inheritance in place:
** 1. store the base directive core code in an Angular service, by actually storing the class as a public property of the service,
** 2. inject the base code service into the derived code service
** 3. in derived code service, grab the base class as a public property and use that to build up the inheritance chain
(function () {
'use strict';
// This is just a classic implementation, with comments,
// of a 'helper' to obtain prototypical inherithance in JavaScript
// Get a dummy 'class' whose constructor has no side effects,
// so the class can be instantiated without issues, and that
// adds no members, so not to be 'transparent' inherithance-wise
function ParentWrapper() { }
function extend(baseClass, derivedClass) {
// Copy prototype from base class to wrapper
ParentWrapper.prototype = baseClass.prototype;
// Complete inherithance chain by setting wrapper as derived class prototype
derivedClass.prototype = new ParentWrapper();
// Remember the constructor property was set wrong, let's fix it
derivedClass.prototype.constructor = derivedClass;
// Allow invoking base (prototype) members
derivedClass.prototype._super = baseClass.prototype;
}
Function.prototype.inheritsFrom = function (parentClassOrObject) {
extend(parentClassOrObject, this);
}
})();
@Swathi-S-Sunder
Copy link

Nice explanation.
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 & functionlity.
I do not wish to rewrite "myButton" features again.
Also, I want all the component directives to inherit /extend from a base directive called "component".
I tried using your approach.
However, I am not able to achieve it.
Could you please suggest any options for creating various components by reusing AngularJS directives?

@stereokai
Copy link

Can you please explain this line?

// setup inheritance chain, with one of the many helpers found on the web
DerivedDirCore.inheritsFrom(BaseDirCore);

Also, why are you passing the $compile service to all functions?

Thank you very much!

@BrainCrumbz
Copy link
Author

Just a quick one: sorry to both of you, we did not receive any notice for those emails. We'll check our settings and we'll also try to answer here as well.

@BrainCrumbz
Copy link
Author

@stereokai: it was not you :) We just did not included the source code of that method. Please check the newly added file inheritsFrom.js

@BrainCrumbz
Copy link
Author

@stereokai: We pass the $compile service to the base directive simply because we need that down along the line. E.g. in a directive we had a DOM node generated by a jQuery plugin, that hence was unknown to Angular. We used the $compile service to let Angular process the DOM node, and then we swapped it with the original one.

@stereokai
Copy link

@BrainCrumbz thank you so much for taking the time to reply.

Regarding $compile, I understand what you're talking about, but out of curiosity, wouldn't a simple transclude do the job for you?

Thank you for including the implementation of inheritsFrom. So if I get this straight - to use your pattern I do not need to touch the directive declarations. Basically, I should define everything that is required for setting up the directive(s) inside the services - am I correct?

@BrainCrumbz
Copy link
Author

  1. regarding $compile, out of curiosity: at that time we were not so confident (yet) with all directive settings, transclude in particular. Besides that, I'm not sure that in our peculiar case it could work. There we needed $compile for a specific bit of DOM created by the plugin (a tr) within a larger DOM still created by the plugin (a table). So we could not just swap with a transclude, I think.
    Of course we might be wrong on this. Could we move this topic somewhere else, not to go O.T.? If you know more about it, it could as well be that transclude could work.
  2. I take it as your directive declaration stands for the directive definition object (the hash with restrict, priority, link, ...). While using our technique you still define a directive factory function, and the factory still have to return, at the end of the day, the definition object. That's a must for Angular. As long as your directive class contains the required attributes (restrict, priority, ...), Angular does not care if that's a simple hash Object, or if it's from a custom class with a prototype, or whatever.
    The whole definition object might be a new instance of a class coming from the namespace. Or, the factory could e.g. create an instance of some class coming from the namespace, and e,g, pass one of the instance methods as the link property of the definition object. That's up to your specific choice.
  3. The service is used to create a namespace. The namespace holds the directive definition class (what now is termed as Core in the Gist). The directive factory lists the namespace-service as a dependency. The directive factory must return the definition object. In doing that, the directive factory will take advantage of the namespace and use one of its classes somehow. If returning the definition object becomes quite trivial, then yes: most of the action takes place within the namespace-service (actually, within its factory function).

@BrainCrumbz
Copy link
Author

In order to make things a little bit more clear, we set up a plunkr at http://plnkr.co/edit/6kzpuSGPFfXe6eSH5bc2

@BrainCrumbz
Copy link
Author

@Swathi-S-Sunder please have a look at the plunkr and see if that helps

@nicolasr75
Copy link

Many thanks for the plunkr!!! I think it contains some crucial points that I could not have found myself easily.
F.e. the use of $.proxy() and how to return the directive definition object.

@nhuttrung
Copy link

Thanks @BrainCrumbz.

Just a question about this.link = $.proxy(this._postLink, this); (in the file baseDirective.js)

Why do we use the $.proxy? And why don't we use the following instead?

BaseDefinition.prototype.link = function (scope, element, attr, controller) {
...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment