What is best practice to create an AngularJS 1.5 component in Typescript?

29,885

Solution 1

If you wanted to completely adopt an Angular 2 approach, you could use:

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

Solution 2

I am using a simple Typescript decorator to create the component

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

so I can use it like this

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

You can try a working jsbin here http://jsbin.com/jipacoxeki/edit?html,js,output

Solution 3

This is the pattern I use:

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>

main.ts

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

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

Solution 4

I was struggling with the same question and put my solution in this article:

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

}

Solution 5

I'm using the following pattern to use angular 1.5 component with 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: "&"
        }
    });
Share:
29,885
kpg
Author by

kpg

I have a great breadth of experience in business and technology gained over many years working in many different roles in every type of organisation in several countries around the world. I am an ICF certified coach. I am a Zoho consultant and a director of Appz Ltd, A Zoho Partner based in Malta but still like to get my hand dirty writing Typescript/Javascript for Node.js and Zoho Catalyst.

Updated on September 23, 2020

Comments

  • kpg
    kpg almost 4 years

    I am experimenting with the .component() syntax in Angular 1.5.

    It seems that the latest fashion is to code the controller in-line in the component rather that in a separate file, and I can see the advantage of that given that the component boilerplate is minimal.

    The problem is that I having been coding my controllers as typescript classes and would like to continue doing so because that seems to be consistent with Angular2.

    My best effort is something like this:

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

    It works, but it's not elegant. Is there a better way?