Angular 2 Beta, обучающий курс «Тур героев» часть 3

от автора

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

Запустить приложение, часть 3

Где мы остановились

Прежде чем продолжить наш Тур героев, давайте проверим, что наш проект имеет следующую структуру. Если это не так, нужно будет вернуться к предыдущим главам.

    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

Это позволит держать приложение запущенным, пока мы продолжаем создавать Тур героев.

Создание компонента детальной информации о герое

Список героев и детальная информация о герое находятся в одном и том же компоненте, в одном файле. Пока что они невелики, но каждый из них может вырасти. Мы можем получить новые требования к одному из них, что потребует изменения только одного, но не другого. Тем не менее, каждое изменение таит опасность ошибок для двух компонентов и удваивает тестирование. Если бы возникла необходимость повторно использовать детальную информацию о герое в другом месте нашего приложения, нам пришлось бы прихватить и список героев.

Наш текущий компонент нарушает единый принцип ответственности. Этот материал всего лишь урок, но мы можем сделать все правильно — тем более, что это не так сложно. Кроме того, в процессе мы узнаем больше о том, как строить приложения в 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 как тип 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 откажет в связывании и выдаст сообщение об ошибке.

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

Есть несколько способов, как указать, что hero является входящим. Мы сделаем это предпочтительным способом, аннотировав свойство hero декоратором @Input, которое мы импортировали ранее.

    @Input()      hero: Hero;

Узнать больше о декораторе @Input() можно в главе Директивы атрибутов.

Обновление AppComponent

Вернемся к 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

Файлы кода, которые мы обсуждали в этой главе.

app/hero-detail.component.ts

    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;     }

app/app.component.ts

    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" }     ];

app/hero.ts

    export class Hero {       id: number;       name: string;     }

Путь, который мы прошли

Давайте подведем итоги того, что мы создали.

  • Мы создали компонент, который можно использовать повторно.
  • Мы узнали, как сделать, чтобы компонент принимал входные данные.
  • Мы научились связывать родительский компонент с дочерним компонентом.
  • Мы научились объявлять нужные нам директивы приложения в массиве directives.

Запустить приложение, часть 3

Предстоящий путь

Наш тур героев стал более подходящим для многократного использования с разделяемыми компонентами.

Мы все еще получаем наши данные о героях (используя заглушку для их получения) в AppComponent. Это не лучший вариант. Мы должны сделать рефакторинг доступа к данным, вынеся получение данных в отдельный сервис, и расшарить этот сервис компонентам, которым необходимы эти данные.

Мы будем учиться создавать сервисы в следующей главе.

ссылка на оригинал статьи https://habrahabr.ru/post/282634/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *