Что нас ждет в Angular 2.0
Одним из самых ожидаемых событий 2015 года, для фронт-енд разработчиков, помимо выхода финальной спецификации ES6, является появление новой версии одного из самых популярных фреймворков, AngularJS. Анонсированные изменения, настолько значительны, что существует мнение о том, что это по сути новый фреймворк написанный с нуля.В связи с этим, я позволю себе представить вам перевод большой статьи «All About Angular 2.0», одного из разработчиков фреймворка Роба Ейзенберга, в которой он раскрывает что ждет нас в следующей версии.
Все, кому это может быть интересным, добро пожаловать под кат. И запаситесь временем… :-)
Предпосылки для возникновения Angular 2.0AtScriptВнедрение зависимостей (Dependency Injection, DI)Шаблонизация и связывание данных (Templating and Databinding)Маршрутизатор (The Router)
ПреамбулаХотите понять как стратегия будет реализована в Angular 2.0? Вы в нужном месте. В следующей статье я объясню основные возможности Angular 2.0 включая мотивацию которая стоит за этими изменениями. По мере чтения, я постараюсь раскрыть свою точку зрения и идеи о процессе создания Angular 2.0, включая важные области в которых, как я думаю, все еще необходимы улучшения.Angular 1.3 Прежде чем начать говорить о будущем AngularJS, давайте на минутку посмотрим на его текущую версию. Angular 1.3 является лучшей версией AngularJS доступной на данный момент. И она была выпущена в релиз совсем недавно. С множеством багфиксов, расширением возможностей и увеличением производительности. Это один из сильных и зрелых фреймворков доступных сегодня.Но у вас вероятно имеется множество вопросов о будущем AngularJS. Когда выйдет версия 2.0? Что случится с 1.х? Будет ли возможно миграция с 1.х на 2.0?
Команда Angular работает гораздо лучше отвечая на подобные вопросы и вы можете помочь им в этом.
Могу вам сказать что более 1600 проектов в Google были построены с использованием Angular. Как вы можете видеть Google много инвестировал в текущую версию и в любом случае будет вынужден поддерживать ее. Отвечая на вопросы на ngEurope Brad Green сказал что после выхода релиза Angular 2.0 можно ожидать что Angular 1.3 будет поддерживаться еще в течении 1.5 — 2 лет. Мы так же предприняли соответствующие изменения в команде и руководстве, направленные на поддержку Angular 1.3. И поэтому даже несмотря на то что мы интенсивно работаем над Angular 2.0, остается команда преданная Angular 1.3. Этой командой руководит Pete Bacon Darwin, которого, я уверен вы знаете по его значительному вкладу в AngularJS. Я бы хотел воодушевить вас на обращение к руководителям проекта, с просьбой о более значительных действий в этом направлении, включая официальную поддержку старой версии.
Тоже самое касается вопроса миграции с Angular 1.х на Angular 2.0. Я думаю мы должны и будем работать в этой области. Если это так же важно для вас, дайте об этом знать. Команде AngularJS должна знать насколько это важно для вас, что бы они думали об этом и планировали все наперед.
Мотивация для возникновения Angular 2.0 Возможно вы думаете: — А зачем делать Angular 2.0 вообще? Зачем сразу прыгать к 2.0 и вносить столько ключевых изменений? Не является ли это капризом? Ну можно еще понять небольшие изменения, но к чему все эти принципиальные изменения в Angular 2.0. Это оправдано? Оно того стоит? Я бы хотел остановиться на нескольких моментах, прежде чем погружусь в детали. Я надеюсь это предоставит основу понимания последующих деталей и базу для осмысленной критики (часть из которой я намерен предложить сам).
Производительность Когда AngularJS был впервые создан, более 5 лет назад, он по сути не был предназначен для программистов. Это был инструмент больше нацеленный на верстальщиков, которые нуждались в быстром создании HTML форм. В течении времени произошли изменения основанные на различных требованиях и разработчики взяв это инструмент стали создавать все более и более сложные приложения. Команда Angular 1.х много работала над изменениями позволяющими удовлетворять потребности современных вебприложений. Однако существует ряд ограничений влияющий на возможные улучшения. Часть этих ограничений связаны с производительностью и являются результатом используемой модели связывания данных и инфраструктуры шаблонов. Для решения данных проблем требуется совершенно новый подход.Изменения в Интернете За прошедшие пять лет, с момента выхода AngularJS, в интернете произошли значительные изменения. Например 5 лет назад было возможно создать приличный кросс-браузерный сайт без помощи jQuery. Однако сегодняшние браузеры не только интенсивно вносят различные изменения в DOM, но и часто эти изменения очень актуальных для фреймворков.и интернет продолжает меняться…
Все те массовые изменения произошедшие за последние несколько лет, только бледная тень того что нас ждет в ближайшие 1–3 года. В течении нескольких месяцев ES6 будет завершен. И есть все основания думать что в браузеры в 2015 году обеспечат его полную поддержку. Текущие браузеры уже поддерживают некоторые функции такие как модули, классы, лямбды, генераторы и т.д. Эти возможности фундаментально изменят опыт программирования на JavaScript. Но большие изменения не ограничены только JavaScript. На горизонте Web Components. Это термин обычно связан с коллекцией следующих W3C спецификаций: — Custom Elements — возможность расширять HTML настраиваемыми тегами.— HTML Imports — возможность упаковки различных ресурсов (HTML, JS, CSS…).— Template Element — возможность вставки инертного HTML в документ.— Shadow DOM — возможность инкапсуляции DOM и CSS.
Комбинация этих четырех возможностей позволяет разработчикам создавать декларативные компоненты (Custom Elements) которые полностью инкапсулируются (Shadow DOM). Эти компоненты могут описывать их собственные виды (Template Element) и могут легко паковаться для передачи другим разработчикам (HTML Imports). Когда спецификации будут поддерживаться всеми основными бразуреми, мы вероятно увидим взрыв креативных решений в области создания собственных компонентов, решающих основные проблемы и недостатки в стандартном HTML (взято из внутренней документации проекта Databinding whit Web Components).Это уже сегодня возможно в Chrome, и другие браузеры так же внедряют эти изменения. Отличное будущие, не правда ли? Здесь есть только одна проблема: большинство из сегодняшних фреймворков не готовы к этому, включая Angular 1.х. Большинство фреймворков имеют систему связывания данных основанную на небольшом количестве HTML элементов с хорошо известными эвентами и поведением. AngularJS должен позволить разработчикам использовать все преимущества Web Components, а для этого требуется новая реализация связывания данных (databinding).
Мобильные устройства Говоря о пяти прошедших пяти годах… боже мой, насколько изменился компьютерный пейзаж. Мобильные телефоны и планшеты повсюду! Несмотря на то что AngularJS может использоваться для создания мобильных аппликаций, он не создавался с этой целью. Это включает в себя все, начиная от проблем с производительностью, недостающие возможностью маршрутизации, отсутствия кеширования предкомпилированных видов и даже не блещущая поддержка эвентов основанных на прикосновениях.Простота использования Давайте будем честными… AngularJS не самая легкая вещь в изучении. Да когда вы начинаете вы думаете: Да это прекрасно. Это реально легко и волшебно!!!» Потом вы начинаете создавать свою аппликацию и говорите: «Боже… что это?! Я не понимаю!». Я слышу эту историю снова и снова. Даже есть забавный график иллюстрирующий это.Большинство из сложностей произрастает из оригинального дизайна фреймворка. Первоначально, не было кастомных директив. Для их реализации писали хардкод. И уже позже, они появились в API. Первоначально не было контроллеров, потом… ну в общем взгляните на график.Итог Что бы привести AngularJS в соответствие с текущим положением дел в интернете, стало абсолютно понятно что необходимо его принципиальное изменение. Фактически, без этих изменений, AngularJS рискует стать устаревшим в течении года. И именно поэтому команда AngularJS отвечая на вызовы времени, создает новую версию Angular. Речь идет, по существу, о переосмыслении AngularJS с позиций современно интернета. ПримечаниеДаже несмотря на то что я не могу раскрыть всех деталей, я могу определенно сказать что Angular 2.0 сильно отличается от 1.х. Кто то даже может спросить если это тот же самый фреймворк. Думаю это хороший вопрос. Как я уже упоминал ранее, я уверен что команде AngularJS необходимо взять на себя бремя поддержки версии 1.х, а так же подготовить механизм миграции на 2.0 и руководство для компаний принимающих решение об использовании фреймворка сегодня и всех кто планирует двигаться дальше с 2.0. И хотя сейчас не существует подобных задач у команды сосредоточенной на технологической стороне вопроса, я думаю это необходимо и будет полезно и уважительно по отношению к сообществу AngularJS.
Теперь, когда понятен бэкграунд изменений, давайте посмотрим на ключевые моменты.
AtScript AtScript язык расширяющий ES6, который начали использовать авторы Angular 2.0. Он имеет синтаксис подобный TypeScript, для представления дополнительных типов данных, который может проверятся на стадии выполнения, что лучше чем на стадии компиляции. Он так же расширяет язык аннотаций метаданных (metadata annotations).Вот примет кода: import {Component} from 'angular'; import {Server} from './server';
@Component ({selector: 'foo'}) export class MyComponent { constructor (server: Server) { this.server = server; } } Здесь мы видим ES6 базовый код с небольшим добавлением AtScript. Объявления Import и class соответствуют точному синтаксису ES6. Ничего особенного. Но взгляните на функцию конструктор. Обратите внимание на то что параметр server определен вместе с типом. В AtScript типы используются для создания проверок в момент выполнения (runtime). Обратите так же внимание на синтаксис @Component, над декларацией класса. Это и есть аннотация метаданных. Component является обычным классом, как любой другой. Когда мы его создаем с использованием аннотации, компилятор создает код являющийся экземпляром аннотации и сохраняет его в область доступную для фреймворка. Помня это, давайте посмотрим на пример в чистом ES6:
import * as rtts from 'rtts'; import {Component} from 'angular'; import {Server} from './server';
export class MyComponent { constructor (server) { rtts.types (server, Server); this.server = server; } }
MyComponent.parameters = [{is: Server}]; MyComponent.annotate = [ new Component ({selector: 'foo'}) ]; RTTS аббревиатура для RunTime Type System. Это небольшая библиотека нацеленная на проверку типов на стадии выполнения скрипта. В момент обработки кода компилятором, переменная server будет объявлена типом Server. Вы так же можете создавать собственные типы, для соответствия структурной типизации, или использовать специальные правила. Но когда вам нужно будет создать готовый релиз, компилятор может исключить эти объявления типов из финального кода для повышения производительности.
Прекрасно то, что типизация является независимой, объявление типа и аннотации могут отслеживаться. Оба вида объявлений могут транслироваться в очень простую ES5 совместимую структуру данных, хранящуюся внутри функции MyComponent. Это позволяет любому фреймворку или библиотеке находить и использовать эти данные. На протяжении нескольких лет это было весьма полезно на таких платформах как .NET и Java. Это, так же, чем то напоминает возможности мета-программирования имеющиеся в Ruby. Конечно же, аннотации могут использоваться для мета-программирования в Angular 2.0, для более простого создания директив. Подробности позже.
ПримечаниеМне лично нравится AtScript, но я так же старый фанат TypeScript. Так что можно сказать, что я был уже подготовлен к AtScript. Я знаю что многие разработчики противятся использованию типов в JavaScript. Не могу их винить. Я имею богатый опыт с различными система типизации. Иногда хороший, иногда не очень. Интересным моментом тут является то, что в AtScript вы можете использовать синтаксис типов, как простой путь для предоставления метаданных другим библиотекам. Я думаю что это одна из самых мощных возможностей AtScript, позволяющая хранить информацию о типе в момент выполнения, для предоставления фреймворку или мета-программирования. Я не буду удивлен если и в других транслируемых языках (в которых перед компиляцией происходит трансляция в другой язык. Например: TypeScript, CoffeeScript транслируются в JavaScript), так же появятся подобные возможности.
Тем не менее у меня есть пара замечаний.
Я бы хотел видеть более официальное признание AtScript. Это значит что этот язык должен быть не только внутри команды работающей над AngularJS. Он должен жить собственной жизнью, в которой команда AngularJS только важный пользователь. Это значит что несколько разработчиков должны фултайм работать над развитием AtScript, исправлением багов, улучшению генерируемого кода, созданию инструментов и т.п., и это должен быть долговременный план. Когда разработчики выбирают язык, это важное решение. Я бы хотел видеть что Google серьезно выбирает AtScript для будущей работы.
Другим вопросом, который возникает в связи с этим, является Dart. Dart другой язык так же разработанный в Google. И это не просто транслируемый в JavaScript язык. Он имеет собственную среду исполнения и библиотеки классов. Как результат Dart имеет собственный API для манипуляций с DOM, коллекции, события, регулярные выражения и другое. Хотя эти API хороши сами по себе, они не совместимы с существующим JavaScript кодом. Из-за этого несовпадения, между Dart и другим миром, он общается через специальный ограниченный интерфейс. И несмотря на техническую возможность использования JavaScript библиотек, чаще всего это непрактично. В случае с AngularJS, это приведет к неприемлемо низкой производительности. Ну, Google взяли и создал Angular Dart. Версию Angular повторенную в Dart.
Проблем решена… да уж скорее нет.
Теперь есть две версии AngularJS, в которых нужно исправлять баги, добавлять возможности, выпускать релизы… написанные в разных языках и обслуживаемые разными командами. В общем одну проблему решили за счет появления другой.
Возможно вы сейчас удивлены: Какое все это имеет отношение к AtScript?
Для Angular 2 есть идея объединить Angular и Angular Dart. Вместо двух команд, работающих над двумя разными версиями одного и того же, лучше иметь одну команду делающую работу в одном направлении. AtScript поможет здесь потому что он имплементирован на вершине #Traceur, который легко расширяется. Тем самым команды получат возможность использовать AtScript для Javascript и Dart.
Прекрасно! И в чем проблема?
Помните я писал что Dart имеет другую модель DOM и прочие. Не так то просто транслировать подобный код. Как результат, процесс сборки в Angular 2.0, будет в реальности сложнее чем можно подумать. Потребуется создать различные версии API для Javascript и Dart, которые потом будет использовать компилятор. Это еще та задача.И это повышает барьер для тех кто захочет внести свой вклад в Angular 2.0. Хотя следует отметить, что возможно это только проблема текущей, ранней стадии. И возможно будут найдены другие решения. Я знаю что многие из вас внесли свой большой вклад в развитие AngularJS. Команда Angular это ценит и думает над тем как это может быть улучшено. Однако, в настоящие время, это далеко от идеала.
Важное замечание! То что Angular 2.0 пишется с использованием AtScript не значит что вы тоже будете вынуждены его использовать. Свои Angular 2.0 приложения, вы можете легко писать на TypeScript, ES6, ES5, CoffeeScript… или том что вы предпочитаете. Конечно лучше всего использовать AtScript для разработки на Angular 2.0, из-за его способности автоматически создавать метаданные из примитивов языка, но в конце концов они транслируются и в простой ES5. Финальный ES5, в этом случае, концептуально подобен DDO из Angular 1.x, но он все равно лучше чем просто directive-specific технология.
Внедрение зависимостей (Dependency Injection, DI) Базовой возможностью Angular 1.х было внедрение зависимостей. Через DI вы могли легко увидеть принцип «разделяй и властвуй» в разработке ПО. Сложные задачи могут быть концептуализированны с точки зрения их роли и обязанностей. Потом они могут быть представлены в виде объектов, совместное использование которых приводит к достижению конечной цели. Большие (или маленькие) системы, которые следуют этому пути, могут монтироваться на лету, путем использование DI фреймворка. Такие системы обычно проще тестировать, поскольку финальный дизайн получается более модульным и позволяет проще изолировать компоненты. Все это было возможно в Angular 1.х, но тем не менее было несколько проблем.Первая проблема, мучающая Angular 1.х связана с минификацией. В момент когда минификатор производил замену имен на более короткие, он искажал название зависимости, в результате чего она переставала работать. Изменения API добавили возможность сделать код боле дружественным для минификации, но при этом потерялась исходная элегантность.Другие проблемы Angular 1.х, связаны с отсутствующими возможностями, обычными для серверных DI фреймворков, доступных в мире .NET и Java. Например, lifetime/scope control и child injection.
Аннотации В AtScript мы представили основной механизм для связывания метаданных с функцией. Также, AtScript формат для метаданных устойчив к минификации и легко пишется и в ES5. Это делает его идеальным кандидатом для поддержи DI библиотек, с информацией необходимой для создания экземпляров объектов.Когда DI нужен экземпляру класса (или при вызове функции), он исследует их для получения ассоциированных метаданных. Вот пример в AtScript:
MyComponent.parameters = [{is: Server}]; В новом DI ищется значения свойства parametrs, которое будет использовано для определения зависимостей и попытки из вызвать. В данном случае, это один параметр типа Server. В результате будет создан экземпляр объекта Server и помещен в функцию перед ее вызовом. Вы так же можете использовать специальную Inject аннотацию для использования вместе с DI. Это отменит использование parametrs. Это так же легко поддерживается, даже если вы используете язык который автоматически не создает метаданные. Вот пример на чистом ES5:
MyComponent.annotate = [new Inject (Server)]; Среда выполнения воспринимает это как параметры. Нужно отметить, что сейчас вы можете использовать что угодно как injection token. Например, вот так:
MyComponent.annotate = [new Inject ('my-string-token')]; Сразу как вы сконфигурируете DI, с чем то что можно связать с 'my-string-token', все это будет работать прекрасно. Тем не менее рекомендуется использовать экземпляр конструктора как было показано выше.
Экземпляр области видимости (Instance Scope) В Angular 1.х все экземпляры класса в DI были синглтонами. Это осталось дефолтным и в Angular 2.0.Для того что бы получить другое поведение в 1.х, нужно было использовать Services, Providers, Constants… Это приводило к путанице.К счастью, новый DI имеет новые, более мощные, возможности. Появился контроль области видимости. Итак, если вам нужно, DI создаст новый экземпляр объекта. Всякий раз когда вам потребуется это, нужно всего лишь сделать следующие: @TransientScope export class MyClass { … } Это становится еще более мощным, когда вы создаете ваш собственный идентификатор области видимости для использования в комбинации с child injectors…
Вставка наследника (Child injectors) Child injectors это большая новая возможность. Child injectors наследует от родителя все сервисы, но имеет возможность переопределять их на своем уровне. Когда это возможность комбинируется с собственной областью видимости вы можете легко вызывать некоторые типы объектов в вашей системе, что автоматически переопределит их в разных областях видимости. Это очень мощная возможность. Как пример этого, новый маршрутизатор имеет Child Routers. Каждый Child Router, внутри создает собственный Child injector. Это позволяет каждой части маршрутизатора наследовать сервисы от родительского маршрутизатора или переопределять эти сервисы на основании разных сценариев навигации. ПримечаниеНастраиваемые области видимости и Child injectors, предполагаются как довольно сложные и продвинутые в использовании возможности. Я не ожидаю что большинство приложений будет это использовать. Однако, поскольку это уже внутри Angular, это доступно и вам, если вам потребуются подобные возможности.
и еще…
Некоторые другие возможности в новом DI, такие как провайдеры (providers) (кастомные функции которые обеспечивают значения для вставки), ленивая вставка (lazy injection) (определяет что вы хотите что-то вставить, но не сейчас, а позже) и promise-based async injection (вставки и обещания к которым вы можете получить доступ асинхронно запросив соответствующую зависимость)
КомментарийМне лично нравиться новый DI. Опять же, я немного предвзят здесь, поскольку использую DI уже много лет, и это всегда было центральным компонентом в UI фреймворках которые я создавал. Новый DI играет важную роль в Angular 2.0. Возможности, такие как Child injectors, дают огромную разницу в сравнении со старыми решениями. Теперь когда это возможность есть, она может использоваться и для шаблонизации и для маршрутизации. И там и там есть потребность создавать собственную область видимости и изолировать различные сервисы.
Одной из важнейших вещей которые будут удалены в Angular 2.0 является: $scope. Однако, несмотря на то что $scope будет удален, некоторые его возможности сохранятся. Эти возможности будут перемещены и улучшены став частью дизайна фреймворка. Вы возможно будете застигнуты в врасплох удалением $scope, но новый дизайн упростит вещи и внутри Angular и для разработчиков. Я пишу это здесь, поскольку новые возможности DI, такие как Child injectors, пересекаются с предыдущими возможностями из $scope. В этом случае, новый DI выкладывает на стол лучшее решение. В общем, это не только решит внутренние потребности самого Angular, но и откроет новые возможности для вас.
К сожалению, жизнь, это не прогулка в розовом саду, с молодой любовницей. Давайте поговорим от проблемах. Речь пойдет о модулях. Вы можете удивится, каким боком здесь это. Но в планы для Angular 2.0 входит адаптация ES6 стандартов для модулей. В текущей версии Angular был использован свой специфичный подход для обработки модулей. Пять лет назад, когда Angular разрабатывался, не было никаких стандартов на это счет. Сегодня вещи изменились и есть более чистый подход к решению этой задачи. Это ключевое изменение и требует переработки кода от каждого кто решить мигрировать на новую версию Angular. Это конечно тяжело, что такое критическое изменение должно быть сделано, но Angular может стать неуместным, если эта проблема не будет решена сейчас.
Есть еще один камень преткновения связанный с DI, особенно если вы пишите на ES5. Angular 2.0 предпочитает дизайн основанный на классах с аннотациями и метаданными. Синтаксис классов и аннотаций не особо хорош в ES5. В общем то, его там вообще нет. Вы можете реализовать все используя прототипы и другое, но это не так чисто как в AtScript или даже в ES6 или TypeScript, которые поддерживают классы и статичные члены классов. Я удивлюсь если это может быть улучшено для разработчиков не готовых перейти на ES6. Возможно, дополнительная библиотека даст вам простой путь создания классов с метаданными? Возможно это будет немного похоже на DDO объект в Angular 1.х, но более общее, что позволит создать класс с метаданными. Я бы хотел услышать соображения об этой идее и другие идеи которые у вас возможно есть, как сгладить разработку в ES5 и улучшить процесс миграции.
Шаблонизация и связывание данных (Templating and Databinding) Мы подошли к действительно интересным вещам: шаблонизации и связыванию данных. Я собираюсь обсудить их в тандеме. Хотя система связывания данных технически стоит отдельно от шаблонизации, в реальном опыте, при создании приложения они используются совместно. Таким образом рассматривать их бок-о-бок имеет больший смысл.Давайте начнем с понимания как вид (View) попадает на экран. По существу вы начинаете с фрагмента HTML кода. Он будет находится внутри элемента. Этот фрагмент обрабатывается компилятором шаблонизатора. Компилятор анализирует фрагмент, определяет в нем директивы, связывает выражения, события и т.п. Все эти данные извлекаются из DOM в структуру данных, которая может быть использована в конце концов для создания экземпляра шаблона. Как часть данной фазы происходит некоторая обработка данных, такая как обработка выражений например. Каждый узел содержащий специальные инструкции связан со соответствующим классом. Результат данного процесса кешируется так что бы не возникала необходимость повторной обработки. Мы называет это результат: ProtoView. После того как вы получили ProtoView вы можете использовать его для создания View. Когда ProtoView создает View, для всех директив, идентифицированных на предыдущем шаге, создаются их экземпляры для вставки в DOM. Watchers устанавливаются на связанных выражениях. Конфигурируются перехватчики событий. Структуры данных, которые были получены в предыдущем процессе, в фазе компиляции, позволяют нам делать все очень быстро. После того как вы получили View, вы можете отобразить его добавив во ViewPort. Как разработчик, вы не увидите большую часть всего этого, вы просто создадите шаблон и он будет работать. Но я хотел расписать этот процесс на более высоком уровне, прежде чем перейти к деталям.
Динамическая загрузка (Dynamic Loading) Одной из очень недостающих возможностей в Angular 1.х была динамическая загрузка кода. Если вы хотели добавить новые директивы или контроллеры на лету, это было очень сложно или даже невозможно. Это просто не поддерживалось. В Angular 2.0, мы создаем некоторые вещи с нуля, помня об асинхронности. И когда мы компилируем шаблон, это по сути асинхронный процесс.Теперь, мне нужно упомянуть о деталях компиляции шаблона и я оттолкнусь от моего упрощенного объяснения выше. Когда вы компилирует шаблон вы не только обеспечиваете компилятору шаблон, но и вы так же предоставляете определение для компонентов (Component definition). Component definition содержит метаданные о том что именно, директивы, фильтры и тому подобное использовано в шаблоне. Это гарантирует то, что все необходимые зависимости, будут загружены прежде чем они будут обработаны в процессе компиляции. Из-за того что мы базируем наш код на ES6 спецификации, простое упоминание зависимости в Component definition вызовет модуль загрузки и загрузит ее, если она еще не была загружена. Таким образом, используя возможности ES6 для модулей, мы бесплатно получаем динамическую загрузку всего что нам понадобится.
Директивы (Directives) Прежде чем мы копнем синтаксис шаблонов, мы должны взглянуть на директивы как на способ расширения HTML. В Angular 1.х DDO (Directive Definition Object) использовался для создания директив. Это кажется был один из самых больших источников для страданий разработчиков.Что если мы сделаем директивы проще?
Мы поговорили о модулях, классах и аннотациях. Что если мы используем эти основные конструкции для создания директив? И конечно же мы это сделали.
В Angular 2.0 существует три основных типа директив.
Component Directive — создает кастомный компонент объединяющий View и Controller. Мы можем использовать его как собственных HTML элемент. А так же маршрутизатор может создавать маршрут к компонентам. Decorator Directive — расширение существующих HTML элементов дополнительным провидением. Классический пример ng-sh-ow. Template Directive — Трансформация HTML в многократно используемый шаблон. Создатель директивы может контролировать где и как шаблон будет обработан и вставлен в DOM. Примером могут служить ng-if и ng-repeat. Вы возможно уже слышали о смерти контроллеров в Angular 2.0. Ну, это не совсем правда. В реальности контроллеры стали частью того что мы называем Component. Component состоит из вида (View) и контроллера (Controller). Вид это ваш HTML шаблон и контроллер определяющий JavaScript логику. Вместо того что бы определять контроллер через API как это сделано в Angular 1.х, в 2.0 вы можете создать обычный класс с некоторой аннотацией. Вот пример части компоненты реализующей контроллер (как выгляди Вид рассмотрим немного позже):
@ComponentDirective ({
selector:'tab-container',
directives:[NgRepeat]
})
export class TabContainer {
constructor (panes: Query
select (selectedPane: Pane) { … } } Можно отметить здесь несколько моментов.
Первый, контроллер компоненты это просто класс. Конструктор автоматически вставит зависимости. Благодаря тому что здесь использован Child injector, класс может получить доступ к любому сервису выше в DOM иерархии, а так же службам локальным для данного элемента. Например, в данном случае, Query injector. Это специальная коллекция, которая автоматически синхронизируется с наследниками Pane (панель) элемента и дает вам знать когда что-то добавляется или удаляется.Это позволит вам обрабатывать подобную логику как в $link коллбек из Angular 1.х, но там это обрабатывается иным способом чем через конструктор класса.
Теперь, взгляните на @ComponentDirective аннотацию. Это идентифицирует класс как компоненту и предоставляет метаданные которые нужны компилятору для его вставки. Например selector:'tab-container', это CSS селектор который будет использован для поиска в HTML. Элемент совпавший с этим селектором будет возвращен в TabContainer. Так же, directives:[NgReapet] указывает на зависимость используемую в шаблоне. Я пока это не демонстрировал. Чуть позже мы увидим это когда поговорим о синтаксисе.
Важной деталью является то что шаблон будет прямо связан с классом. Это значит что любые свойства и методы класса могут быть доступны прямо в шаблоне. Это подобно «controller as» синтаксису из Angular 1.2. Но при этом нет необходимости установки $scope между классом и шаблоном. Как результат упрощение Angular изнутри и упрощение синтаксиса для разработчиков на нем.
Давайте теперь взглянем на Decorator Directive. Как насчет NgShow?
@DecoratorDirective ({ selector:'[ng-show]', bind: { 'ngShow': 'ngShow' }, observe: {'ngShow': 'ngShowChanged'} }) export class NgShow { constructor (element: Element) { this.element = element; }
ngShowChanged (newValue){ if (newValue){ this.element.style.display = 'block'; }else{ this.element.style.display = 'none'; } } } Здесь мы можем видеть немного больше аспектов. Прежде всего, снова использован класс с аннотацией. Конструктор связывает класс с декодируемым HTML элементом. Компилятор понимает что речь идет о декорации потому что это Decorator Directive и понимает что нужно это применить к любому элементу который совпадает с CSS селектором selector:'[ng-show]'.
Так же есть несколько других любопытных свойств у данной аннотации.
bind: {'ngShow': 'ngShow'} используется для связывания свойства класса с HTML атрибутом. Не все свойства класса проявятся в виде HTML атрибутов. Если вы хотите что бы свойство было связываемым с HTML, вы определяете это в bind метаданных.
observe: {'ngShow': 'ngShowChanged'} говорит системе связывания что вы хотите получать уведомления когда ngShow свойство изменится и будет вызван метод ngShowChanged. Обратите внимание что ngShowChanged реагирует на изменения так, что в свою очередь изменят отображение HTML элемента к которому он присоединен. (Заметим что это очень простоя реализация, только для демонстрационных целей).
ОК, как же выглядит Tеmplate Directive? Как насчет того чтобы взглянуть на NgIf?
@TemplateDirective ({ selector: '[ng-if]', bind: {'ngIf': 'ngIf'}, observe: {'ngIf': 'ngIfChanged'} }) export class NgIf { constructor (viewFactory: BoundViewFactory, viewPort: ViewPort) { this.viewFactory = viewFactory; this.viewPort = viewPort; this.view = null; }
ngIfChanged (value) { if (! value && this.view) { this.view.remove (); this.view = null; }
if (value) { this.view = this.viewFactory.createView (); this.view.appendTo (this.viewPort); } } } Надеюсь вам понятен смысл этой Tеmplate Directive аннотации. Прежде всего идет регистрация директивы и предоставление необходимых метаданных для установки свойств и наблюдателей, точно так как в предыдущем примере для NgShow. Tеmplate Directive имеет доступ к определенным специальным службам которые могут быть вставлены в конструкторе. Первой является ViewFactory. Как упоминалось раньше Tеmplate Directive преобразует HTML указанный в шаблоне. Шаблон автоматически компилируется и вы получаете доступ к ViewFactory в Tеmplate Directive. Вызов createView API создает экземпляр шаблона. Вы так же получаете доступ к ViewPort который представляет область DOM из которого был извлечен шаблон. Вы можете использовать это для добавления или удаления экземпляра шаблона из DOM. Обратите внимание как ngIfChanged реагирует на изменения в обрабатываемом шаблоне и добавляет или удаляет это из ViewPort. Если вы реализуете вместо это NgRepeat вы должны многократно создать экземпляр шаблона и так же предоставить специфические данные в createView API и затем вы сможете добавить множество экземпляров во ViewPort. Это основы.
Теперь вы ознакомились с каноническими примерами трех типов директив. Я надеюсь что это разъяснило вещи в плане того как вы можете расширить HTML новым поведением.
Тем не менее, есть важная вещь, которую я до сих пор не полностью разъяснил. Это контроллеры.
Как вы создаете контроллер для вашей аппликации? Допустим, вы хотите настроить маршрутизатор так чтобы происходил вызов контроллера и отображался Вид. Как вы это сделаете? Простой ответ, это использовать Component Directive.
В Angular 1.х директивы и контроллеры были разными вещами. С разным API и возможностями. В Angular 2.0 мы убрали DDO и сделали директивы основными на классах, где мы смогли объединить директивы и контроллеры внутри модели компоненты. Теперь когда вы настраиваете маршрутизатор, вы просто указываете маршрут к Component Directive (которая содержит вид и контроллер, по существу, так же как и раньше).
Например если вы создаете гипотетический контроллер для редактирования клиентов, это может выглядеть так:
@ComponentDirective export class CustomerEditController { constructor (server: Server) { this.server = server; this.customer = null; }
activate (customerId) { return this.server.loadCustomer (customerId) .then (response => this.customer = response.customer); } } Здесь нет реально ничего нового. Мы просто вставили гипотетическую службу server и используем ее для загрузки данных о клиенте в момент когда мы активировали маршрутизатор. Единственное, что может быть тут интересно, то что вам не нужен селектор или другие метаданные. Причиной того является что этот компонент не используется как элемент. Он был динамически создан маршрутизатором и динамически вставлен в DOM. Как результат мы опустили ненужные детали.
Итак, вы знаете как создать Component Directive, как создать эквивалент Angular 1.х контроллера для использования с маршрутизатором. Это было очень непросто в Angular 1.х, но теперь у нас есть классы и метаданные и работа с директивами значительно упрощается и становится значительно проще создавать свои «контроллеры» таким способом.
ПримечаниеЯ бы хотел отметить, что примеры кода директив показанных выше основаны на комбинации предварительной версии кода и черновых