Это первый пост в серии постов, предназначенных для переноса вашего кода с Angular 1 на Angular 2. В этом посте вы познакомитесь со всеми шаблонами кода, необходимыми для написания кода Angular 1 на Typescript.

Будущие сообщения будут включать перенос вашего кода Angular 1 в Webpack и использование модуля NgUpgrade, чтобы начать миграцию вашего кода в Angular 2.

Шаблоны в этом посте включают в себя принципы руководства по стилю, изложенные Джоном Папой. Будет полезно ознакомиться с этими принципами, прежде чем продолжать вносить эти изменения, но это не обязательно.

Переход на машинопись

Начиная с версии 1.8 Typescript переходить стало проще. Проект Typescript теперь может быть сформирован из файлов .ts и .js. Это означает, что вы можете преобразовывать части своего приложения Angular по одному и при этом компилировать все свои файлы вместе одним простым шагом.

Если вы следовали руководству по стилю Джона Папы, вы, вероятно, оборачивали весь свой текущий код в IIFE (выражения с немедленным вызовом функции), чтобы предотвратить утечку глобальной области видимости. При работе с Typescript мы будем использовать его внутреннюю модульную систему, что означает, что мы будем заключать весь наш код в объявление пространства имен.

namespace superHeroApp {
  ...
}

Когда этот код скомпилирован до стандартного JavaScript, он будет упакован в IIFE, поэтому вам не нужно беспокоиться об утечке глобальной области видимости. Все файлы, кроме файла модуля, будут заключены в объявление пространства имен. Это значительно упрощает ссылки на объекты и определения типов в файлах.

Теперь об узорах.

Шаблон конструктора

Прежде всего, это наш стандартный шаблон конструктора на основе классов, который выглядит примерно так.

namespace superHeroApp {
  class HeroesCtrl {
    static $inject = [
      "heroesService",
      "$q"
    ];
    constructor (
      private heroesService: HeroesService,
      private $q: angular.IQService
    } ( )
  }
  angular
    .module("superHeroApp")
    .controller("HeroesCtrl", HeroesCtrl);
}

Здесь мы зарегистрировали наш контроллер, в который внедрены и наш HeroesService, и сервис $q. Чтобы внедрение зависимостей (DI) работало, все, что нам нужно сделать, это установить свойство $inject и angular позаботиться обо всем остальном, когда он инициализирует контроллер. Службы, которые мы внедряем, затем передаются функции-конструктору. Установка параметров конструктора как «частных» автоматически добавит их к объекту контроллера, чтобы на них можно было ссылаться как на свойства экземпляра, например. this.heroesService.

Этот шаблон можно использовать для регистрации контроллеров и сервисов.

Директивы, которые не требуют внедрения зависимостей для использования в функции ссылки, могут быть написаны с использованием того же шаблона с той лишь разницей, что директива регистрируется.

namespace superHeroApp {
  class LinkFn {
    constructor(scope, element, attrs, ngModel) {}
  }
  class HeroesDir {      
    restrict = "E";
    require = "ngModel";
    templateUrl = "superHeroApp/heroes.html";
    controller = "HeroesDirCtrl";     
    controllerAs = "ctrl";
    bindToController = true;    
    scope = {      
      selected: "="    
    };
    link: (scope, element, attrs, ngModel) => 
            new LinkFn(scope, element, attrs, ngModel); 
  }
  angular    
    .module("superHeroApp")    
    .directive("heroes", () => new HeroesDir());
}

Свойства класса используются для установки всех свойств нашей директивы, а затем при регистрации директивы мы просто возвращаем новый экземпляр класса.

Примечание. Здесь используется синтаксис толстой стрелки ES6, но его можно написать, как показано ниже.

angular 
  .module("superHeroApp")
  .directive("heroes", function() { return new HeroesDir(); });

Заводской узор

Существует небольшая вариация нашего шаблона конструктора, который нам нужен для регистрации директив, требующих внедрения зависимостей или фабрик для других сервисов angular. Заводской шаблон можно зарегистрировать, как показано ниже.

namespace superHeroApp {
  class LinkFn {
    constructor(
      scope,
      element,
      attrs,
      ngModel,
      heroesService
    ) {}
  }
  class HeroesDir {      
    static Factory() {
      let dir = (heroesService) => {
        return new HeroesDir(heroesService);
      };
      dir.$inject = ["heroesService"];
      return dir;
    }
    constructor (
      private heroesService: HeroesService
    ) {}
 
    restrict = "E";
    require = "ngModel";
    templateUrl = "superHeroApp/heroes.html";
    controller = "HeroesDirCtrl";     
    controllerAs = "ctrl";
    bindToController = true;    
    scope = {      
      selected: "="    
    };
    link: (scope, element, attrs, ngModel) =>
      new LinkFn(
        scope, element, attrs, ngModel, this.heroesService);  

}
angular    
    .module("superHeroApp")    
    .directive("heroes", () => new HeroesDir.Factory());
}

В этом примере у нас есть статический фабричный метод, который обрабатывает DI. Затем мы вызываем этот метод для нового экземпляра класса, созданного при регистрации angular. Служба, введенная в директиву, затем может быть передана функции ссылки.

В обоих вариантах определения директивы у вас есть выбор, когда дело доходит до контроллера. Вы можете либо зарегистрировать контроллер с помощью angular, а затем сослаться на контроллер с его зарегистрированным именем angular, либо вы можете просто сослаться на класс (который необходимо будет экспортировать, если ваш контроллер находится в файле, отличном от директивы);

<!-- heroesDir.controller.ts --> 
namespace superHeroApp {
  export class HeroesDirCtrl {
    ...
  }
}
<!-- heroes.directive.ts --> 
namespace superHeroApp {
  class HeroesDir {
    ...
    controller = HeroesDirCtrl;
    ...
  }
}

Как упоминалось выше, фабрики и все другие службы, основанные на фабриках angular, могут быть зарегистрированы с использованием того же метода.

namespace superHeroApp {
  class ConfigFactory {      
    static Factory() {
      let factory = (configService) => {
        return new ConfigFactory(configService);
      };
      factory.$inject = ["configService"];
      return factory;
    }
    constructor (
      private configService: ConfigService
    ) {}
    getConfig() {}
  }
  angular    
    .module("superHeroApp")    
    .factory("config", () => new ConfigFactory.Factory());
}

Заводская выкройка плюс

Это не может быть так просто! Фильтры добавляют еще одну вариацию к заводскому шаблону. Для фильтров требуется функция фильтра, которая может вызываться всякий раз, когда вызывается фильтр, поэтому нам нужно настроить наш шаблон, чтобы вернуть это.

namespace superHeroApp {
  class BadGuyFilter {      
    static Factory() {
      let filter = (badGuyService) => {
        return new BadGuyFilter(badGuyService).get();
      };
      filter.$inject = ["badGuyService"];
      return filter;
    }
   constructor (
      private badGuyService 
    ) {}
    get() {
      return (heroes) => {
        return this.badGuyService.find(heroes);
      };
    }
  }
  angular    
    .module("superHeroApp")    
    .factory("config", () => new BadGuyFilter.Factory());
}

Метод Factory был изменен, чтобы вызвать метод get, который вернет функцию фильтра.

Вот и все! Вооружившись приведенными выше шаблонами, вы сможете зарегистрировать любую службу angular v1.x, написанную на машинописном языке!

P.S. проверьте модуль npm Typings, чтобы установить типы Typescript для angular и любых других библиотек, которые вы используете.