54 votes

angularjs avec héritage oop en action

Résumé

Je travaille sur une application qui utilise angular comme framework côté client, angular est actuellement génial et je suis vraiment heureux de l'utiliser, mais maintenant je trouve que j'utilise trop de code copié-collé que je voudrais organiser en hiérarchie de classe. Par exemple, les boîtes de dialogue partagent un ensemble commun de fonctionnalités, elles doivent être ouvertes, fermées, le code qui fournit les fonctions d'ouverture, de fermeture, de fermeture et d'ouverture des boîtes de dialogue est très important. typeahead est également un premier candidat pour hériter d'un parent BaseTypeaheadClass, bien qu'une chose que je n'ai pas trouvé dans angular est une manière standard d'organiser ces hiérarchies. Les contrôleurs, les services et les fournisseurs utilisent tous les deux des fonctions javascript ordinaires, qui peuvent être étendues au moyen de prototype donc ma question est :

Question

Quelle est la manière angulaire d'organiser les fonctions de mes classes ? Existe-t-il des mécanismes standard permettant de dériver une classe d'une autre ?

P.S.

Mes suppositions sur le problème :

  • Définir l'implémentation des classes de base en tant que services, ce qui permet de les injecter facilement dans n'importe quel contrôleur ou autre service où cette classe spécifique est nécessaire.
  • Définir OOP et fournir des méthodes telles que define , derive etc. qui seront utilisées pour créer des classes de base/dérivées.

Editar

Un certain temps s'est écoulé depuis le moment où j'ai posé ma question initiale. Depuis lors, j'ai mis au point une approche que j'utilise avec succès dans plusieurs projets, que j'aime beaucoup et que je souhaite partager avec tout le monde.

Actuellement, angular ne fournit pas de constructions pour organiser les hiérarchies de classes et c'est dommage car une application plus ou moins grande ne peut se contenter des constructions Modèle/Vue/Contrôleur/..., elle doit organiser son code en objets OOP.

Je travaille dans le domaine du développement web depuis assez longtemps déjà et je n'ai pas vu un seul projet d'entreprise qui tirait massivement parti de la POO avec JavaScript. Ce que j'ai vu, c'est une logique énorme et bien organisée côté serveur / côté base de données + des spaghettis javascript presque infinis, graissés avec des tas de frameworks et de bibliothèques côté client.

Aucun MVVM, les frameworks MVP tels que knockout.js, backbone, autres... ne sont capables de remplacer la POO en tant que telle. Si vous n'utilisez pas les principes de base de la programmation orientée tels que les classes, les objets, l'héritage, l'abstraction, le polymorphisme, vous avez de gros problèmes, et ce que vous obtiendrez sera un méga long spaghetti javascript.

En ce qui concerne Angular, je pense qu'il s'agit d'un framework très différent de knockout.js / backbone.js / tout autre framework MVV-quelque chose, mais selon ma pratique, ce n'est pas non plus une solution miracle capable de remplacer la POO. Lorsque j'essaie de ne pas utiliser la POO avec Angular, je me retrouve avec une logique dupliquée située principalement dans les contrôleurs. Et malheureusement, il n'y a pas (je n'ai pas trouvé) de moyen propre et angulaire de résoudre ce problème.

Mais j'ai réussi (je pense) à résoudre ce problème.

J'ai utilisé une librairie compacte, sans dépendance, qui implémente seulement John Resig's Simple JavaScript Inheritance ( https://github.com/tracker1/core-js/blob/master/js-extensions/040-Class.js ). Avec l'aide de cette bibliothèque, j'ai pu créer / hériter / créer des méthodes abstraites / les surcharger, en d'autres termes faire tout ce que j'ai l'habitude de faire du côté serveur.

Voici un exemple d'utilisation :

Application.factory('SomeChildObject', ['$http', 'SomeParentClass', function ($http, SomeParentClass) {
    var SomeChildClass = SomeParentClass.extend({
        init: function() { // Constructor
            this._super.init(123, 231); // call base constructor
        },
        someFunction: function() {
            // Notice that your OOP now knows everything that can be injected into angular service, which is pretty cool :)
            $http({method: 'GET', url: '/someUrl'}).then(function(){
                this._super.someFunction(); // call base function implementation
            });
        }
    });

    // return new SomeChildClass(); // We are not returning instance here!

    return SomeChildClass; // Service is a function definition not an instance of an object
}]);

// So now we can both use this service in angular and have the ability to extend it using the `extend` method call, like so:
Application.controller('MegaController', ['$scope', 'SomeChildClass', function ($scope, SomeChildClass) {
    $scope.someObject = new SomeChildClass();
}]);

La POO et Angular vont très bien ensemble, les objets créés dans le contexte d'Angular peuvent profiter de l'injection de dépendance via les services automatiquement, donc vous n'avez pas besoin d'injecter des instances dans vos constructeurs POO et ce fait rend votre hiérarchie POO très mince et libre de choses non pertinentes qui doivent être (et sont) gérées par Angular.js.

Jouez donc avec cette approche et donnez votre avis ici sur les résultats que vous avez obtenus ou les problèmes que vous avez rencontrés,

Un autre montage

Récemment, j'ai rencontré quelques problèmes avec l'implémentation originale de Class.js, comme suit :

1) Si vous passez une référence à vos méthodes d'instance en tant que callbacks à d'autres méthodes, ces méthodes peuvent ne pas fonctionner comme vous le souhaitez. Elles perdront la référence à this . Dans ce cas, vous vous attendez à voir votre objet actuel à l'intérieur de l'objet. this mais ce sera soit le niveau supérieur Window ou un autre objet de contexte selon la façon dont le callback appelle votre méthode. Cela se produit en raison de l'architecture JavaScript. Afin de combattre ce problème, une méthode spéciale ClassMember est fournie, qui demande à Class pour lier votre méthode au contexte de l'objet lors de sa création (vérifier Usage ci-dessous pour plus d'informations).

2) Évidemment original Class.js ne sait rien du type angulaire de déclarations de méthodes de contrôleur, c'est-à-dire

Class.extend('YourClassDisplayName', {
    ctor: function () {
        // Some useful constructor logic
    },
    controller: ['$scope', '$attrs', function ($scope, $attrs) {
        // Do something with $scope and $attrs
    }]
});

L'implémentation actuelle comprend la syntaxe ci-dessus

3) Si l'on utilise l'approche ci-dessus sans la manipulation appropriée, il y aura une rupture de l'angulaire. $$annotate sur le processus, donc en se référant à l'exemple ci-dessus il serait impossible d'injecter $scope y $attrs en en ClassMember ou une méthode surchargée qui utilise la méthode this.base(...) appels. Donc, ceci est également corrigé.

Des problèmes :

1) Lorsque vous utilisez this.base(...) dans un gestionnaire d'opérations asynchrones (quelque chose comme $http.get(..., function() { self.base(...); }) ) veuillez noter que this.base(...) a une durée de vie limitée et dès que la méthode retourne this.base(...) cesse d'exister. Vous devez donc sauvegarder explicitement la référence à la méthode de base si vous prévoyez d'appeler les méthodes de base de manière asynchrone :

...
var self = this;
var base = this.base;
...
$http.get(..., function () {
    base.call(self, ...); // or base.apply(self, ...), or base() if you don't care about `this`
})

J'ai résolu tous les problèmes ci-dessus (à l'exception d'un seul qui ne peut être résolu en raison de l'architecture JavaScript) et je voudrais le partager avec tout le monde, en espérant que vous en tirerez profit :

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 *
 * Inspired by base2 and Prototype

 * Angular adaptations by Denis Yaremov http://github.com/lu4
 * Usage:
 ---------------------------------

   var X = Class.extend('X', {
       ctor: function () {
           this.name = "I'm X";
       },

       myOrdinaryMethod: function (x, y, z) {
           console.log([this.name, x, y, z]);
       },

       myClassMemberMethod: ClassMember(function (x, y, z) {
           console.log([this.name, x, y, z]);
       })
   });

   var Y = Class.extend('Y', {
       ctor: function () {
           this.name = "I'm Y";
       },

       myOrdinaryMethod: function (x, y, z) {
           console.log([this.name, x, y, z]);
       },

       myClassMemberMethod: ClassMember(function (x, y, z) {
           console.log([this.name, x, y, z]);
       })
   });

   var x = new X();
   var y = new Y();

   x.myClassMemberMethod('a', 'b', 'c'); // ["I'm X", "a", "b", "c"] 
   y.myClassMemberMethod('u', 'v', 'm'); // ["I'm Y", "u", "v", "m"] 

   x.myOrdinaryMethod('a', 'b', 'c'); // ["I'm X", "a", "b", "c"] 
   y.myOrdinaryMethod('u', 'v', 'm'); // ["I'm Y", "u", "v", "m"] 

   y.theirOrdinaryMethod = x.myOrdinaryMethod;
   y.theirClassMemberMethod = x.myClassMemberMethod;

   y.theirOrdinaryMethod('a', 'b', 'c'); // ["I'm Y", "a", "b", "c"] 
   y.theirClassMemberMethod('u', 'v', 'm'); // ["I'm X", "u", "v", "m"]

*/

angular.module('app').factory('ClassMember', function () {
    return function ClassMember(fn) {
        if (this instanceof ClassMember) {
            this.fn = fn;
        } else {
            return new ClassMember(fn);
        }
    };
});

angular.module('app').factory('Class', function (ClassMember) {
    var runtime = { initializing: false },
        fnTest = /xyz/.test(function() { xyz; }) ? /\bbase\b/ : /.*/,
        FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m,
        STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    var toString = Object.prototype.toString;

    // The base Class implementation (does nothing)
    function Class() { };

    Class.members = { };

    // Create a new Class that inherits from this class
    Class.extend = function extend(displayName, properties) {
        var array;

        var targetMembers = {};
        var sourceMembers = this.members;

        for (var memberName in sourceMembers) {
            if (sourceMembers.hasOwnProperty(memberName)) {
                targetMembers[memberName] = sourceMembers[memberName];
            }
        }

        var base = this.prototype;

        // Instantiate a base class (but only create the instance,
        // don't run the ctor constructor)
        runtime.initializing = true;
        var prototype = new this();
        runtime.initializing = false;

        // Copy the properties over onto the new prototype
        for (var name in properties) {
            if (properties.hasOwnProperty(name)) {
                // Check if we're overwriting an existing function
                var property = properties[name];

                // Support angular's controller/service/factory declaration notation
                if (toString.call(property) === '[object Array]') {
                    array = property;

                    var item = array[array.length - 1];

                    if (toString.call(item) === '[object Function]' || item instanceof ClassMember) {
                        property = array[array.length - 1];
                    } else {
                        array = null;
                    }
                } else {
                    array = null;
                }

                var isClassMember = property instanceof ClassMember;

                if (isClassMember) {
                    property = property.fn;
                }

                if (typeof property === "function") {
                    if (typeof base[name] === "function" && fnTest.test(property)) {
                        property = (function (propertyName, fn) {
                            var args = fn.toString().replace(STRIP_COMMENTS, '').match(FN_ARGS)[1];
                            return (new Function('propertyName', 'fn', 'base', 'return function (' + args + ') {\n\
                                    var prevBase = this.base;\n\
                                    var hasBase = "base" in this;\n\
\n\
                                    // Add a new .base() method that is the same method\n\
                                    // but on the super-class\n\
\n\
                                    this.base = base[propertyName];\n\
\n\
                                    // The method only need to be bound temporarily, so we\n\
                                    // remove it when we\'re done executing\n\
                                    var ret = fn.call(this' + (!!args ? (', ' + args) : args) + ');\n\
\n\
                                    if (hasBase) {\n\
                                        this.base = prevBase;\n\
                                    } else {\n\
                                        delete this["base"];\n\
                                    }\n\
                                    return ret;\n\
                                }'))(propertyName, fn, base);
                        })(name, property);
                    }

                    if (isClassMember) {
                        targetMembers[name] = property;
                    } else if (name in targetMembers) {
                        delete targetMembers[name];
                    }

                    if (array) {
                        array[array.length - 1] = property;

                        property = array;
                    }

                    prototype[name] = property;
                } else {
                    prototype[name] = property;
                }
            }
        }

        var membersArray = [];
        for (var i in targetMembers) {
            if (targetMembers.hasOwnProperty(i)) {
                membersArray.push({ name: i, fn: targetMembers[i] });
            }
        }

        // All construction is actually done in the ctor method
        var ChildClass = (new Function("runtime", "members", "FN_ARGS", "STRIP_COMMENTS", "return function " + (displayName || "Class") + "() {\n\
            if (!runtime.initializing && this.ctor)\n\
            {\n\
                var length = members.length;\n\
                for (var i = 0; i < length; i++)\n\
                {\n\
                    var item = members[i];\n\
                    this[item.name] = (function (me, fn) {\n\
                        var args = fn.toString().replace(STRIP_COMMENTS, '').match(FN_ARGS)[1];\n\
                        return args ? (new Function('me', 'fn', 'return function (' + args + ') { return fn.call(me, ' + args + '); }'))(me, fn) : function () { return fn.call(me); };\n\
                    })(this, item.fn);\n\
\n\
                }\n\
                this.ctor.apply(this, arguments);\n\
            }\n\
        }"))(runtime, membersArray, FN_ARGS, STRIP_COMMENTS);

        ChildClass.members = targetMembers;

        // Populate our constructed prototype object
        ChildClass.prototype = prototype;

        // Enforce the constructor to be what we expect
        ChildClass.prototype.constructor = ChildClass;

        // And make this class extendable
        ChildClass.extend = extend;

        return ChildClass;
    };

    return Class;
});

Un autre montage

Finalement, je suis tombé sur un autre problème lié à l'implémentation originale de John Resig par rapport à angular, et le problème est lié au processus d'annotation d'angular (utilisé pour l'injection de dépendances) qui utilise Function.prototype.toString() et quelques Regex dans le but d'extraire les noms des dépendances. Et le problème avec l'implémentation originale est qu'elle ne s'attend pas à cela et donc vous n'êtes pas en mesure de déclarer des méthodes qui acceptent les dépendances, donc j'ai modifié l'implémentation un peu pour traiter le problème décrit précédemment et le voici :

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 *
 * Inspired by base2 and Prototype

 * Angular adaptations by Denis Yaremov http://github.com/lu4
 * Usage:
 ---------------------------------

   var X = Class.extend('X', {
       ctor: function () {
           this.name = "I'm X";
       },

       myOrdinaryMethod: function (x, y, z) {
           console.log([this.name, x, y, z]);
       },

       myClassMemberMethod: ClassMember(function (x, y, z) {
           console.log([this.name, x, y, z]);
       })
   });

   var Y = Class.extend('Y', {
       ctor: function () {
           this.name = "I'm Y";
       },

       myOrdinaryMethod: function (x, y, z) {
           console.log([this.name, x, y, z]);
       },

       myClassMemberMethod: ClassMember(function (x, y, z) {
           console.log([this.name, x, y, z]);
       })
   });

   var x = new X();
   var y = new Y();

   x.myClassMemberMethod('a', 'b', 'c'); // ["I'm X", "a", "b", "c"] 
   y.myClassMemberMethod('u', 'v', 'm'); // ["I'm Y", "u", "v", "m"] 

   x.myOrdinaryMethod('a', 'b', 'c'); // ["I'm X", "a", "b", "c"] 
   y.myOrdinaryMethod('u', 'v', 'm'); // ["I'm Y", "u", "v", "m"] 

   y.theirOrdinaryMethod = x.myOrdinaryMethod;
   y.theirClassMemberMethod = x.myClassMemberMethod;

   y.theirOrdinaryMethod('a', 'b', 'c'); // ["I'm Y", "a", "b", "c"] 
   y.theirClassMemberMethod('u', 'v', 'm'); // ["I'm X", "u", "v", "m"]

*/

angular.module('homer').factory('Class', function () {
    function ClassMember(fn) {
        if (this instanceof ClassMember) {
            this.fn = fn;
            return this;
        } else {
            return new ClassMember(fn);
        }
    }

    function ClassEvent() {
        if (this instanceof ClassEvent) {
            return this;
        } else {
            return new ClassEvent();
        }
    }

    var runtime = { initializing: false },
        fnTest = /xyz/.test(function () { xyz; }) ? /\bbase\b/ : /.*/,
        fnArgs = /^function\s*[^\(]*\(\s*([^\)]*)\)/m,
        stripComments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    var toString = Object.prototype.toString;

    // The base Class implementation (does nothing)
    function Class() { };

    Class.events = {};
    Class.members = {};

    // Create a new Class that inherits from this class
    Class.extend = function Extend(displayName, properties) {
        var array;

        var targetEvents = {};
        var sourceEvents = this.events;

        var targetMembers = {};
        var sourceMembers = this.members;

        for (var eventName in sourceEvents) {
            if (sourceEvents.hasOwnProperty(eventName)) {
                targetEvents[eventName] = sourceEvents[eventName];
            }
        }

        for (var memberName in sourceMembers) {
            if (sourceMembers.hasOwnProperty(memberName)) {
                targetMembers[memberName] = sourceMembers[memberName];
            }
        }

        var base = this.prototype;

        // Instantiate a base class (but only create the instance,
        // don't run the ctor constructor)
        runtime.initializing = true;
        var prototype = new this();
        runtime.initializing = false;

        // Copy the properties over onto the new prototype
        for (var name in properties) {
            if (properties.hasOwnProperty(name)) {
                // Check if we're overwriting an existing function
                var property = properties[name];

                // Support angular's controller/service/factory declaration notation
                if (toString.call(property) === '[object Array]') {
                    array = property;

                    var item = array[array.length - 1];

                    if (toString.call(item) === '[object Function]' || item instanceof ClassMember) {
                        property = array[array.length - 1];
                    } else {
                        array = null;
                    }
                } else {
                    array = null;
                }

                var isClassMember = property instanceof ClassMember;

                if (isClassMember) {
                    property = property.fn;
                }

                var isClassEvent = property instanceof ClassEvent;

                if (isClassEvent) {
                    property = (function() {
                        function Subscriber(fn) {
                            Subscriber.listeners.push(fn.bind(this));
                        };

                        Subscriber.listeners = [];
                        Subscriber.fire = function() {
                            var listeners = Subscriber.listeners;

                            for (var i = 0; i < listeners.length; i++) {
                                var result = listeners[i].apply(this, arguments);

                                if (result !== undefined) return result;
                            }

                            return void 0;
                        }

                        return Subscriber;
                    })();
                }

                if (typeof property === "function") {
                    if (typeof base[name] === "function" && fnTest.test(property)) {
                        property = (function (propertyName, fn) {
                            var args = fn.toString().replace(stripComments, '').match(fnArgs)[1];
                            return (new Function('propertyName', 'fn', 'base', 'return function (' + args + ') {\n\
                                    var prevBase = this.base;\n\
                                    var hasBase = "base" in this;\n\
\n\
                                    // Add a new .base() method that is the same method\n\
                                    // but on the super-class\n\
\n\
                                    this.base = base[propertyName];\n\
\n\
                                    // The method only need to be bound temporarily, so we\n\
                                    // remove it when we\'re done executing\n\
                                    var ret = fn.call(this' + (!!args ? (', ' + args) : args) + ');\n\
\n\
                                    if (hasBase) {\n\
                                        this.base = prevBase;\n\
                                    } else {\n\
                                        delete this["base"];\n\
                                    }\n\
                                    return ret;\n\
                                }'))(propertyName, fn, base);
                        })(name, property);
                    }

                    if (isClassEvent) {
                        targetEvents[name] = property;
                    } else {
                        delete targetEvents[name];
                    }

                    if (isClassMember) {
                        targetMembers[name] = property;
                    } else if (name in targetMembers) {
                        delete targetMembers[name];
                    }

                    if (array) {
                        array[array.length - 1] = property;

                        property = array;
                    }

                    prototype[name] = property;
                } else {
                    prototype[name] = property;
                }
            }
        }

        var eventsArray = [];
        for (var targetEventName in targetEvents) {
            if (targetEvents.hasOwnProperty(targetEventName)) {
                eventsArray.push({ name: targetEventName, fn: targetEvents[targetEventName] });
            }
        }

        var membersArray = [];
        for (var targetMemberName in targetMembers) {
            if (targetMembers.hasOwnProperty(targetMemberName)) {
                membersArray.push({ name: targetMemberName, fn: targetMembers[targetMemberName] });
            }
        }

        // All construction is actually done in the ctor method
        var ChildClass = (new Function("runtime", "events", "members", "FN_ARGS", "STRIP_COMMENTS", "return function " + (displayName || "Class") + "() {\n\
            if (!runtime.initializing && this.ctor)\n\
            {\n\
                var length = members.length;\n\
                var bind = function (me, $$fn$$) {\n\
                    var args = $$fn$$.toString().replace(STRIP_COMMENTS, '').match(FN_ARGS)[1];\n\
                    var result = args ? (new Function('me', '$$fn$$', 'return function (' + args + ') { return $$fn$$.apply(me, arguments); }'))(me, $$fn$$) : function () { return $$fn$$.apply(me, arguments); };\n\
                    return result;\n\
                };\n\
                for (var i = 0; i < length; i++)\n\
                {\n\
                    var item = members[i];\n\
                    var fn = item.fn;\n\
                    var name = item.name;\n\
                    var property = this[name] = bind(this, fn);\n\
                    if (fn.fire) {\n\
                        property.fire = bind(this, fn.fire);\n\
                    }\n\
                    if (fn.listeners) {\n\
                        property.listeners = fn.listeners;\n\
                    }\n\
                }\n\
                \n\
                var length = events.length;\n\
                for (var i = 0; i < length; i++)\n\
                {\n\
                    var item = events[i];\n\
                    var fn = item.fn;\n\
                    var name = item.name;\n\
                    var property = this[name] = bind(this, fn);\n\
                    if (fn.fire) {\n\
                        property.fire = bind(this, fn.fire);\n\
                    }\n\
                    if (fn.listeners) {\n\
                        property.listeners = fn.listeners;\n\
                    }\n\
                }\n\
                this.ctor.apply(this, arguments);\n\
            }\n\
        }"))(runtime, eventsArray, membersArray, fnArgs, stripComments);

        ChildClass.members = targetMembers;

        // Populate our constructed prototype object
        ChildClass.prototype = prototype;

        // Enforce the constructor to be what we expect
        ChildClass.prototype.constructor = ChildClass;

        // And make this class extendable
        ChildClass.extend = Extend;
        ChildClass.event = ClassEvent;
        ChildClass.member = ClassMember;

        return ChildClass;
    };

    Class.member = ClassMember;
    Class.event = ClassEvent;

    return Class;
});

0 votes

Bonjour, utilisez-vous toujours l'héritage de John Resig de la manière décrite dans votre exemple ? Rencontrez-vous le problème de "l'héritage profond" ? J'ai essayé de faire la même chose, mais si mes BaseClass a un objet à l'intérieur, alors cette approche ne fonctionne pas, parce que toutes les instances de Child Classes ont une référence à la même instance de "object property". Je ne sais donc pas si c'est un problème conceptuel ou si j'utilise simplement la mauvaise version de l'implémentation de la classe de John Resig. Merci !

0 votes

Bonjour Alex, non, je n'ai pas utilisé l'approche de l'héritage profond, mais j'ai été confronté à un autre problème lié à la dépendance circulaire (voir la lecture suivante ->). goo.gl/g5OBNC + goo.gl/FUBFwF ). En conséquence, j'en suis venu à penser que le problème de dépendance circulaire est le résultat de mauvaises habitudes de codage qui peuvent me frapper lorsque je n'attends pas cela. Donc, en ce qui concerne votre problème, je pense que l'imbrication des classes est une mauvaise chose à faire puisque vous ne serez pas en mesure de les tester de manière isolée en raison de la nature prototypique de javascript. Avez-vous essayé de reconsidérer votre architecture ?

0 votes

Il y a des tonnes de cadres d'héritage plus sophistiqués sur Internet, donc vous pouvez aussi essayer de substituer l'héritage exact de John Resig par l'héritage de quelqu'un d'autre, mais encore une fois, je ne recommanderais pas d'imbriquer les classes en elles-mêmes en général, de cette façon vous violez les limites de dépendance, en faisant qu'une dépendance contienne deux classes (c'est la solution à laquelle je suis arrivé, avant de lire sur les dépendances circulaires)...

25voto

Minko Gechev Points 11295

Vos suppositions semblent parfaitement applicables.

Vous pouvez réutiliser la fonctionnalité définie dans les contrôleurs parents en appelant simplement les méthodes attachées à la portée parent :

HTML

<div ng-controller="ParentCtrl">
    <!-- Something here ... -->
    <div ng-controller="ChildCtrl">
        <!-- Something here ... -->
    </div>
    <!-- Something here ... -->
</div>

JavaScript

function ParentCtrl($scope) {
    $scope.parentMethod = function () {
        //method body
    };
}

function ChildCtrl($scope) {
    $scope.childMethod = function () {
        //functionality
        $scope.parentMethod();
        //functionality
    };
}

Si vous voulez utiliser l'approche JavaScript avec l'héritage de prototype, vous pouvez utiliser :

var myApp = angular.module('myApp',[]);

function Parent($scope) {
    $scope.name = 'Superhero';    

    $scope.clickParent = function() {
        $scope.name = 'Clicked from base controller';
    }    
}

function Child($scope, $injector) {

    debugger;
    $injector.invoke(Parent, this, {$scope: $scope});

    $scope.name = 'Superhero Child';

    $scope.clickChild = function(){
        $scope.clickParent();
    }       
}
Child.prototype = Object.create(Parent.prototype);

http://jsfiddle.net/mhevery/u6s88/12/

Pour les services, par exemple, vous pouvez utiliser :

(function () {

function ParentService(arg1) {
   this.arg1 = arg1;
}

function ChildService(arg1, arg2) {
   ParentService.call(this, arg1);
   this.arg2 = arg2;
}

ChildService.prototype = new ParentService();

app.service('ChildService', ChildService);

}());

Vérifiez également este discussion et le article de blog sur l'héritage dans AngularJS J'ai posté.

1 votes

Merci, la réponse est vraiment utile, tout comme les exemples, mais j'utiliserais probablement app.factory au lieu de app.service, ce qui permettra de définir une fonction de classe dans la portée de la fonction du service, afin de ne pas perturber la portée globale et de garder sa définition privée pour les variables globales d'un côté et absolument publique pour l'infrastructure angulaire.

0 votes

Un conseil : au lieu de call, utilisez apply ParentService.apply(this, arguments) ; de cette façon, si vous ajoutez de nouveaux arguments à la classe de base, vous ne risquez pas d'oublier de les ajouter à l'appel.

1 votes

@Lu4 vous pouvez utiliser IIFE pour préserver l'espace de nom global (je l'ai utilisé dans mon dernier exemple).

6voto

Mihaly KR Points 102

Laissez-moi vous donner mon opinion sur la situation d'Angular / héritage.

On ne fait pas d'héritage de classe/prototype dans Angular.js. Cela peut être difficile à tester, et c'est un problème. Pour ceux qui recherchent l'héritage dans Angular, je recommande ceci :

Votre classe de base est le contrôleur . Le contrôleur est un modèle abstrait de toute façon, il est donc parfait pour cela. Utilisez une fonction $scope.init() dans votre contrôleur, mais ne l'appelez pas à partir de là !

Si vous souhaitez "étendre" les fonctionnalités de votre contrôleur, utilisez les directives suivantes . Dans votre fonction directive link(), appelez la fonction $scope.init() du contrôleur. (lors de la compilation, angular exécute les contrôleurs en premier, et les fonctions de lien de directive après). Si scope avait un $scope.name='base' dans le lien de la directive, vous pourrez redéfinir les éléments suivants $scope.name=child et après cela, exécutez $scope.init().

Mais attendez ! Mais cela ne permet qu'un héritage à un seul niveau. - Oui, c'est vrai. Mais si vous cherchez un héritage à plusieurs niveaux, vous devriez utiliser Services .

L'héritage multi-niveaux n'est rien d'autre que le partage du même code dans une structure de classe hiérarchique. Pour ce faire, utilisez les services et intégrez ces services dans vos directives à l'aide de l'injecteur de dépendances. C'est très facile. Cela devrait être facile à réaliser, facile à comprendre, et les tests devraient se dérouler sans problème.

Les directives sont des outils très puissants, car vous pouvez combiner dynamiquement des partiels avec des contrôleurs.

0 votes

J'ai lu votre message environ 10 fois, et franchement, j'ai une perception négative de ce que vous essayez de dire (celle qui est proche de la colère furieuse et de la dissonance cognitive massive). Le paradigme qui se cache derrière l'héritage (je parle de la POO) est un outil qui est utilisé pour résoudre certaines tâches. Tout comme le marteau, il a été inventé il y a des siècles et aucun outil ne pourra jamais le remplacer sans être éventuellement appelé "marteau", ressembler à un marteau, sonner comme un marteau et être comme un marteau.

1 votes

L'autre aspect (plus problématique, à mon avis) de votre proposition est le fait qu'au lieu d'utiliser un marteau, vous proposez d'utiliser un arbre de Noël qui est collé dans le cul d'un poulet enroulé sur un microscope avec du ruban adhésif et un baril de superglue. goo.gl/NXWbJa . Mec, tu ne dis pas "On ne fait pas d'héritage de classe/prototype dans Angular.js" parce que tu ne fournis pas au moins un exemple minimal qui expliquerait ce que tu veux dire... (C'est une blague)

0 votes

J'ai réussi à faire ce que vous suggérez, en utilisant les contrôleurs comme définition abstraite, les modèles comme définitions des classes enfant.

3voto

MobiusTrip Points 31

Je pense que vos suppositions sont plutôt bonnes et j'ai joué avec quelques approches de ce type, mais elles se sont toutes avérées plus verbeuses que je ne l'avais espéré.

J'ai eu un problème où j'avais développé un dialogue complexe sous forme d'onglet dans notre interface d'administration, mais je voulais un dialogue presque identique dans une fenêtre contextuelle dans la section utilisateur, mais les données seraient alimentées à partir d'une source différente et il y aurait quelques boutons supplémentaires. En fait, c'est un excellent candidat pour l'héritage classique. Pour l'interface utilisateur, j'ai utilisé un modèle qui a été inclus à deux endroits avec des contrôleurs différents. Mais pour éviter de dupliquer la logique complexe de l'interface utilisateur dans les contrôleurs, je voulais utiliser l'héritage.

La méthode d'héritage de la portée repose quelque peu sur la structure de l'application et n'était pas appropriée car les deux interfaces utilisateur se trouvaient dans des applications effectivement différentes. L'approche consistant à placer le code réutilisé dans les services aurait fini par être verbeuse, car il aurait fallu que chaque méthode du contrôleur appelle une méthode équivalente dans le service. J'ai donc utilisé l'approche simple suivante de l'héritage JavaScript :

/**
 * Effective base class for Thing Controllers.
 * This should be considered abstract since it does not define
 * $scope.readData() or $scope.saveData() which may be called from its
 * other functions.
 */
function BaseThingController($scope, $http){
    $scope.data = []; // local data store;
    $scope.validateForm(){...}
    $scope.edit(){...}
    $scope.cancel(){...}
    $scope.reset(){...}
    $scope.otherMethod1(){...}
    $scope.otherMethod2(){...}
    $scope.otherMethod3(){...}
}

/**
 * AdminThingController effectively extends BaseThingController
 */
function AdminThingController($scope, $http){
    // Calling BaseThingController as a function defines all the needed 
    // functions and properties in our scope.
    BaseThingController($scope, $http)

    $scope.readData(){
       // $scope.data = data from admin data source
    }

    $scope.saveData(newData){
       // save to special admin service
    }

    // initialize local data
    $scope.readData()
}

/**
 * UserThingController effectively extends BaseThingController
 */
function UserThingController($scope, $http){
    // Calling BaseThingController as a function defines all the needed 
    // functions and properties in our scope.
    BaseThingController($scope, $http)

    $scope.readData(){
       // $scope.data = data from user data source
    }

    $scope.saveData(newData){
       // save to user service
    }

   /**
    * Overriding base class behaviour here
    */
   $scope.otherMethod1(){...}

    // initialize local data
    $scope.readData()

}

Je n'ai donc pas utilisé l'héritage de prototype car le $scope est facilement disponible. Mais j'ai obtenu tout le comportement du contrôleur de base et j'ai seulement ajouté ou surchargé ce que je voulais. Mes vues pourraient être configurées avec l'un ou l'autre contrôleur et fonctionneraient sans aucune modification.

0 votes

J'ai essayé cette approche à l'époque où j'écrivais cet article, elle a ses avantages et ses inconvénients, mais ce que j'ai fini par utiliser est le suivant John Resig's Simple JavaScript Inheritance parce que je pensais que la portée devait être exempte de tout ce qui n'est pas spécifique à la portée, c'est-à-dire que vous ne pouvez pas composer deux objets différents sur une portée. Cette approche est joliment implémentée dans core-js ( github.com/tracker1/core-js/blob/master/js-extensions/ ). En gros, j'ai utilisé les services Angular comme point de distribution pour les classes implémentées avec core-js Class.extend(...) définition

0 votes

Je comprends votre point de vue sur la nécessité de garder le champ d'application propre, et votre approche est très concise, mais si je présentais votre exemple à un développeur junior et lui demandais d'ajouter une nouvelle fonctionnalité, je pense qu'il passerait une demi-heure à essayer de comprendre le code et à chercher ce qu'est js-core. Je serais peut-être dans le même bateau si je revenais dans 5 ans pour revoir le code. Mon approche est plus primitive mais évidente pour les développeurs comme pour les réviseurs. Je suppose que c'est en grande partie une question de goût et d'environnement.

1voto

Carlos Pliego Points 67

Voici un excellent exemple d'héritage de services : http://bit.ly/1iyPpIh

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X