【问题标题】:AngularJS/Typescript Integration Pattern - Scope MethodsAngularJS/Typescript 集成模式 - 范围方法
【发布时间】:2015-07-28 00:27:08
【问题描述】:

我正在尝试将编写 AngularJS 应用程序的方式从纯 JavaScript 更改为使用 TypeScript 作为预处理器。

当涉及到作用域方法调用时,我正在努力协调这两种方法。

出于说明目的,让我们考虑常见的菜单用例;我希望突出显示当前显示的特定菜单项。 HTML 模板如下所示:

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

这需要一个名为isSelected 的作用域函数。使用老式 javascript 编码,我将其写为:

$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 属于控制器,而不是作用域。这似乎是明智的。但是,作用域和控制器之间的“链接”仍然依赖于匿名方法。

更糟糕的是,我不得不显式绑定this 的上下文,以确保我可以编写this.location 来访问isSelected() 实现中的定位服务。

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

【问题讨论】:

  • 使用“Controller as vm”sintax 并且可以工作,那么你应该调用 vm.isSelected
  • 参见 Angular 风格指南的 Y30(Angular 团队的 recommend)并尽可能避免使用 $scope

标签: javascript angularjs typescript


【解决方案1】:

您不应该将$scope 作为变量存储在this 中,而是使用this 作为$scope,这样做的方法是在构造函数的开头使用$scope.vm = this;,现在类的每个函数和变量都将成为 $scope 的一部分。

你无法避免this.location = $location,因为这是 TypeScript 的语法。

顺便说一句,您应该对依赖项使用 $inject。

【讨论】:

    【解决方案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 $injectstring[] 注入每个可注入对象,然后将它们分别添加为构造函数参数。此角色在定义服务时也可用,并且可以缩小。
    4. 您还可以将$scope 注入您的控制器类以访问特定于范围的工具,例如$applyon 等。
    5. 您可以定义服务以将它们定义为一个类,而不是定义工厂。
    6. 在服务中注入与在控制器中注入相同。
    7. 您可以将 http 调用的返回类型定义为 ng.IPromise&lt;Model&gt; 然后返回 response.data 以确保返回方法的类型只是实体,而不是与 http 相关的数据。

    【讨论】:

      【解决方案3】:

      我们正在考虑进行类似的转换(例如,从 Javascript 到 Angular 的 Typescript)。当我们开始实施时,有些事情(比如你的例子)看起来很奇怪。最快的方法是使用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 语法更进一步。下面的示例不会让您真正自己新建控制器(例如,new 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 使用强类型函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-11-23
        • 2016-11-07
        • 1970-01-01
        • 2013-08-17
        • 2018-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多