AngularJS / Typescript集成模式 - 范围方法

时间:2015-07-27 14:23:41

标签: javascript angularjs typescript

我正在尝试改变我从简单javascript编写AngularJS应用程序到使用TypeScript作为预处理器的方式。

当涉及到范围方法调用时,我正在努力调和这两种方法。

为了便于说明,我们考虑常见的菜单用例;我想强调当前显示的特定菜单项。 HTML模板如下所示:

<ul class="nav navbar-nav">
  ...
  <li ng-class="{active: isSelected('/page1')}"><a href="#/page1">Page 1</a></li>
  ...
</ul>

这预示着一个名为isSelected的范围函数。使用老式的javascript编码,我&#39;把它写成:

$scope.isSelected = function(path) {
  return $location.path().substr(0, path.length) == path;
}

这个匿名函数声明似乎并没有真正遵循更传统的TypeScript类模型。在打字稿中,我发现自己很想写这个:

export interface MenuScope extends ng.IScope {
    isSelected(path: String): boolean;
}

export class MenuController {

    location: ng.ILocationService;

    scope: MenuScope;

    constructor($scope: MenuScope, $location: ng.ILocationService) {
        this.scope = $scope;
        this.location = $location;

        this.scope.isSelected = function(path) { return this.isSelected(path) }.bind(this);
    }

    isSelected(path: String): boolean {
        return this.location.path().substr(0, path.length) == path;
    }
}

在这种情况下,isSelected属于控制器,而不是范围。这似乎是明智的。然而,&#34;链接&#34;范围和控制器之间仍然依赖于匿名方法。

更糟糕的是,我必须明确绑定this的上下文,以确保我可以在this.location的实现中编写isSelected()来访问位置服务。

我从TypeScript中寻找的一个好处是更清晰的编写代码的方式。这种通过绑定匿名函数的间接似乎与此相反。

3 个答案:

答案 0 :(得分:2)

您不应将$scope作为变量存储在this中,而是使用this作为$scope,执行此操作的方法是{ {1}}在构造函数的开头,现在该类的每个函数和变量都将成为$ scope的一部分。

您无法避免使用$scope.vm = this;,因为这是TypeScript的语法。

顺便说一句,你应该使用$ inject来获取依赖项。

答案 1 :(得分:2)

这是一个带控制器和服务的简单应用程序。我在我的项目中使用这种风格:

/// <reference path="typings/angularjs/angular.d.ts" />
module App {
    var app = angular.module("app", []);
    app.controller("MainController as vm", Controllers.MainController);
    app.service("backend", Services.Backend);
}

module App.Controllers {
    export class MainController {
        public persons: Models.Person[];

        static $inject = ["$location", "backend"];
        constructor(private $location: ng.ILocationService, private backend: Services.Backend) {
            this.getAllPersons();
        }

        public isSelected(path: string): boolean {
            return this.$location.path().substr(0, path.length) == path;
        }

        public getAllPersons() {
            this.backend.getAllPersons()
                .then((persons) => {
                    this.persons = persons;
                })
                .catch((reason) => console.log(reason));
        }
    }
}

module App.Services {
    export class Backend {
        static $inject = ["$http"];
        constructor(private $http: ng.IHttpService) { }

        public getAllPersons(): ng.IPromise<Models.Person[]> {
            return this.$http.get("api/person")
                .then((response) => response.data);
        }
    }
}

module App.Models {
    export interface Person {
        id: number;
        firstName: string;
        lastName: string;
    }
}
  1. 我有应用程序,控制器,服务和模型的模块。
  2. 控制器定义为类,但必须通过controller as语法注册到应用程序。因此,您在课堂中定义的所有内容都可以通过视图(控制器范围)中的vm访问。我们在这里personsisSelectedgetAllPersons
  3. 您可以通过static $inject string[]注入每个注射剂,然后分别将它们添加为构造函数参数。在定义服务时,此角色也可用,并且可以进行修改。
  4. 您还可以向控制器类注入$scope以访问范围特定的工具,例如$applyon等。
  5. 您可以定义服务,而不是定义工厂,以便将它们定义为类。
  6. 注入服务与注入控制器相同。
  7. 您可以将http调用的返回类型定义为ng.IPromise<Model>,然后返回response.data以确保返回方法的类型只是实体,而不是http相关数据。

答案 2 :(得分:1)

我们正在考虑类似的转换(例如从Javascript到Anglecript for Angular)。在我们开始实施时,有些事情(比如你的例子)看起来很奇怪。最简单的方法是使用controller as语法。这样,您可以直接在控制器上公开方法。

<!-- use controller as syntax -->
<div ng-controller="MenuController as menu">
<ul class="nav navbar-nav">
  ...
  <li ng-class="{active: menu.isSelected('/page1')}"><a href="#/page1">Page 1</a></li>
  ...
</ul>
</div>

这将允许您超越将范围的方法绑定回控制器上的方法的需要。我不喜欢这种方法:

  • 现在可以通过控制器直接使用每个注射剂(例如$ scope,$ location)。这可能不是什么大问题,但是当你想知道控制器能做什么以及保持适当的范围时,这似乎是不可取的。
  • 生成的类代码看起来过于混乱,并没有针对缩小进行优化...这更像是我的一个小小的烦恼,你可以轻松地使用像class这样熟悉的代码,以获得仍有空间的代码优化。请参阅Typescript Playground生成的继承代码(为您要扩展类的每个.js文件生成的扩展函数,除非您的引用是在点,可以缓存函数的原型以向其添加方法而不是每个方法的ClassName.prototype.method ...),但我离题

另一种选择是不使用类,而是坚持强类型函数:

export function MenuController($scope: MenuScope, $location: ng.ILocationService): any {
    $scope.isSelected = function(path:string): boolean {
      return $location.path().substr(0, path.length) == path;
    }
}

由于angular负责实例化控制器,因此返回类型any无关紧要。但如果你的$ scope的方法有错字,你就会被抓住。

要解决此问题,您可以使用controller as语法更进一步。下面的示例不会让您自己实际启动控制器(例如,新的MenuController($ location)会因only void function can be called with new keyword而失败),但这可以忽略不计,因为angular会为您处理实例化。

export interface IMenuController {
    isSelected(path: string): boolean;
}
export function MenuController($location: ng.ILocationService): IMenuController {
    var self:IMenuController = this;

    self.isSelected = function(path:string): boolean {
      return $location.path().substr(0, path.length) == path;
    }

    // explicitly return self / this to compile
    return self;
}

TL DR:我是编辑时检查类型的粉丝,并且喜欢使用类的概念。但是,我不认为它完全符合Angular 1.x模型。看起来这是为Angular2设计的。对Angular 1.x使用强类型函数。