Часть 1 Часть 2 Часть 3 Часть 4
Нужно больше героев
Нашей истории необходимо больше героев. Мы расширим наше приложение Тур героев, чтобы отобразить список героев, в котором пользователь может выбрать героя, и посмотреть детальную информацию о нем.
Давайте подумаем, что нам нужно, чтобы отобразить список героев. Во-первых, нам нужен массив, который будет содержать список героев для отображения. Затем нам необходим способ передать данные из массива в шаблон.
Где мы остановились
Прежде чем перейти ко второй Тура героев, необходимо выполнить первую часть. Если вы этого не сделали, вернитесь назад.
Поддержка преобразования кода и выполнения приложения
Мы хотим запустить компилятор TypeScript, чтобы при этом он отслеживал изменения в файлах и сразу выполнял компиляцию, а также запустить наш web-сервер. Мы сделаем это, набрав
npm start
Это позволит держать приложение запущенным, пока мы продолжаем создавать Тур героев.
Отображение наших героев
Создание героев
Давайте создадим массив из десяти героев в нижней части app.component.ts
.
app.component.ts (Массив героев)
let HEROES: Hero[] = [
{ "id": 11, "name": "Mr. Nice" },
{ "id": 12, "name": "Narco" },
{ "id": 13, "name": "Bombasto" },
{ "id": 14, "name": "Celeritas" },
{ "id": 15, "name": "Magneta" },
{ "id": 16, "name": "RubberMan" },
{ "id": 17, "name": "Dynama" },
{ "id": 18, "name": "Dr IQ" },
{ "id": 19, "name": "Magma" },
{ "id": 20, "name": "Tornado" }
];
HEROES
— массив элементов типа Hero
, класса, который мы создали в первой части. Мы, конечно, хотим получать этот список героев из веб-сервиса, но давайте делать маленькие шаги, и для начала отобразим героев из мок-объекта (заглушки, которая возвращает заранее указанные данные).
Представление героев
Давайте создадим свойство в AppComponent
, которое будет источником героев для связывания.
app.component.ts (Свойство — массив героев)
public heroes = HEROES;
Нам не нужно явно определять тип для heroes
. TypeScript может получить его из переменной HEROES
.
Мы могли бы определить список героев здесь, в этом классе компонента. Но мы знаем, что в итоге мы будем получать героев из web-службы. Поэтому имеет смысл сразу вынести эти данные из реализации класса.
Отображение героев в шаблоне
Наш компонент содержит heroes
. Давайте создадим неупорядоченный список в нашем шаблоне, чтобы отобразить их. Мы вставить следующий кусок HTML под заголовком, выше детальной информации о герое.
app.component.ts (Шаблон героев)
<h2>My Heroes</h2>
<ul class="heroes">
<li>
<!-- each hero goes here -->
</li>
</ul>
Теперь у нас есть шаблон, который мы можем заполнить нашими героями.
Вывод списка героев с ngFor
Мы хотим связать массив героев в нашем компоненте с нашим шаблоном, перебрать его, и отобразить каждого из героев по отдельности. Нам понадобится некоторая помощь Angular, чтобы сделать это. Давайте начнем шаг за шагом:
Во-первых, изменим тег <li>
, добавив встроенную директиву *ngFor
.
app.component.ts (ngFor)
<li *ngFor="#hero of heroes">
*Ведущая звездочка (`) перед
ngFor` является важной частью синтаксиса.**
Префикс *
для ngFor
указывает на то, что элемент <li>
и его дочерние элементы составляют мастер-шаблон.
Директива ngFor
проходит по массиву heroes
, который вернуло свойство AppComponent.heroes
, и выводит их в шаблон.
Текст в кавычках, присвоенный ngFor
, обозначает: "возьми каждого героя в массиве heroes
, сохрани в локальной переменной hero
, и сделай его доступным для соответствующего экземпляра шаблона".
Префикс #
перед hero
идентифицирует героя как локальную переменную шаблона. Затем в шаблоне мы можем сослаться на эту переменную, чтобы получить доступ к свойствам героя.
Узнать больше о ngFor
и локальных переменных шаблона можно в главах Отображение данных и Синтаксис шаблона.
Теперь мы вставим некоторый код между тэгами <li>
, который использует переменную шаблона hero
, чтобы отобразить свойства героя.
app.component.ts (ngFor template)
<li *ngFor="#hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
Когда браузер обновит страницу, мы увидим список героев!
Добавление стилей нашим героям
Наш список героев выглядит довольно скучно. Мы хотим сделать его визуально очевидным для пользователя, чтобы он понимал, какой герой выбран, а над каким героем сейчас находится курсор.
Давайте добавим стили к нашему компоненту. Присвоим свойству styles
в декораторе @Component
следующие CSS классы:
app.component.ts (Добавление стилей)
styles:[`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes .text {
position: relative;
top: -3px;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
`]
Обратите внимание на то, что мы снова используем `-нотацию для многострочного представления длинной строки.
Когда мы назначаем стили компоненту, они находятся только в области видимости этого конкретного компонента. Поэтому наши стили будут применяться только к AppComponent
и не будут "утекать" во внешний HTML.
Наш шаблон для отображения героев должен теперь выглядеть следующим образом:
app.component.ts (Стили для героев)
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="#hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
Как много стилей! Мы можем включить их в описание компонента, как показано здесь, или переместить их в отдельный файл, что сделает проще код нашего компонента. Мы сделаем это в следующей главе. Пока что оставим все, как есть.
Выбор героя
В нашем приложении есть список героев и информация об одном герое. Список героев и один герой никак не связаны между собой. Мы хотим, чтобы пользователь выбрал героя в нашем списке, и информация о выбранном герое появилась в детальном представлении. Этот шаблон UI широко известен как "Master-Detail" (переводится как "главный-подчиненный", но в дальнейшем будет использоваться "Master/Detail"). В нашем случае, Master — список героев, а Detail — детальное представление выбранного героя.
Давайте соединим Master с Detail через свойство компонента 'selectedHero', связанного с событием клика на герое в списке.
Событие клика (щелчка мыши)
Изменим <li>
, вставив обработку события клика, используя привязку события Angular.
app.component.ts (Захват события клика)
<li *ngFor="#hero of heroes" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
Сфокусируемся на привязке события:
(click)="onSelect(hero)"
Скобки назначают элемент <li>
целью для события клика. Выражение справа от знака равенства вызывает метод onSelect()
, который находится в компоненте AppComponent
, передавая локальную переменную шаблона hero
в качестве аргумента. Это та же самая переменная hero
, которую мы определили ранее в ngFor
.
Подробнее о привязке событий можно узнать в главах Пользовательский ввод и Синтаксис шаблонов.
Добавление обработчика клика
Наше событие привязано к методу onSelect
, который пока не существует. Добавим этот метод к нашему компоненту. Что должен делать этот метод? Он должен записать в переменную "выбранный герой" нашего компонента того героя, на которого нажал пользователь.
Пока что наш компонент не имеет такой переменной, поэтому начнем того, что добавим ее.
Назначение выбранного героя
Нам больше не нужно свойство hero
в AppComponent
. Заменим его на свойство selectedHero
:
app.component.ts (selectedHero)
selectedHero: Hero;
Мы решили, что герой не должен иметь значения, пока пользователь сам не выберет героя, поэтому мы не будем инициализировать selectedHero
, как мы делали с hero
.
Теперь добавим метод onSelect
, который записывает в свойство selectedHero
значение hero
, на котором кликнул пользователь.
app.component.ts (onSelect)
onSelect(hero: Hero) { this.selectedHero = hero; }
Мы должны отобразить детальную информацию выбранного героя в нашем шаблоне. В данный момент шаблон по-прежнему обращается к старому свойству hero
. Давайте исправим шаблон для привязки к новому свойству selectedHero
.
app.component.ts (Привязка к selectedHero's)
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>
Сокрытие пустых данных с ngIf
После загрузки нашего приложения мы видим список героев, но герой не выбран. selectedHero
не определен, то есть он undefined
. Вот почему мы увидим следующее сообщение об ошибке в консоли браузера:
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
Как вы помните, мы отображаем selectedHero.name
в шаблоне. Свойство name
не существует, потому что переменная selectedHero
, которая содержит свойство, не определена.
Мы решим эту проблему, убрав детальную информацию о герое из DOM до тех пор, пока герой не будет выбран.
Мы обернули HTML, который содержит детальную информацию о герое, в <div>
. Добавим встроенную директиву ngIf
и установим ей свойство selectedHero
нашего компонента.
app.component.ts (ngIf)
<div *ngIf="selectedHero">
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>
</div>
*Запомните, что ведущая звездочка () перед 'ngIf' является важной частью синтаксиса.**
Пока переменная selectedHero
не определена, директива ngIf
удаляет HTML с детальной информацией о герое из DOM. Таким образом у нас не будет ни элементов с детальной информацией о герое, ни привязок, о которых стоит беспокоиться.
Когда пользователь выбирает героя в списке, переменная selectedHero
получает значение и становится определенной, а ngIf
размещает данные с детальной информацией о герое в DOM и осуществляет вложенные привязки.
ngIf
и `ngFor' называются "структурными директивами", потому что они могут изменить структуру частей DOM. Другими словами, они определяют структуру того, как Angular отображает контент в DOM.
Узнать больше о ngIf, ngFor и других структурных директивах можно в главах Структурные директивы и Синтаксис шаблонов.
Браузер обновляется, и мы видим список героев, но без детальной информации о выбранном герое. NgIf
хранит ее вне DOM, пока переменная selectedHero
не определена. Когда мы нажимаем на героя в списке, отображается детальная информация о выбранном герое. Все работает, как мы ожидали.
Стилизация выбранного элемента
Мы видим информацию о выбранном герое под списком, но мы не можем быстро найти этого героя в списке выше. Мы можем исправить это путем применения класса CSS selected
к соответствующему элементу <li>
в главном списке. Например, когда мы выбираем Magenta из списка героев, мы можем сделать его выделить его визуально, изменив цвет фона, как показано здесь.
Мы добавим свойство привязки к элементу class
, чтобы в шаблоне установить класс selected
. Мы сделаем это через выражение, которое сравнивает текущего selectedHero
и hero
.
Ключ это имя класса CSS (selected
). Значение истинно (true
), если оба героя совпали и ложь (false
) иначе. Мы говорим "применить класс selected
, если герои совпадают, удалить его, если это не так".
app.component.ts (Установка CSS класса)
[class.selected]="hero === selectedHero"
Обратите внимание, что в шаблоне class.selected
заключен в квадратные скобки ([]
). Это синтаксис для связывания свойств, привязки, в которой потоки данных идут в одну сторону от источника данных (выражение hero === selectedHero
) к свойству class
.
app.component.ts (Стилизация каждого героя)
<li *ngFor="#hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
Узнайте больше о Связывании свойств можно в главе Синтаксис шаблонов.
Браузер перезагружает наше приложение. Мы выбираем героя Magenta, и выбор четко идентифицируется цветом фона.
Мы выбираем другого героя и цвет фона переключается на этого героя.
Вот полное содержимое app.component.ts
на данный момент:
import {Component} from 'angular2/core';
export class Hero {
id: number;
name: string;
}
@Component({
selector: 'my-app',
template:`
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="#hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
<div *ngIf="selectedHero">
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>
</div>
`,
styles:[`
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes .text {
position: relative;
top: -3px;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
`]
})
export class AppComponent {
title = 'Tour of Heroes';
heroes = HEROES;
selectedHero: Hero;
onSelect(hero: Hero) { this.selectedHero = hero; }
}
var HEROES: Hero[] = [
{ "id": 11, "name": "Mr. Nice" },
{ "id": 12, "name": "Narco" },
{ "id": 13, "name": "Bombasto" },
{ "id": 14, "name": "Celeritas" },
{ "id": 15, "name": "Magneta" },
{ "id": 16, "name": "RubberMan" },
{ "id": 17, "name": "Dynama" },
{ "id": 18, "name": "Dr IQ" },
{ "id": 19, "name": "Magma" },
{ "id": 20, "name": "Tornado" }
];
Путь, который мы прошли
Вот то, чего мы достигли в этой главе:
- Наш Тур героев теперь отображает список героев.
- Мы добавили возможность выбора героя и отображения детальной информации о выбранном герое.
- Мы узнали, как использовать встроенные директивы
ngIf
иngFor
, в шаблоне компонента.
Предстоящий путь
Наш Тур героев вырос, но до завершения еще далеко. Мы не можем поместить в один компонент приложение целиком. Нам нужно разбить его на подкомпоненты и научить их работать вместе. Мы узнаем, как это сделать, в следующей главе.