Статьи

Пересматривая AngularJS с TypeScript

Многие из моих недавних постов и презентаций в блоге были посвящены чистому JavaScript. Я считаю, что TypeScript — невероятно полезный инструмент, особенно при разработке тяжелых клиентских приложений большими группами. Я не использую его в большинстве примеров, поэтому они остаются актуальными для разработчиков, которые не приняли его. TypeScript — сильный актив для приложений AngularJS. Недавно меня спросили о структуре моего приложения Angular с использованием TypeScript, поэтому я разработал небольшой пример.

В моем недавнем выступлении о Advanced AngularJS Tips and Tricks я привел довольно интерактивный пример с использованием списка картинок с выбором. Цель состояла в том, чтобы продемонстрировать контроллер как синтаксис и показать, что вы можете выполнить то, что необходимо, без зависимости от явных наблюдателей. Вы можете просмотреть источник ; контроллеры и т. д. находятся под подпапками «01».

Первый шаг к разработке приложений в TypeScript — это выбор любых связанных файлов определений. Я использую репозиторий, который называется « Типизированный» . Существуют определения для большинства общих библиотек, в том числе Angular. Более того, вы также можете установить его в свои проекты Visual Studio с помощью NuGet .

После того, как у меня есть определения типов в моем проекте, я определяю модуль приложения. Модуль инкапсулирует функциональность и предотвращает конфликты имен. Я должен дать вам предупреждение, согласно рекомендациям Google, вы всегда должны ссылаться на свои модули, вызывая angular.module с именем модуля (после того, как вы определили модуль путем передачи зависимостей), а не пытаться захватить глобальную ссылку. Лично у меня нет проблемы с сохранением ссылки, когда я уже создаю область модуля для приложения, поэтому я решил использовать ее.

declare var angular: ng.IAngularStatic;

module Application {
    "use strict";

    export var angularApp: ng.IModule =
        angular.module("angularApp", ["ngAnimate"]);
}

Код использует преимущества однозначно типизированной библиотеки для определения углового статического типа. Это дает мне IntelliSense / автоматическое завершение и проверку во время разработки функциональных вызовов, которые я буду делать. Единственный реальный сгенерированный код — это ссылка на текущий модуль, содержащаяся в переменной angularApp. Сгенерированный код выглядит так:

var Application;
(function (Application) {
    "use strict";

    Application.angularApp = angular.module(
        "angularApp", ["ngAnimate"]);
})(Application || (Application = {}));

Следующий логический компонент для определения — это данные. Для данных я делаю две вещи. Во-первых, я экспортирую интерфейс, чтобы упростить работу с другими приложениями. Это обеспечит обнаружение и проверку типа во время разработки. Во-вторых, я создаю функцию для инкапсуляции определения, прежде чем передать его в Angular. Это закрытие предотвращает «утечку» данных к остальной части приложения (и, возможно, столкновение или дальнейшее загрязнение логических пространств имен), поэтому после вызова единственный способ ссылаться на них — через зависимость от Angular. Я усек список для отображения:

module Application {

    "use strict";

    export interface ISunsetData {
        image: string;
        location: string;
        title: string;
    }

    function initData(): void {
        var data: ISunsetData[] = [
            {
                image: "backyard.jpg",
                location: "Woodstock, GA",
                title: "Sunset in Woods"
            }
        ];
        angularApp.value("sunsets", data);
    };


    initData();
}

После того, как данные определены, я помещаю их в сервис. Сервис предоставляет фильтр, который контроллеры могут использовать, и предоставляет «выбранный элемент», используемый для визуализации полноразмерного изображения. Обратите внимание, что в TypeScript я могу выставить контракт для сервиса, не раскрывая сам сервис. Класс для сервиса не экспортируется, поэтому он доступен только для регистрации в Angular. Когда я ссылаюсь на это, я использую интерфейс.

module Application {

    "use strict";

    export interface ISunsetService {
        sunsets: ISunsetData[];
        selected: ISunsetData;
        filter: string;
        select: (sunset: ISunsetData) => void;
    }

    class SunsetService implements ISunsetService {
        constructor(
            public sunsets: ISunsetData[],
            public $log: ng.ILogService) {

            $log.log("Sunsets service created.");
        }

        public selected: ISunsetData;

        public filter: string;

        public select(sunset: ISunsetData): void {
            this.$log.log("Selected sunset "
                + sunset.title);
            this.selected = sunset;
        }

    }

    angularApp.service("sunsetSvc", [
        "sunsets", "$log", SunsetService]);
} 

В этом примере я регистрирую службу, используя переменную, которую я установил в основном приложении для ссылки на модуль. Я также использую версию внедрения зависимости во время регистрации, так как я регистрирую класс одновременно с его определением. Если бы я использовал систему для определения класса, а затем зарегистрировал его в другом месте, я бы использовал статическую сигнатуру $ inject: string [], чтобы можно было легко обнаружить зависимости.

У меня нет необходимости выставлять определение контроллера для остальной части приложения, поэтому я просто определяю класс, не экспортируя его, и регистрирую его в Angular. Я использую экспортированные контракты, чтобы привести свойства в конструктор. Обратите внимание, как легко TypeScript позволяет определять свойства get и setter.

module Application {

    "use strict";

    class SearchController {

        private _filter:string;

        constructor(
            public $log: ng.ILogService,
            public sunsetSvc: ISunsetService) {
            this._filter = "";
            $log.log("Search controller created.");
        }

        public get filter(): string {
            return this._filter;
        }

        public set filter(val: string) {
            this.sunsetSvc.filter = val;
            this._filter = val;
        }

    }

    angularApp.controller("searchCtrl", ["$log", "sunsetSvc", SearchController]);
} 

Это сгенерированный JavaScript для свойства фильтра:

Object.defineProperty(SearchController.prototype,
    "filter", {
    get: function () {
        return this._filter;
    },
    set: function (val) {
        this.sunsetSvc.filter = val;
        this._filter = val;
    },
    enumerable: true,
    configurable: true
});

Контроллер списка похож. Вы можете просмотреть полный исходный код преобразованного проекта TypeScript по этой ссылке . Он является частью исходного проекта и содержит только исходные файлы TypeScript для простоты.

Как видите, TypeScript очень хорошо работает с AngularJS. Файлы определений помогают в изучении API и обеспечении его правильного использования во время разработки. Интерфейсы помогают описать структуру, и их легко содержать в определенных модулях или областях. Хотя все это возможно с чистым JavaScript, язык TypeScript не только облегчает определение этих шаблонов, но и реализует их согласованным образом.

Используете ли вы TypeScript с AngularJS или вы рассматриваете это? Если это так, поделитесь своими мыслями или вопросами в комментариях ниже!