Подтвердить что ты не робот

Angularjs с наследованием oop в действии

Аннотация

Я работаю над приложением, использующим angular в качестве платформы на стороне клиента, angular в настоящее время работает, и я действительно рад его использовать, хотя теперь я нахожу, что я использую для копирования и вставки кода, который я хотел бы организовать иерархию классов. Например, диалоги имеют общий набор функциональных возможностей, их нужно открыть, закрыть, код, который обеспечивает функциональность typeahead, также является первым кандидатом, наследуемым от некоторого родительского BaseTypeaheadClass, хотя одна вещь, которую я не нашел в angular является стандартным способом организации этих иерархий. Оба контроллера, службы, провайдеры используют обычные функции javascript, которые могут быть расширены с помощью prototype, поэтому мой вопрос:

Вопрос

Что такое angular способ организации моих функций класса, существуют ли какие-либо стандартные механизмы, которые позволят вывести один класс из другого

P.S.

Мои догадки о проблеме:

  • Определите реализацию базовых классов в качестве сервисов, в результате они будут легко внедряться в любой контроллер или другие службы, в которых потребуется этот конкретный класс.
  • Определите службу OOP и предоставите такие методы, как define, derive и т.д., которые будут использоваться для создания базовых/производных классов

Изменить

Некоторое время прошло со времени, когда я изначально задавал свой вопрос. С тех пор я пришел с подходом, который я успешно использую в нескольких проектах, которые мне очень нравятся и хочу поделиться со всеми.

В настоящее время angular не предоставляет никаких конструкций для организации иерархии классов, и жаль, поскольку более или менее большое приложение не может быть достаточным только для конструкций Model/View/Controller/..., оно должно организовать его код в ООП.

Я работаю в области веб-разработки уже довольно давно, и я не видел даже одного корпоративного проекта, который широко использовал ООП с JavaScript. То, что я видел, было огромной и красиво организованной логикой на стороне сервера/базы данных + близко к бесконечным спамбитам javascript, смазанным зоопарком фреймворков и библиотек на стороне клиента.

Никакие MVVM, MVP-структуры, такие как knockout.js, магистраль, другие... не способны заменить ООП как таковой. Если вы не используете основные принципы ориентированного программирования, такие как Classes, Objects, Inheritance, Abstraction, Polymorphism, вы испытываете глубокие проблемы, то, что вы закончите, это мега-спам файлы javascript.

Что касается angular, я думаю, что это каркас, сильно отличающийся от knockout.js/backbone.js/любых других каркасов MVV-any, но, согласно моей практике, это не серебряная пуля, способная заменить ООП. Когда я пытаюсь не использовать ООП с angular, я получаю дублирующую логику, расположенную главным образом в контроллерах. И, к сожалению, нет (я не нашел нет) чистых и angular -в пути избиения этой проблемы.

Но я успешно (я думаю) решил эту проблему.

Я использовал компактную, нулевую зависимость lib, которая просто реализует John Resig Simple JavaScript Inheritance (https://github.com/tracker1/core-js/blob/master/js-extensions/040-Class.js). С помощью этой библиотеки я смог создавать/наследовать/создавать абстрактные методы/переопределять их, другими словами делать все, что я привык на стороне сервера.

Вот пример использования:

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();
}]);

OOP + angular отлично сочетаются, объекты, созданные в контексте angular, могут автоматически использовать инъекцию зависимостей через службы, поэтому вам не нужно вводить экземпляры в свои конструкторы ООП, и этот факт делает вашу иерархию ООП очень тонкий и свободный от нерелевантного материала, который должен (и) обрабатываться angular.js

Итак, играйте с этим подходом и дайте обратную связь здесь с результатами, которые вы получили, или проблемами, с которыми вы столкнулись,

Другое редактирование

Недавно я столкнулся с небольшими проблемами с оригинальной реализацией Class.js следующим образом:

1) Если вы передадите ссылку на ваши методы экземпляра как обратные вызовы другим методам, эти методы могут работать не так, как вы ожидаете, что они будут работать. Они потеряют ссылку на this. В таком случае вы ожидаете увидеть свой текущий объект внутри this, но он будет либо верхним уровнем Window, либо каким-либо другим объектом контекста в зависимости от того, как обратный вызов вызывает ваш метод. Это происходит из-за архитектуры JavaScript. Для борьбы с этой проблемой предоставляется специальная функция ClassMember, которая инструктирует Class связывать ваш метод с контекстом объекта при его создании (см. Usage ниже для дальнейших указаний).

2) Очевидно, что оригинальная реализация Class.js ничего не знает о angular типах объявлений методов контроллера, т.е.

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

Текущая реализация понимает выше синтаксис

3) При использовании вышеописанного подхода без соответствующей обработки он сломал бы angular $$annotate 'на процессе, поэтому, ссылаясь на приведенный выше пример, было бы невозможно ввести $scope и $attrs в метод ClassMember или переопределенный метод, который использует вызовы this.base(...). Так что это также исправлено.

Gotchas:

1) При использовании this.base(...) в обработчике операции async (например, $http.get(..., function() { self.base(...); })) обратите внимание, что вызов this.base(...) имеет ограниченное время жизни, и как только метод возвращает this.base(...), останавливается. Поэтому вы должны явно сохранить ссылку на базовый метод, если вы планируете называть базовые методы асинхронным способом. то есть:

...
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`
})

Я разрешил все вышеперечисленные проблемы (за исключением одной ошибки, которая не может быть решена из-за архитектуры JavaScript) и хотела бы поделиться со всеми, надеюсь, что вы выиграете от нее:

/* 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 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;
});

Другое редактирование

В конце концов я наткнулся на другую проблему, связанную с оригинальной реализацией John Resig по отношению к angular, и проблема связана с процессом аннотации angular (используется для инъекции зависимостей), который использует Function.prototype.toString() и некоторые Regex'es с целью извлечения имен зависимостей. И проблема с оригинальной реализацией заключается в том, что она этого не ожидает, и поэтому вы не можете объявлять методы, которые принимают зависимости, поэтому я немного изменил реализацию, чтобы справиться с ранее описанной проблемой, и вот она:

/* 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 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;
});
4b9b3361

Ответ 1

Ваши догадки звучат отлично.

Вы можете повторно использовать функциональность, определенную в родительских контроллерах, просто вызывая методы, связанные с родительской областью:

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
    };
}

Если вы хотите использовать подход JavaScript с наследованием прототипов, вы можете использовать:

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/

Для служб, например, вы можете использовать:

(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);

}());

Также проверьте это обсуждение и сообщение в блоге о наследовании в AngularJS Я опубликовал.

Ответ 2

Позвольте мне высказать свое мнение о ситуации Angular/наследования.

Вы не выполняете класс/прототипное наследование в Angular.js. Это может быть трудно проверить, и это проблема. Для тех, кто ищет "наследование" в Angular, я рекомендую это:

Ваш базовый класс - это контроллер. Контроллер является абстрактной моделью в любом случае, поэтому он идеально подходит для этой цели. Используйте функцию $scope.init() в вашем контроллере, но не вызывайте ее оттуда!

Если вы хотите "расширить" функциональность вашего контроллера, используйте директивы. В функции directive link() вызовите контроллер $scope.init(). (при компиляции Angular запускает контроллеры сперва, а функции директивной ссылки - после). Если область имеет $scope.name='base', в директивной ссылке вы сможете переопределить $scope.name=child, а затем запустите $scope.init().

Но подождите! Но это позволяет одноуровневое наследование. - Да, это правда. Но если вы ищете многоуровневое наследование, вы должны использовать Службы.

Многоуровневое наследование - это не что иное, но совместное использование одного и того же кода в иерархической структуре класса. Для этого используйте Службы и запустите эти службы с помощью инжектора зависимостей в ваши директивы. Су легко. Это должно быть легко осуществимо, легко понять, и тесты проходят гладко.

Директивы - очень мощные инструменты, потому что вы можете динамически комбинировать части с контроллерами.

Ответ 3

Я думаю, что ваши догадки довольно хорошие, и я играл с такими подходами, но все они оказались более подробными, чем я надеялся.

У меня возникла проблема, когда я разработал сложный диалог в качестве вкладки в нашем интерфейсе администратора, но мне захотелось получить почти идентичный диалог во всплывающем окне в разделе пользователя, но данные будут заполнены из другого источника, и быть несколькими дополнительными кнопками. В основном отличный кандидат на классическое наследование. Для стороны пользовательского интерфейса я использовал шаблон, который был включен в два места с разными контроллерами. Но чтобы избежать дублирования сложной логики пользовательского интерфейса в контроллерах, я хотел использовать наследование.

Метод наследования области действия в некоторой степени опирается на структуру приложения и не подходит, потому что два пользовательских интерфейса были в фактически разных приложениях. Подход к повторному использованию кода в сервисах оказался бы подробным, поскольку мне бы потребовалось, чтобы каждый метод контроллера вызывал эквивалентный метод в службе. Поэтому я использовал следующий простой подход к наследованию 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()

}

Итак, я не использовал наследование прототипов, поскольку область $легко доступна. Но я получил все поведение от базового контроллера и только добавил или переопределил то, что хочу. Мои представления могут быть настроены с любым контроллером и будут работать без каких-либо изменений.