在行动接力传承angularjs [英] angularjs with oop inheritance in action

查看:129
本文介绍了在行动接力传承angularjs的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

摘要

我正在使用的角度作为一个客户端框架的应用程序,目前角岩和我使用它真的很开心,但现在我发现我用太多的复制和粘贴code,我会喜欢组织成类层次结构。例如对话框共享一组通用的功能,他们需要被打开,关闭,code,提供了预输入功能也从一些家长继承第一候选BaseTypeaheadClass,但有一件事我没有发现角是组织这些层次的标准方式。两个控制器,服务提供商使用普通的JavaScript函数下面可以通过原型的方式进行扩展,所以我的问题是:

问题

什么是组织我类函数的角度的方式,在那里,将允许获得从另一个类的标准机制

P.S。

我对这个问题的猜测:


  • 定义执行的基类为服务,作为结果,他们将很容易地注入到任何控制器或其他服务,其中将需要的特定的类

  • 定义 OOP 服务,并提供了一​​些方法,如定义导出等将被用于创建基/派生类

感谢您,


修改

一段时间已从时候,我最初问我的问题通过。从那时起,我拿出了我成功地利用了几个项目,我很喜欢,想和大家一起分享的方式。

目前的角度不提供组织类层次结构的任何构建,这是一个遗憾,因为或多或少的大型应用程序不能足够只模型/视图/控制器/ ...结构,它具有组织是code成OOP的对象。

我在Web开发领域工作了相当长的时间已经和我没有看到被大规模利用面向对象的JavaScript连一个企业项目。我看到的是巨大的,很好地组织了服务器端/数据库端逻辑+接近于无穷大与框架和库的动物园在客户端的JavaScript润滑面条。

没有MVVM,MVP框架如knockout.js,骨干,其他的...都能够替代OOP这样的。如果您使用的不是orientired编程的核心原则,如Classses,对象,继承,抽象,多态性你是在深狗屎,你最终会是一个大型的JavaScript的长面条。

关于角,我认为这是一个框架非常不同knockout.js / Backbone.js的/任何其他MVV,任何框架,但据我的做法也实在是没有银弹能够取代OOP的。当我试图不使用OOP与角我最终大多位于控制器重复逻辑。而不幸的是没有(我还没有发现)清洁击败这个问题的角度路。

不过,我已经成功(我认为)解决了这个问题。

我用紧凑,zerro依赖性LIB只是工具约翰Resig的简单的JavaScript继承 https://github.com/tracker1/core-js/blob/master/js-extensions/040-Class。 JS )。与该库的帮助下,我能够创建/继承/创建抽象方法/覆盖它们,换句话说,做到这一点我已经习惯了在服务器端的一切。

下面是一个例子用法:

  Application.factory('SomeChildObject',['$ HTTP,SomeParentClass',函数($ HTTP,SomeParentClass){
    VAR SomeChildClass = SomeParentClass.extend({
        初始化:功能(){//构造
            this._super.init(123,231); //调用基构造
        },
        someFunction:功能(){
            //注意,您的OOP现在知道可以注入角度的服务,这一切都为pretty爽:)
            $ HTTP({方法:GET,网址:'/ someUrl'}),然后(函数(){
                this._super.someFunction(); //调用基函数实现
            });
        }
    });    //返回新SomeChildClass(); //我们在这里不是返回实例!    返回SomeChildClass; //服务是一个函数定义不是一个对象的一个​​实例
}]);//所以我们现在既可以使用角度这项服务,并有使用`extend`方法调用,扩展它像这样的能力:
Application.controller('MegaController',['$范围,SomeChildClass',函数($范围,SomeChildClass){
    $ scope.someObject =新SomeChildClass();
}]);

OOP +角同场竞技非常漂亮,下角上下文中创建能够自动利用通过服务依赖注入的,所以你不必实例注入到你的OOP构造的对象,这一事实使您的OOP层次非常纤薄和免费通过angular.js处理的需要是(现在也是)的无关紧要的东西。

所以这种方法玩,你获得的结果或问题在这里给你的反馈遇到

感谢您,

另一个修改

最近,我面临着原有Class.js实施一些问题,具体如下:

1)如果将要传递引用到您的实例方法的回调等方法,这些方法可能工作不是你所期望的工作方式。他们将失去参照这个。在这种情况下,你会期待看到您的当前对象在这个但是这将是要么顶层窗口或一些这取决于如何回调调用你的方法等上下文对象。它发生是因为JavaScript架构。为了打好这一问题提供了一个特殊的 ClassMember 函数,它指示绑定你的方法时,它的对象上下文正在创建(检查用法下面进一步的指导)。

2)显然原始的 Class.js 执行不知道角型控制器方法声明任何东西,即

  Class.extend('YourClassDisplayName',{
    构造函数:函数(){
        //一些有用的构造逻辑
    },
    控制器:['$范围,$ ATTRS',函数($范围,$ ATTRS){
        //做不便。以$范围和$ ATTRS
    }]
});

当前实现了解到上述语法

3)在使用上述方法没有适当的处理,将工艺突破角 $$注释所以引用上面的例子它将使不可能注入 $范围 $ ATTRS 进入到 ClassMember 方法,或重写的方法,该方法是使用 this.base(...)来电。因此,这也是固定的。

陷阱:

1)当使用 this.base(...)异步操作处理程序中(类似 $ http.get(...功能(){self.base(...);})),请注意, this.base(...)调用产生寿命有限,只要方法返回 this.base(...)停止存在。所以,你应该保存参考基方法明确,如果你打算调用异步方式的基础方法。即:

  ...
VAR自我=这一点;
VAR基地= this.base;
...
$ http.get(...,功能(){
    base.call(个体经营,...); //或base.apply(个体经营,...),或碱(),如果你不关心`this`
})

我已经解决了所有上述问题(除非有一个问题不能被由于JavaScript的架构解决),愿与大家一起分享,希望大家能从中获益:

  / *简单的JavaScript继承
 *由John Resig的http://ejohn.org/
 * MIT许可。
 *
 *通过BASE2和原型启发 *角改编由丹尼斯Yaremov http://github.com/lu4
 *使用方法:
 ---------------------------------   变种X = Class.extend(X,{
       构造函数:函数(){
           this.name =我X;
       },       myOrdinaryMethod:函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       },       myClassMemberMethod:ClassMember(函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       })
   });   变种Y = Class.extend('Y',{
       构造函数:函数(){
           this.name =我Y;
       },       myOrdinaryMethod:函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       },       myClassMemberMethod:ClassMember(函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       })
   });   变种x =新的X();
   变种Y =新的Y();   x.myClassMemberMethod('一个','B','C'); // [我X,一个,B,C]
   y.myClassMemberMethod('U','V','M'); // [我Y,U,V,M]   x.myOrdinaryMethod('一个','B','C'); // [我X,一个,B,C]
   y.myOrdinaryMethod('U','V','M'); // [我Y,U,V,M]   y.theirOrdinaryMethod = x.myOrdinaryMethod;
   y.theirClassMemberMethod = x.myClassMemberMethod;   y.theirOrdinaryMethod('一个','B','C'); // [我Y,一个,B,C]
   y.theirClassMemberMethod('U','V','M'); // [我X,U,V,M]* /angular.module(应用)。工厂('ClassMember',函数(){
    返回功能ClassMember(FN){
        如果(这个的instanceof ClassMember){
            this.fn = FN;
        }其他{
            返回新ClassMember(FN);
        }
    };
});angular.module(应用)。工厂(类,功能(ClassMember){
    变种运行时间= {初始化:假},
        fnTest = /xyz/.test(函数(){XYZ;})? / \\ bbase \\ B /:/.*/,
        FN_ARGS = / ^功能\\ S * [^ \\(] * \\(\\ S *([^ \\)] *)\\)/平方米,
        STRIP_COMMENTS = /((\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/))/mg;    VAR的toString = Object.prototype.toString;    //基类实现(什么都不做)
    功能类(){};    Class.members = {};    //创建一个从这个类继承的新的Class
    Class.extend =功能扩展(显示名,属性){
        VAR阵列;        变种targetMembers = {};
        VAR sourceMembers = this.members;        对(在sourceMembers VAR memberName){
            如果(sourceMembers.hasOwnProperty(memberName)){
                targetMembers [memberName] = sourceMembers [memberName]
            }
        }        VAR基地= this.prototype;        //实例化一个基类(但只能创建实例,
        //不运行构造函数的构造函数)
        runtime.initializing = TRUE;
        VAR原型=新本();
        runtime.initializing = FALSE;        //在复制的属性到新的原型
        为(在属性变量名称){
            如果(properties.hasOwnProperty(名称)){
                //检查我们是否覆盖现有功能
                VAR属性=属性[名]                //支持角度的控制器/服务/工厂声明符号
                如果(toString.call(财产)=== [对象数组]'){
                    阵列=财产;                    VAR项目=阵列[array.length - 1];                    如果(toString.call(项目)=== [对象功能]'||的instanceof ClassMember项){
                        属性=阵列[array.length - 1];
                    }其他{
                        阵=无效;
                    }
                }其他{
                    阵=无效;
                }                VAR isClassMember =财产的instanceof ClassMember;                如果(isClassMember){
                    财产= property.fn;
                }                如果(typeof运算性能===功能){
                    如果(typeof运算基础[名] ===功能与&&安培; fnTest.test(财产)){
                        财产=(函数(propertyName的,FN){
                            变参= fn.toString()取代(STRIP_COMMENTS,'').match(FN_ARGS)[1]。
                            返程(新功能('propertyName的','FN','基地','回报功能('+ ARGS +'){\\ n \\
                                    VAR prevBase = this.base; \\ n \\
                                    VAR hasBase =基地在这一点; \\ n \\
\\ n \\
                                    //添加一个新的.base()方法是相同的方法\\ n \\
                                    //但对超一流\\ n \\
\\ n \\
                                    this.base =基本[propertyName的]; \\ n \\
\\ n \\
                                    //该方法只需要暂时的约束,所以我们的\\ n \\
                                    //删除它,当我们\\'重新做执行\\ n \\
                                    VAR RET = fn.call(这个'+(!! ARGS(?','+参数):参数)+'); \\ n \\
\\ n \\
                                    如果(hasBase){\\ n \\
                                        this.base = prevBase; \\ n \\
                                    }其他{\\ n \\
                                        删除[基地]; \\ n \\
                                    } \\ n \\
                                    返回RET; \\ n \\
                                }'))(propertyName的,FN,基地);
                        })(名称,属性);
                    }                    如果(isClassMember){
                        targetMembers [名] =财产;
                    }否则如果(在targetMembers名){
                        删除targetMembers [名]
                    }                    如果(阵列){
                        数组[array.length - 1] =财产;                        财产=阵列;
                    }                    原型[名] =财产;
                }其他{
                    原型[名] =财产;
                }
            }
        }        变种membersArray = [];
        对于(VAR我在targetMembers){
            如果(targetMembers.hasOwnProperty(ⅰ)){
                membersArray.push({名称:I,FN:targetMembers [I]});
            }
        }        //所有建筑在构造函数方法实际上完成
        VAR ChildClass =(新功能(运行,成员,FN_ARGS,STRIP_COMMENTS,复位功能+(显示名||类)+(){\\ n \\
            如果(runtime.initializing&安培;!&安培; this.ctor)\\ n \\
            {\\ n \\
                VAR长度= members.length; \\ n \\
                对于(VAR I = 0; I<长度;我++)\\ n \\
                {\\ n \\
                    VAR项目=会员[I]; \\ n \\
                    这个[item.name] =(功能(我,FN){\\ n \\
                        变参= fn.toString()取代(STRIP_COMMENTS,'').match(FN_ARGS)[1]; \\ n \\
                        返回ARGS? (新功能(我,FN,回归功能('+ ARGS +'){返回fn.call(我,'+ ARGS +');}'))(我,FN):功能() {返回fn.call(箱); }; \\ n \\
                    })(这一点,item.fn); \\ n \\
\\ n \\
                } \\ n \\
                this.ctor.apply(这一点,参数); \\ n \\
            } \\ n \\
        }))(运行时,membersArray,FN_ARGS,STRIP_COMMENTS);        ChildClass.members = targetMembers;        //填充我们构建原型对象
        ChildClass.prototype =原型;        //强制构造是我们所期望的
        ChildClass.prototype.constructor = ChildClass;        //使这个类扩展
        ChildClass.extend =延长;        返回ChildClass;
    };    返回Class的;
});


另一个修改

最后,我对有关的角度与原来的约翰Resig的实施另一个问题迷迷糊糊的,问题是关系到(使用依赖注入)角的注释过程,它使用Function.prototype.toString()和一些正则表达式 ES提取依赖的姓名的目的。并与原来实行的问题是,它并不期望这一点,所以你不能够宣布接受相关性的方法,所以我调整了实施一点点地处理previously说明问题,这里是

  / *简单的JavaScript继承
 *由John Resig的http://ejohn.org/
 * MIT许可。
 *
 *通过BASE2和原型启发 *角改编由丹尼斯Yaremov http://github.com/lu4
 *使用方法:
 ---------------------------------   变种X = Class.extend(X,{
       构造函数:函数(){
           this.name =我X;
       },       myOrdinaryMethod:函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       },       myClassMemberMethod:ClassMember(函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       })
   });   变种Y = Class.extend('Y',{
       构造函数:函数(){
           this.name =我Y;
       },       myOrdinaryMethod:函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       },       myClassMemberMethod:ClassMember(函数(X,Y,Z){
           ([this.name,X,Y,Z])的console.log;
       })
   });   变种x =新的X();
   变种Y =新的Y();   x.myClassMemberMethod('一个','B','C'); // [我X,一个,B,C]
   y.myClassMemberMethod('U','V','M'); // [我Y,U,V,M]   x.myOrdinaryMethod('一个','B','C'); // [我X,一个,B,C]
   y.myOrdinaryMethod('U','V','M'); // [我Y,U,V,M]   y.theirOrdinaryMethod = x.myOrdinaryMethod;
   y.theirClassMemberMethod = x.myClassMemberMethod;   y.theirOrdinaryMethod('一个','B','C'); // [我Y,一个,B,C]
   y.theirClassMemberMethod('U','V','M'); // [我X,U,V,M]* /
angular.module('本垒打')。工厂(类,函数(){
    功能ClassMember(FN){
        如果(这个的instanceof ClassMember){
            this.fn = FN;
            返回此;
        }其他{
            返回新ClassMember(FN);
        }
    }    功能ClassEvent(){
        如果(这个的instanceof ClassEvent){
            返回此;
        }其他{
            返回新ClassEvent();
        }
    }    变种运行时间= {初始化:假},
        fnTest = /xyz/.test(function(){XYZ;})? / \\ bbase \\ B /:/.*/,
        fnArgs = / ^功能\\ S * [^ \\(] * \\(\\ S *([^ \\)] *)\\)/平方米,
        stripComments = /((\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/))/mg;    VAR的toString = Object.prototype.toString;    //基类实现(什么都不做)
    功能类(){};    Class.events = {};
    Class.members = {};    //创建一个从这个类继承的新的Class
    Class.extend =功能扩展(显示名,属性){
        VAR阵列;        变种targetEvents = {};
        VAR sourceEvents = this.events;        变种targetMembers = {};
        VAR sourceMembers = this.members;        对(在sourceEvents VAR eventName的){
            如果(sourceEvents.hasOwnProperty(eventName的)){
                targetEvents [eventName的] = sourceEvents [eventName的];
            }
        }        对(在sourceMembers VAR memberName){
            如果(sourceMembers.hasOwnProperty(memberName)){
                targetMembers [memberName] = sourceMembers [memberName]
            }
        }        VAR基地= this.prototype;        //实例化一个基类(但只能创建实例,
        //不运行构造函数的构造函数)
        runtime.initializing = TRUE;
        VAR原型=新本();
        runtime.initializing = FALSE;        //在复制的属性到新的原型
        为(在属性变量名称){
            如果(properties.hasOwnProperty(名称)){
                //检查我们是否覆盖现有功能
                VAR属性=属性[名]                //支持角度的控制器/服务/工厂声明符号
                如果(toString.call(财产)=== [对象数组]'){
                    阵列=财产;                    VAR项目=阵列[array.length - 1];                    如果(toString.call(项目)=== [对象功能]'||的instanceof ClassMember项){
                        属性=阵列[array.length - 1];
                    }其他{
                        阵=无效;
                    }
                }其他{
                    阵=无效;
                }                VAR isClassMember =财产的instanceof ClassMember;                如果(isClassMember){
                    财产= property.fn;
                }                VAR isClassEvent =财产的instanceof ClassEvent;                如果(isClassEvent){
                    财产=(函数(){
                        功能用户(FN){
                            Subscriber.listeners.push(fn.bind(本));
                        };                        Subscriber.listeners = [];
                        Subscriber.fire =功能(){
                            VAR听众= Subscriber.listeners;                            对于(VAR I = 0; I< listeners.length;我++){
                                VAR结果=听众[I]。适用(这一点,参数);                                如果(结果==未定义!)返回结果;
                            }                            返回void 0;
                        }                        返回用户;
                    })();
                }                如果(typeof运算性能===功能){
                    如果(typeof运算基础[名] ===功能与&&安培; fnTest.test(财产)){
                        财产=(函数(propertyName的,FN){
                            变参= fn.toString()取代(stripComments,'').match(fnArgs)[1]。
                            返程(新功能('propertyName的','FN','基地','回报功能('+ ARGS +'){\\ n \\
                                    VAR prevBase = this.base; \\ n \\
                                    VAR hasBase =基地在这一点; \\ n \\
\\ n \\
                                    //添加一个新的.base()方法是相同的方法\\ n \\
                                    //但对超一流\\ n \\
\\ n \\
                                    this.base =基本[propertyName的]; \\ n \\
\\ n \\
                                    //该方法只需要暂时的约束,所以我们的\\ n \\
                                    //删除它,当我们\\'重新做执行\\ n \\
                                    VAR RET = fn.call(这个'+(!! ARGS(?','+参数):参数)+'); \\ n \\
\\ n \\
                                    如果(hasBase){\\ n \\
                                        this.base = prevBase; \\ n \\
                                    }其他{\\ n \\
                                        删除[基地]; \\ n \\
                                    } \\ n \\
                                    返回RET; \\ n \\
                                }'))(propertyName的,FN,基地);
                        })(名称,属性);
                    }                    如果(isClassEvent){
                        targetEvents [名] =财产;
                    }其他{
                        删除targetEvents [名]
                    }                    如果(isClassMember){
                        targetMembers [名] =财产;
                    }否则如果(在targetMembers名){
                        删除targetMembers [名]
                    }                    如果(阵列){
                        数组[array.length - 1] =财产;                        财产=阵列;
                    }                    原型[名] =财产;
                }其他{
                    原型[名] =财产;
                }
            }
        }        变种eventsArray = [];
        对(在targetEvents VAR targetEventName){
            如果(targetEvents.hasOwnProperty(targetEventName)){
                eventsArray.push({名称:targetEventName,FN:targetEvents [targetEventName]});
            }
        }        变种membersArray = [];
        对(在targetMembers VAR targetMemberName){
            如果(targetMembers.hasOwnProperty(targetMemberName)){
                membersArray.push({名称:targetMemberName,FN:targetMembers [targetMemberName]});
            }
        }        //所有建筑在构造函数方法实际上完成
        VAR ChildClass =(新功能(运行,事件,委员,FN_ARGS,STRIP_COMMENTS,复位功能+(显示名||类)+(){\\ n \\
            如果(runtime.initializing&安培;!&安培; this.ctor)\\ n \\
            {\\ n \\
                VAR长度= members.length; \\ n \\
                VAR绑定=功能(我$$ FN $$){\\ n \\
                    变参= $$ FN $$的toString()取代(STRIP_COMMENTS,'').match(FN_ARGS)[1]; \\ n \\
                    VAR的结果= ARGS? (新功能('我','$$ FN $$','回报功能('+ ARGS +'){返回$$ FN $$申请(我的论点);}'))(我,$$ FN $$):函数(){返回$$ FN $$申请(我的论点)。 }; \\ n \\
                    返回结果; \\ n \\
                }; \\ n \\
                对于(VAR I = 0; I<长度;我++)\\ n \\
                {\\ n \\
                    VAR项目=会员[I]; \\ n \\
                    变种FN = item.fn; \\ n \\
                    变量名称= item.name; \\ n \\
                    VAR财产=本[名] =绑定(这一点,FN); \\ n \\
                    如果(fn.fire){\\ n \\
                        property.fire =绑定(这一点,fn.fire); \\ n \\
                    } \\ n \\
                    如果(fn.listeners){\\ n \\
                        property.listeners = fn.listeners; \\ n \\
                    } \\ n \\
                } \\ n \\
                \\ n \\
                VAR长度= events.length; \\ n \\
                对于(VAR I = 0; I<长度;我++)\\ n \\
                {\\ n \\
                    VAR项目=事件[I]; \\ n \\
                    变种FN = item.fn; \\ n \\
                    变量名称= item.name; \\ n \\
                    VAR财产=本[名] =绑定(这一点,FN); \\ n \\
                    如果(fn.fire){\\ n \\
                        property.fire =绑定(这一点,fn.fire); \\ n \\
                    } \\ n \\
                    如果(fn.listeners){\\ n \\
                        property.listeners = fn.listeners; \\ n \\
                    } \\ n \\
                } \\ n \\
                this.ctor.apply(这一点,参数); \\ n \\
            } \\ n \\
        }))(运行时,eventsArray,membersArray,fnArgs,stripComments);        ChildClass.members = targetMembers;        //填充我们构建原型对象
        ChildClass.prototype =原型;        //强制构造是我们所期望的
        ChildClass.prototype.constructor = ChildClass;        //使这个类扩展
        ChildClass.extend =扩展;
        ChildClass.event = ClassEvent;
        ChildClass.member = ClassMember;        返回ChildClass;
    };    Class.member = ClassMember;
    Class.event = ClassEvent;    返回Class的;
});


解决方案

您猜测的声音完美地适用。

您可以通过简单地调用连接到母体范围方法重用父控制器定义的功能:

HTML

 < D​​IV NG控制器=ParentCtrl>
    <! - 这里的东西... - >
    < D​​IV NG控制器=ChildCtrl>
        <! - 这里的东西... - >
    < / DIV>
    <! - 这里的东西... - >
< / DIV>

的JavaScript

 函数ParentCtrl($范围){
    $ scope.parentMethod =功能(){
        //方法体
    };
}功能ChildCtrl($范围){
    $ scope.childMethod =功能(){
        //功能
        $ scope.parentMethod();
        //功能
    };
}

如果您希望使用原型继承JavaScript的方法,您可以使用:

  VAR对myApp = angular.module('对myApp',[]);功能父($范围){
    $ scope.name ='超级英雄';    $ scope.clickParent =功能(){
        $ scope.name ='从基本控制器点击了;
    }
}功能Child($范围,$喷油器){    调试器;
    $ injector.invoke(家长,对此,{$范围:$范围});    $ scope.name ='超级英雄少年';    $ scope.clickChild =功能(){
        $ scope.clickParent();
    }
}
Child.prototype =的Object.create(Parent.prototype);

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

有关的服务,例如,你可以使用:

 (函数(){功能ParentService(ARG1){
   this.arg1 = ARG1;
}功能ChildService(ARG1,ARG2){
   ParentService.call(这一点,ARG1);
   this.arg2 = ARG2;
}ChildService.prototype =新ParentService();app.service('ChildService',ChildService);}());

另外,请检查<一href=\"https://groups.google.com/forum/#!searchin/angular/controller%2420inheritance/angular/qqncwxdcVgw/Kqoswky69-oJ\">this讨论和博客张贴有关继承的AngularJS 我张贴。

Abstract

I'm working on an application that uses angular as a client side framework, angular currently rocks and I'm really happy using it, though now I find that I use to much copy and paste code that I would like to organize into class hierarchy. For example dialogs share a common set of functionality, they need to be opened, closed, the code that provides typeahead functionality is also a first candidate to inherit from some parent BaseTypeaheadClass, though one thing I didn't find in angular is a standard way of organising these hierarchies. Both controllers, services, providers use ordinary javascript functions underneath which can be extended by the means of prototype, so my question is:

Question

What is the angular way of organising my class functions, are there any standard mechanisms that will allow to derive one class from another

P.S.

My guesses on the problem:

  • Define implementation of base classes as services, as a result they will be easily injected into any controller or other services where that specific class will be needed
  • Define OOP service and provide methods such as define, derive, etc. that will be used to create base / derived classes

Thank you,


Edit

Some time has passed from time when I was initially asking my question. Since then I have come out with approach that I'm successfully using in several projects, that I like very much and want to share with everyone.

Currently angular doesn't provide any constructs for organising class hierarchies and it's a pity since more or less large application can't suffice only Model/View/Controller/... constructs, it has to organise it's code into OOP objects.

I'm working in the field of web-development for quite a long time already and I haven't seen even one enterprise project that was taking advantage of OOP with JavaScript massively. What I seen was huge and nicely organised server side / database side logic + close to infinite javascript spaghetti greased with zoo of frameworks and libraries on client side.

No MVVM, MVP frameworks such as knockout.js, backbone, other... are capable of replacing the OOP as such. If you are not using core principles of orientired programming such as Classses, Objects, Inheritance, Abstraction, Polymorphism you are in deep shit, what you will end up is a mega long javascript spaghetti.

Regarding Angular I think it is a framework very much different from knockout.js / backbone.js / any other MVV-anything frameworks but according to my practice also it is not a silver bullet capable of replacing OOP. When I'm trying not to use the OOP with Angular I end up with duplicate logic located mostly in controllers. And unfortunately there is no (I have found no) clean and angular-way of beating that problem.

But I have successfully (I think) solved that problem.

I've used compact, zerro-dependency lib that just implements John Resig's Simple JavaScript Inheritance (https://github.com/tracker1/core-js/blob/master/js-extensions/040-Class.js). With the help of that library I was able to create / inherit / create abstract methods / override them, in other words do everything that I've accustomed to on server side.

Here is an example usage:

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 play together very nicely, objects created under angular context can take advantage of dependency injection via services automatically, so you don't have to inject instances into your OOP constructors and this fact makes your OOP hierarchy very slim and free of irrelevant stuff that needs to be (and is) handled by angular.js

So play with this approach and give feedback here with results you gained or problems you encountered,

Thank you,

Another edit

Recently I've faced few problems with original Class.js implementation, as follows:

1) If you will be passing a reference to your instance methods as callbacks to other methods, these methods might work not the way you expect them to work. They will loose reference to this. In such case you will be expecting to see your current object inside this but it will be either top level Window or some other context object depending on how the callback calls your method. It happens due to JavaScript architecture. In order to fight this problem a special ClassMember function is provided which instructs Class to bind your method to object context when it is being created (check Usage below for further guidance).

2) Obviously original Class.js implementation doesn't know anything about angular type of controller method declarations i.e.

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

Current implementation understands above syntax

3) When using above approach without appropriate handling it would break angular $$annotate'on process so referring to above example it would make impossible to inject $scope and $attrs into into ClassMember method, or overriden method which is using this.base(...) calls. So this is also fixed.

Gotchas:

1) When using this.base(...) within async operation handler (something like $http.get(..., function() { self.base(...); })) please note that this.base(...) call has a limited lifetime and as soon as the method returns this.base(...) stops existing. So you should save reference to base method explicitly if you are planning to call base methods in asynchronous fashion. i.e:

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

I've resolved all of the above problems (except one gotcha which can not be resolved due to JavaScript architecture) and would like to share with everyone, hope you will benefit from it:

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


Another edit

Eventually I've stumbled upon another problem related to original John Resig's implementation in relation to angular, and the problem is related to angular's annotation process (used for dependency injection) which uses Function.prototype.toString() and some Regex'es for the purpose of extracting the names of dependencies. And the problem with original implementation is that it doesn't expect this and so you are not able to declare methods that accept dependencies, so I've tweaked the implementation a little bit to deal with previously described problem and here it is:

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

解决方案

Your guesses sounds perfectly applicable.

You can reuse functionality defined in parent controllers by simply calling methods attached to the parent scope:

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

If you want to use the JavaScript approach with prototype inheritance you can use:

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/

For services, for example, you can use:

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

}());

Also check this discussion and the blog post about inheritance in AngularJS I posted.

这篇关于在行动接力传承angularjs的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆