Статьи

AngularJS Tutorial — Создайте приложение, используя директивы и привязку данных

AngularJS быстро завоевывает репутацию одной из самых дальновидных платформ JavaScript, и на это есть веские причины. При поддержке и разработке Google Angular использует подход, который на первый взгляд может показаться немного странным, но вскоре вы удивитесь, почему вы поступили иначе.

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

Если вы знакомы с CoffeeScript (не требуется для Angular), вам больше понравится эта статья, но для этого должно быть достаточно практического знания JavaScript.

Скорее всего, вы уже видели множество приложений Todo, поэтому давайте создадим что-нибудь веселое — крестики и нолики!

Мы начнем с разметки нашей доски.

Angular утверждает, что расширяет словарный запас HTML вместо того, чтобы скрывать DOM за JavaScript. Философия заключается в том, что HTML довольно хорош сам по себе, но мы можем добавить еще несколько элементов и атрибутов для создания мощного динамического языка шаблонов, с которым вы уже знакомы.

Наша игровая доска будет просто столом. Если мы программируем желаемое за действительное, все, что нам действительно нужно, — это перебирать игровую доску, выводя ячейку для каждого. Реальный код для этого довольно близок к нашему видению:

<table> <tr ng-repeat="row in board.grid"> <td ng-repeat="cell in row"> {{ cell.marker }} </td> </tr> </table> 

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

 <tr ng-repeat="row in board.grid"> 

AngularJS директивы

ng-repeat — это угловая директива , одно из предоставляемых расширений HTML. Это позволяет нам перебирать коллекцию, создавая шаблон для каждого элемента внутри. В нашем случае мы говорим Angular повторить <tr> для каждой строки в свойстве grid нашей доски — предположим, что сейчас grid это двумерный массив, а board это объект в окне.

 <td ng-repeat="cell in row"> {{ cell.marker }} </td> 

Затем мы используем другую директиву ng-repeat для перебора ячеек в строке. Двойные фигурные скобки здесь указывают на выражение, использующее привязку угловых данных — содержимое td будет заменено свойством marker соответствующей ячейки.

Довольно просто, правда? Вы сразу получите представление о том, как будет выглядеть полученная разметка. Нам не нужно использовать что-то тяжелое, например jQuery, для создания новых элементов и их заполнения, мы просто делаем наш шаблон явным. Это более легко обслуживаемо — мы точно знаем, где и как DOM будет изменен, просто взглянув на наш HTML, а не отслеживая какой-то непонятный JavaScript, который мы не помним, когда пишем.

Теперь, когда мы можем визуализировать состояние нашей платы, мы предоставим ей источник данных, определив, что именно представляет собой board .

 app = angular.module('ngOughts', ['ng']) 

Web начинается с добавления некоторого JavaScript, который определяет модуль Angular для нашего приложения. Первый аргумент — это имя нашего приложения, ['ng'] означает, что нам требуется модуль Angular ‘ng’, который предоставляет основные службы Angular.

Мы изменили наш HTML, чтобы указать, что мы будем использовать наш модуль ng-app директивой ng-app .

 <html ng-app='ngOughts'> 

MVC — определение контроллера и представлений

Здесь вступает в игру природа Angular MVC . Мы добавили еще немного JS для вызова функции controller в нашем недавно созданном модуле приложения, передавая имя нашего контроллера и функцию, которая его реализует.

 app.controller "BoardCtrl", ($scope) -> 

В этом случае наша функция контроллера принимает один аргумент, $scope , который является зависимостью нашего контроллера. Angular использует внедрение зависимостей для предоставления нам этого сервисного объекта, выводя правильный объект из имени нашего параметра функции (есть альтернативный синтаксис, который также позволяет минимизировать).

Теперь мы добавим директиву ng-controller в наш HTML-шаблон, чтобы подключить его к нашему контроллеру:

 <body ng-controller="BoardCtrl"> <table> <tr ng-repeat="row in board.grid"> ... </tr> </table> </body> 

Опять же, так же просто, как атрибут с именем нашего контроллера. Здесь все становится интереснее — элементы, вложенные в наш тег body теперь имеют доступ к сервисному объекту $scope . Затем наш атрибут ng-repeat будет искать в области BoardCtrl переменную board, поэтому давайте определим это:

 app.controller "BoardCtrl", ($scope, Board) -> $scope.board = new Board 

Теперь мы куда-то добираемся. Мы ввели Board в наш контроллер, создали его экземпляр и сделали его доступным в рамках BoardCtrl .

Давайте продолжим и на самом деле реализуем простой класс Board .

 class Board SIZE = 3 EMPTY = ' ' NOUGHT = 'O' CROSS = 'X' PLAYER_MARKERS = [NOUGHT, CROSS] constructor: -> @reset() reset: -> @grid = [1..SIZE].map -> [1..SIZE].map -> new Cell(EMPTY) class Cell constructor: (@marker) -> 

Добавление фабрики

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

 angular.module("ngOughts").factory "Board", -> Board 

Можно определить Board непосредственно внутри factory функции или даже поместить Board в объект окна, но его отличное расположение позволяет нам тестировать Board отдельно от AngularJS и поощряет его повторное использование.

Так что теперь у нас есть пустая доска. Захватывающие вещи, верно? Давайте настроим все так, чтобы щелкнув по ячейке
размещает маркер там.

 <table> <tr ng-repeat="row in board.grid"> <td ng-repeat="cell in row" ng-click="board.playCell(cell)"> {{ cell.marker }} </td> </tr> </table> 

Мы добавили директиву ng-click для каждого из наших элементов <td> . Если щелкнуть ячейку таблицы, мы playCell функцию playCell на доске с объектом ячейки, по playCell щелкнули. Заполнение Board реализации:

 class Board SIZE = 3 EMPTY = ' ' NOUGHT = 'O' CROSS = 'X' PLAYER_MARKERS = [NOUGHT, CROSS] constructor: -> @reset() reset: -> @current_player = 0 @grid = [1..SIZE].map -> [1..SIZE].map -> new Cell(EMPTY) playCell: (cell) -> return if cell.hasBeenPlayed() cell.mark(@currentPlayerMarker()) @switchPlayer() currentPlayerMarker: -> PLAYER_MARKERS[@current_player] switchPlayer: -> @current_player ^= 1 class Cell constructor: (@marker) -> mark: (@marker) -> hasBeenPlayed: -> @marker != EMPTY 

Двухстороннее связывание данных

Итак, теперь, когда мы обновили модель платы, нам нужно вернуться и обновить вид, верно?

Нет! Угловое связывание данных выполняется в двух направлениях: оно отслеживает изменения в моделях и передает их обратно в представление. Аналогичным образом, обновление вида обновит соответствующие модели. Наш маркер будет обновлен во внутренней grid Board и содержание <td> будет немедленно изменено, чтобы отразить это.

Это отсекает так много хрупкого, зависимого от селектора стандартного кода, который вам раньше приходилось писать. Вы можете сосредоточиться на логике и поведении своего приложения, а не на слесарном деле.

Было бы хорошо, если бы мы знали, когда кто-то победил. Давайте реализуем это. Здесь мы опускаем код для проверки условий выигрыша, но он присутствует в окончательном коде. Скажем, когда мы находим выигрыш, мы устанавливаем свойство winning в каждой из ячеек, которые его составляли.

Затем мы можем изменить наш <td> на что-то вроде следующего:

 <td ng-repeat="cell in row" ng-click="board.playCell(cell)" ng-class="{'winning': cell.winning}"> {{ cell.marker }} </td> .winning { background: green; color: white; } 

Если winning верна, ng-class применяет CSS-класс «победителя» к <td> , что позволяет нам установить приятный зеленый фон в честь нашей победы. Реванш ты говоришь? Нам понадобится кнопка сброса платы:

 <button ng-click="board.reset()">reset board</button> 

Добавив это в наш контроллер, мы будем вызывать reset после нажатия кнопки. Маркеры на доске будут стерты, все CSS-классы очищены, и мы готовы снова — с нулевым обновлением элементов DOM, необходимых нам.

Давайте по-настоящему злорадствуем за нашу победу

 <h1 ng-show="board.won">{{ board.winning_marker }} won the game!</h1> 

Директива ng-show позволяет нам условно показывать элемент <h1> когда игра выиграна, а привязка данных позволяет интерполировать маркер победителя. Просто и выразительно.

Более удобное для тестирования приложение

Интересно отметить, что большая часть нашего кода имеет дело с простым старым JavaScript. Это преднамеренно — никаких расширенных каркасных объектов, только написание и вызов JS. Этот подход подходит для более составных, тестируемых приложений, которые чувствуют легкий вес. Наши проектные проблемы разделены MVC, но нам не нужно писать стек кода только для того, чтобы соединить все вместе.

AngularJS не без границ, хотя. Многие жалуются на официальную документацию и относительно крутую кривую обучения, у некоторых есть проблемы с SEO, а у других просто возникают проблемы с использованием нестандартных атрибутов и элементов HTML.

Тем не менее, есть решения этих проблем, и уникальный подход AngularJS к веб-разработке, безусловно, стоит потратить некоторое время на изучение.

Вы можете увидеть окончательный код в действии на Plunkr или загрузить его с GitHub .

Комментарии к этой статье закрыты. Есть вопрос по AngularJS? Почему бы не спросить об этом на наших форумах ?