- PVSM.RU - https://www.pvsm.ru -
Наше приложение растет. В этой части сосредоточимся на компонентах, пригодных для повторного использования, а также на передаче данных компонентам. Давайте отделим список героев в отдельный компонент и сделаем этот компонент пригодным для повторного использования.
Запустить приложение, часть 3 [1]
Прежде чем продолжить наш Тур героев, давайте проверим, что наш проект имеет следующую структуру. Если это не так, нужно будет вернуться к предыдущим главам.
angular2-tour-of-heroes
app
app.component.ts
main.ts
node_modules ...
typings ...
index.html
package.json
tsconfig.json
typings.json
Нам нужно запустить компилятор TypeScript, чтобы при этом он отслеживал изменения в файлах и сразу выполнял компиляцию, а также запустить наш web-сервер. Мы сделаем это, набрав
npm start
Это позволит держать приложение запущенным, пока мы продолжаем создавать Тур героев.
Список героев и детальная информация о герое находятся в одном и том же компоненте, в одном файле. Пока что они невелики, но каждый из них может вырасти. Мы можем получить новые требования к одному из них, что потребует изменения только одного, но не другого. Тем не менее, каждое изменение таит опасность ошибок для двух компонентов и удваивает тестирование. Если бы возникла необходимость повторно использовать детальную информацию о герое в другом месте нашего приложения, нам пришлось бы прихватить и список героев.
Наш текущий компонент нарушает единый принцип ответственности [2]. Этот материал всего лишь урок, но мы можем сделать все правильно — тем более, что это не так сложно. Кроме того, в процессе мы узнаем больше о том, как строить приложения в Angular.
Давайте извлечем детальную информацию о герое в свой собственный компонент.
Добавим новый файл с именем hero-detail.component.ts
в папку app
и создадим HeroDetailComponent
, как показано ниже.
hero-detail.component.ts (первоначальная версия)
import {Component, Input} from 'angular2/core';
@Component({
selector: 'my-hero-detail',
})
export class HeroDetailComponent {
}
Cоглашения об именовании
Мы хотели бы понять с первого взгляда, какие классы являются компонентами (по названию класса) и какие файлы содержат компоненты (по названию файла).
Обратите внимание на то, что у нас есть AppComponent
в файле с именем app.component.ts
и наш новый HeroDetailComponent
находится в файле с именем hero-detail.component.ts
.
Все наши составные имена классов заканчиваются на "Component". Все наши составные имена файлов заканчиваются на ".component".
Мы переводим имена файлов в "нижний регистр с тире" (kebab-case), поэтому мы не беспокоимся о чувствительности к регистру на сервере или в системе управления версиями.
Рассмотрим вышеприведенный код.
Мы начали с импорта декораторов Angular — Component
и Input
, потому что они скоро нам понадобятся.
Затем мы создаем метаданные с декоратором @Component
, где мы указываем имя селектора, который идентифицирует элемент компонента. Затем мы экспортируем класс, чтобы сделать его доступным для других компонентов.
Закончив здесь, мы импортируем его в AppComponent
и создадим соответствующий элемент
<my-hero-detail>
.
В данный момент, представления Heroes и Hero Detail объединены в один шаблон в AppComponent
. Давайте вырежем содержимое Hero Detail из AppComponent
и вставим его в новое свойство шаблона HeroDetailComponent
.
Ранее мы привязывали свойство selectedHero.name
в AppComponent
. Наш HeroDetailComponent
будет иметь свойство hero
, а не свойство selectedHero
. Таким образом, мы заменим selectedHero
на hero
повсюду в нашем новом шаблоне. Это наше единственное изменение. Результат будет выглядеть так:
hero-detail.component.ts (шаблон)
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
Теперь наша разметка детальной информации о герое существует только в HeroDetailComponent
.
Добавим свойство hero
, о котором мы говорили выше, к классу компонента.
hero: Hero;
Ой-ой. Мы объявили свойство hero
как тип Hero
, но наш класс героя находится в файле app.component.ts
. У нас есть два компонента, каждый из которых в своем собственном файле, которые должны ссылаться на класс Hero
.
Мы решим эту проблему, переместив класс Hero
из app.component.ts
в свой собственный файл hero.ts
.
hero.ts (Экспортированный класс Hero)
export class Hero {
id: number;
name: string;
}
Мы экспортируем класс Hero
из hero.ts
, потому что нам нужно ссылаться на него в обоих файлах компонентов. Добавьте следующий оператор импорта в верхней части app.component.ts
и hero-detail.component.ts
.
hero-detail.component.ts и app.component.ts (Импорт класса Hero)
import {Hero} from './hero';
Свойство hero является входящим.
Нужно сказать компоненту HeroDetailComponent
, какого героя ему отобразить. Кто ему скажет это? Родитель AppComponent
!
AppComponent
знает, какого героя показать: героя, который пользователь выбрал из списка. Выбор пользователя находится в свойстве selectedHero
.
Мы обновим шаблон AppComponent
так, что он свяжет свое свойство selectedHero
со свойством hero
нашего HeroDetailComponent
. Связывание может выглядеть следующим образом:
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
Обратите внимание на то, что свойство hero
является целевым свойством — оно в квадратных скобках слева от (=).
Angular требует, чтобы объявленное целевое свойство было входящим свойством. Если мы это не сделаем, Angular откажет в связывании и выдаст сообщение об ошибке.
Мы объясним входные свойства более подробно здесь [3], мы также поясним, почему целевые свойства требуют этот специальный подход, а свойства источника нет.
Есть несколько способов, как указать, что hero
является входящим. Мы сделаем это предпочтительным способом, аннотировав свойство hero
декоратором @Input
, которое мы импортировали ранее.
@Input()
hero: Hero;
Узнать больше о декораторе
@Input()
можно в главе Директивы атрибутов [4].
Вернемся к AppComponent
и научим его использовать HeroDetailComponent
.
Начнем с импорта HeroDetailComponent
, чтобы можно было сослаться на него.
import {HeroDetailComponent} from './hero-detail.component';
Найдем место в шаблоне, где мы удалили содержимое Hero Detail и добавим тег элемента, который представляет HeroDetailComponent
.
<my-hero-detail></my-hero-detail>
my-hero-detail — имя, которое мы установили в свойстве
selector
метаданныхHeroDetailComponent
.
Эти два компонента не будет скоординированы, пока мы не свяжем свойство selectedHero
компонента AppComponent
со свойством hero
компонента HeroDetailComponent
, например так:
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
Шаблон AppComponent
должен выглядеть следующим образом:
app.component.ts (Шаблон)
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>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,
Благодаря связыванию, HeroDetailComponent
должен получить героя от AppComponent
и отобразить детальную информацию этого героя под списком. Эта информация должна обновляться каждый раз, когда пользователь выбирает нового героя.
Это пока что не происходит!
Щелкаем в списке героев. Никакой информации. Мы ищем ошибки в консоли "Инструменты разработчика браузера". Ошибок нет.
Выглядит так, будто Angular игнорирует новый тег. Все это потому, что он действительно игнорирует новый тег.
Массив директив
Браузер игнорирует неизвестные ему HTML теги и атрибуты. Так же поступает и Angular.
Мы импортировали HeroDetailComponent
и использовали его в шаблоне, но мы не сказали Angular об этом.
Мы говорим об этом Angular путем перечисления этого компонента в метаданных, в массиве directives
. Добавим этот массив свойств в нижней части конфигурации @Component
, сразу после template
и styles
.
directives: [HeroDetailComponent]
Когда мы просматриваем наше приложение в браузере, мы видим список героев. Когда мы выбираем героя, мы видим детальную информацию о нем.
Принципиальное изменение в том, что мы можем использовать этот компонент HeroDetailComponent
, чтобы отобразить детальную информацию о герое где-нибудь в другом месте приложения.
Мы создали наш первый повторно используемый компонент!
Давайте проверим, что после проведенного в этой главе рефакторинга, у нас следующая структура проекта:
angular2-tour-of-heroes
app
app.component.ts
hero.ts
hero-detail.component.ts
main.ts
node_modules ...
typings ...
index.html
package.json
tsconfig.json
typings.json
Файлы кода, которые мы обсуждали в этой главе.
import {Component, Input} from 'angular2/core';
import {Hero} from './hero';
@Component({
selector: 'my-hero-detail',
template: `
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
</div>
`
})
export class HeroDetailComponent {
@Input()
hero: Hero;
}
import {Component} from 'angular2/core';
import {Hero} from './hero';
import {HeroDetailComponent} from './hero-detail.component';
@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>
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
`,
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;
}
`],
directives: [HeroDetailComponent]
})
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" }
];
export class Hero {
id: number;
name: string;
}
Давайте подведем итоги того, что мы создали.
directives
.Запустить приложение, часть 3 [1]
Наш тур героев стал более подходящим для многократного использования с разделяемыми компонентами.
Мы все еще получаем наши данные о героях (используя заглушку для их получения) в AppComponent
. Это не лучший вариант. Мы должны сделать рефакторинг доступа к данным, вынеся получение данных в отдельный сервис, и расшарить этот сервис компонентам, которым необходимы эти данные.
Мы будем учиться создавать сервисы в следующей главе [5].
Автор: illian
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/119500
Ссылки в тексте:
[1] Запустить приложение, часть 3: https://angular.io/resources/live-examples/toh-3/ts/plnkr.html
[2] единый принцип ответственности: https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html
[3] здесь: https://angular.io/docs/ts/latest/guide/attribute-directives.html#why-input
[4] Директивы атрибутов: https://angular.io/docs/ts/latest/guide/attribute-directives.html#input
[5] следующей главе: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html
[6] Источник: https://habrahabr.ru/post/282634/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.