【问题标题】:What is best practice to create an AngularJS 1.5 component in Typescript?在 Typescript 中创建 AngularJS 1.5 组件的最佳实践是什么?
【发布时间】:2016-05-28 20:37:13
【问题描述】:

我正在 Angular 1.5 中试验 .component() 语法。

似乎最新的时尚是将控制器内联编码在组件中,而不是在单独的文件中,鉴于组件样板最少,我可以看到这样做的优势。

问题是我一直将我的控制器编码为打字稿类,并希望继续这样做,因为这似乎与 Angular2 一致。

我的最大努力是这样的:

export let myComponent = {
  template: ($element, $attrs) => {
    return [
      `<my-html>Bla</my-html>`
    ].join('')
  },
  controller: MyController
};
class MyController {

}

它有效,但并不优雅。有没有更好的办法?

【问题讨论】:

  • 不优雅是指要清理代码吗?
  • @Katana24 我想你可以这样说:)。我无法在 Typescript 中找到 1.5 组件的示例,所以我想知道我的做法是否是最佳实践。例如根据标题,我可以将整个事物定义为一个类吗?
  • 说实话,如果它工作得很好,但它不是在 Typescript 中编写 angular 1 的风格,而且你的帖子是我第一次看到。一般来说,我认为你应该按照推荐的约定用纯 javascript 编写 Angular 1。我知道这并不能真正回答你的问题......

标签: angularjs typescript angularjs-components


【解决方案1】:

我正在使用以下模式将 angular 1.5 component 与 typescript 一起使用

class MyComponent {
    model: string;
    onModelChange: Function;

    /* @ngInject */
    constructor() {
    }

    modelChanged() {
        this.onModelChange(this.model);
    }
}

angular.module('myApp')
    .component('myComponent', {
        templateUrl: 'model.html',
        //template: `<div></div>`,
        controller: MyComponent,
        controllerAs: 'ctrl',
        bindings: {
            model: '<',
            onModelChange: "&"
        }
    });

【讨论】:

  • Function 类型肯定是我缺少的东西之一。我什至没有看到记录在哪里!
  • 我尝试了您的代码,但如果我使用它,我会收到以下错误“必须使用 |new| 调用类构造函数”。你知道为什么吗?
  • @Shamshiel 这太晚了,但有时当 Angular 无法检测到一个对象是 ES6 类时,就会发生这种情况。我相信 Firefox 在声明组件控制器时特别容易受到这个问题的影响。
【解决方案2】:

我在同一个问题上苦苦挣扎,并将我的解决方案放在这篇文章中:

http://almerosteyn.github.io/2016/02/angular15-component-typescript

module app.directives {

  interface ISomeComponentBindings {
    textBinding: string;
    dataBinding: number;
    functionBinding: () => any;
  }

  interface ISomeComponentController extends ISomeComponentBindings {
    add(): void;
  }

  class SomeComponentController implements ISomeComponentController {

    public textBinding: string;
    public dataBinding: number;
    public functionBinding: () => any;

    constructor() {
      this.textBinding = '';
      this.dataBinding = 0;
    }

    add(): void {
      this.functionBinding();
    }

  }

  class SomeComponent implements ng.IComponentOptions {

    public bindings: any;
    public controller: any;
    public templateUrl: string;

    constructor() {
      this.bindings = {
        textBinding: '@',
        dataBinding: '<',
        functionBinding: '&'
      };
      this.controller = SomeComponentController;
      this.templateUrl = 'some-component.html';
    }

  }

  angular.module('appModule').component('someComponent', new SomeComponent());

}

【讨论】:

  • 您应该将解决方案的重要部分(“答案”)放在答案中。
  • 是的,会编辑它。从我的手机发布此消息blush
【解决方案3】:

这是我使用的模式:

ZippyComponent.ts

import {ZippyController} from './ZippyController';

export class ZippyComponent implements ng.IComponentOptions {

    public bindings: {
        bungle: '<',
        george: '<'
    };
    public transclude: boolean = false;
    public controller: Function = ZippyController;
    public controllerAs: string = 'vm'; 
    public template: string = require('./Zippy.html');
}

ZippyController.ts

export class ZippyController {

    bungle: string;
    george: Array<number>;

    static $inject = ['$timeout'];

    constructor (private $timeout: ng.ITimeoutService) {
    }
}

Zippy.html

<div class="zippy">
    {{vm.bungle}}
    <span ng-repeat="item in vm.george">{{item}}</span>
</div>

ma​​in.ts

import {ZippyComponent} from './components/Zippy/ZippyComponent';

angular.module('my.app', [])
    .component('myZippy', new ZippyComponent());

【讨论】:

  • 既然用了require,那你是用babel来构建这个的吗?
  • 我一直在用 webpack 构建。
【解决方案4】:

我正在使用一个简单的 Typescript 装饰器来创建组件

function Component(moduleOrName: string | ng.IModule, selector: string, options: {
  controllerAs?: string,
  template?: string,
  templateUrl?: string
}) {
  return (controller: Function) => {
    var module = typeof moduleOrName === "string"
      ? angular.module(moduleOrName)
      : moduleOrName;
    module.component(selector, angular.extend(options, { controller: controller }));
  }
}

所以我可以这样使用它

@Component(app, 'testComponent', {
  controllerAs: 'ct',
  template: `
    <pre>{{ct}}</pre>
    <div>
      <input type="text" ng-model="ct.count">
      <button type="button" ng-click="ct.decrement();">-</button>
      <button type="button" ng-click="ct.increment();">+</button>
    </div>
  `
})
class CounterTest {
  count = 0;
  increment() {
    this.count++;
  }
  decrement() {
    this.count--;
  }
}

你可以在这里http://jsbin.com/jipacoxeki/edit?html,js,output尝试一个工作的jsbin

【讨论】:

  • 顺便说一句,这与 Angular 2 组件几乎相同,因此升级的痛苦更少。如果同时使用两个版本,装饰器可以重命名为 Ng1Component
  • 在这个 Typescript 装饰器行 controllerAs?: string, 和接下来的 2 行中,在我的情况下有这个错误:TS1005: ";" expected。为什么? :/ 谢谢。只是我复制粘贴了
  • 采用scarlz,您可以将您的选项类型替换为ng.IComponentOptions
  • 虽然我喜欢这个想法,但一个缺点是它需要引用您的 Angular 模块,根据您的应用程序结构,它可能不容易拥有甚至还不存在。特别是如果您使用带有 ES6 模块加载的功能孤岛。在这种情况下,您最终会将所有组件都放在 index.js 文件或其他一些反模式中。
【解决方案5】:

如果您想完全采用 Angular 2 方法,您可以使用:

module.ts

import { MyComponent } from './MyComponent';

angular.module('myModule', [])
  .component('myComponent', MyComponent);

MyComponent.ts

import { Component } from './decorators';

@Component({
  bindings: {
    prop: '<'
  },
  template: '<p>{{$ctrl.prop}}</p>'
})
export class MyComponent {

   prop: string;

   constructor(private $q: ng.IQService) {}

   $onInit() {
     // do something with this.prop or this.$q upon initialization
   }
}

decorators.ts

/// <reference path="../typings/angularjs/angular.d.ts" />

export const Component = (options: ng.IComponentOptions) => {
  return controller => angular.extend(options, { controller });
};

【讨论】:

  • 你将如何使用 Typescript 在 MyComponent 中访问 prop
  • 它在 Chrome、Safari 和 Edge 中完美运行,但 Firefox 在$controllerInit 命令上出现错误Error: class constructors must be invoked with |new| 失败。任何想法如何解决它?
  • 我应该使用任何模块加载器来让这种 ng2 样式在 ng1 中工作吗? Angular 如何仅通过注释了解该组件?你有任何示例回购吗?
  • 我认为这不适用于任何现代版本的 TypeScript,因为无论装饰器返回什么都必须与 Controller 类兼容
【解决方案6】:

我建议不要使用定制的解决方案,而是使用 ng-metadata 库。您可以在https://github.com/ngParty/ng-metadata 找到它。像这样,您的代码与 Angular 2 最兼容。正如自述文件中所述,它是

没有黑客。没有覆盖。生产就绪。

我只是在使用此处答案中的定制解决方案后进行了切换,但是如果您立即使用此库会更容易。否则,您将不得不迁移所有小的语法更改。一个例子是这里的其他解决方案使用语法

@Component('moduleName', 'selectorName', {...})

Angular 2 使用

@Component({
  selector: ...,
  ...
})

因此,如果您不立即使用 ng-metadata,您将大大增加以后迁移代码库的工作量。

编写组件的最佳实践的完整示例如下

// hero.component.ts
import { Component, Inject, Input, Output, EventEmitter } from 'ng-metadata/core';

@Component({
  selector: 'hero',
  moduleId: module.id,
  templateUrl: './hero.html'
})
export class HeroComponent {

  @Input() name: string;
  @Output() onCall = new EventEmitter<void>();

  constructor(@Inject('$log') private $log: ng.ILogService){}

}

(复制自ng-metadata recipies

【讨论】:

    【解决方案7】:

    我相信一种好方法是使用angular-ts-decorators。有了它,您可以像这样在 AngularJS 中定义组件:

    import { Component, Input, Output } from 'angular-ts-decorators';
    
    @Component({
      selector: 'myComponent',
      templateUrl: 'my-component.html
    })
    export class MyComponent {
        @Input() todo;
        @Output() onAddTodo;
    
        $onChanges(changes) {
          if (changes.todo) {
            this.todo = {...this.todo};
          }
        }
        onSubmit() {
          if (!this.todo.title) return;
          this.onAddTodo({
            $event: {
              todo: this.todo
            }
          });
        }
    }
    

    然后使用以下方法将它们注册到您的模块中:

    import { NgModule } from 'angular-ts-decorators';
    import { MyComponent } from './my-component';
    
    @NgModule({
      declarations: [MyComponent]
    })
    export class MyModule {}
    

    如果您想查看使用它的真实应用程序示例,可以查看this one

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-27
      • 2013-05-17
      • 1970-01-01
      • 1970-01-01
      • 2021-04-02
      • 1970-01-01
      • 2020-10-19
      • 1970-01-01
      相关资源
      最近更新 更多