02. Компонент редактирования. Роутинг

Редактирование

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

Сперва создаём новый компонент, который будет отвечать за вывод информации на редактирование.

ng generate component friend-detail

Теперь сделаем так, чтобы компонент принимал в себя экземпляр объекта типа Friend. Для этого внесём такие изменения в класс компонента ./src/app/friend-detail/friend-detail.component.ts:

import { Component, Input } from '@angular/core'; // Здесь добавляем Input, тем самым сообщаем, что в компоненте будут свойства, которые будут задаваться извне
import { Friend } from '../friend'; // Подключим наш класс, чтобы компонент был в курсе, что есть такой класс - Friend

@Component({
	selector: 'app-friend-detail',
	templateUrl: './friend-detail.component.html',
	styleUrls: ['./friend-detail.component.css']
})
export class FriendDetailComponent {

	@Input() friend: Friend; // Вот так мы говорим компоненту, что эта переменная будет задаваться "снаружи"

	constructor() { }

}

Зададим шаблон компонента. Для этого открываем на редактирование шаблон компонента ./src/app/friend-detail/friend-detail.component.html:

<div *ngIf="friend" class="friend-card"></div>

Структурная директива *ngIf проверяет существование переменной friend, той самой, для которой мы задавали @Input в коде класса листингом выше. Это нужно, т.к. в дальнейшем мы будем отправлять друга на редактирование кликом по его имени в списке. А значит, при загрузке по умолчанию переменная friend не будет задана.

Добавим в этот же файл поля ввода:

<div *ngIf="friend" class="friend-card">

	<div class="friend-card__row">
		<div class="friend-card__cell friend-card__cell_double friend-card__cell_ta-center">
			<h2>{{ friend.name }}</h2>
		</div>
	</div>
	<div class="friend-card__row">
		<div class="friend-card__cell friend-card__cell_width-40 friend-card__cell_ta-right">
			<span>ID:</span>
		</div>
		<div class="friend-card__cell friend-card__cell_width-60">
			<span>{{ friend._id }}</span>
		</div>
	</div>
	<div class="friend-card__row">
		<div class="friend-card__cell friend-card__cell_width-40 friend-card__cell_ta-right">
			<label for="friend-card__name">Имя:</label>
		</div>
		<div class="friend-card__cell friend-card__cell_width-60">
			<input id="friend-card__name" type="text" [(ngModel)]="friend.name" placeholder="Имя друга"/>
		</div>
	</div>

</div>

На данный момент приложение пока выдаёт ошибку, но это мы исправим чуть позже.

Самая и интересная строчка в листинге выше, это <input id="friend-card__name" type="text" [(ngModel)]="friend.name" placeholder="Имя друга"/>. Конструкция [(ngModel)] делает двустороннее привязывание. Или «two-way data binding» в англоязычной документации. Она привязывает текущее input-поле к свойству name объекта friend.

Главному модулю надо рассказать о компоненте и об использовании возможностей модуля форм. Для этого редактируем ./src/app/app.module.ts.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FriendsService } from './friends.service';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms'; // Вот этот модуль позволяет делать двустороннее привязывание

import { AppComponent } from './app.component';
import { FriendDetailComponent } from './friend-detail/friend-detail.component'; // А тут мы добавляем компонент


@NgModule({
  declarations: [
	AppComponent,
	FriendDetailComponent // Декларируем новый компонент в декораторе @NgModule
  ],
  imports: [
	BrowserModule,
	HttpModule,
	FormsModule // Подключаем модуль форм
  ],
  providers: [FriendsService],
  bootstrap: [AppComponent]
})
export class AppModule { }

А сейчас нам нужно реализовать функцию выбора друга и сохранение его в свойстве основного компонента (в AppComponent). Делается это через шаблон основного компонента ./src/app/app.component.html. Внимательно читаем комментарии в листингах.

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="150" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<ul class="friends-list">
	<li *ngFor="let friend of friends"> <!-- Мы берём переменную friend основного компонента -->
		<span (click)="selectFriend(friend)">{{friend.name}}</span> <!-- И через событие клика передаём в метод selectFriend -->
	</li>
</ul>

Событие клика заключено в круглые скобки (click), это называется привязкой события («event binding»). Теперь в основном модуле надо создать метод selectFriend(friend) и свойство selectedFriend. По клику на имени в списке, метод selectFriend() будет передавать друга, по которому произошёл клик, в свойство selectedFriend.

Опишем нехитрый функционал в классе основного компонента (./src/app/app.component.ts):

import { Component, OnInit } from '@angular/core';
import { Friend } from './friend';
import { FriendsService } from './friends.service';


@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
	title = 'app';
	friends: Friend[];
	selectedFriend: Friend; // Сюда будем сохранять друга по которому произошёл клик
	constructor (private friendsService: FriendsService) {
		
	}
	ngOnInit() {
		this.getFriends();
	}
	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {
			this.friends = result;
		});
	}
	selectFriend(friend: Friend):void { // Функция, которая производит сохранение переданной ей переменной типа Friend
		this.selectedFriend = friend;
	}
}

Добавим компонент FriendDetailComponent в AppComponent. Делается это через шаблон основного компонента ./src/app/app.component.html.

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="150" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<ul class="friends-list">
	<li *ngFor="let friend of friends"> <!-- Мы берём переменную friend основного компонента -->
		<span (click)="selectFriend(friend)">{{friend.name}}</span> <!-- И через событие клика передаём в метод selectFriend -->
	</li>
</ul>
<app-friend-detail [friend]="selectedFriend"></app-friend-detail>

Откуда взялся <app-friend-detail>? Селектор компонента, которым он будет вызываться в шаблонах других компонентах, описывает в декораторе @Component в свойстве selector, вы это можете увидеть в файле ./src/app/friend-detail/friend-detail.component.ts.

В селекторе <app-friend-detail> конструкция [friend]="selectedFriend" пробрасывает переменную selectedFriend компонента AppComponent в переменную @Input friend: Friend; компонента FriendDetailComponent. Это называется односторонней привязкой. Если потребуется гуглить в англоязычном сегменте интернета, там оно называется «one-way data binding».

По клику на любом имени в списке, в самом низу появляется форма. Она работает, правда нуждается в стилизации.

Добавим стили компонента в ./src/app/friend-detail/friend-detail.component.css:

.friend-card {
	margin:2px;
	position:relative;
	overflow:hidden;
	padding:0 0 12px 0;
}
.friend-card::before {
	display:block;
	content:"";
	position:absolute;
	top:0;
	right:0;
	bottom:0;
	left:0;
	background:#fff;
	opacity:.4;
	-webkit-box-shadow: 0 3px 15px 0 rgba(0, 0, 0, 0.75);
	-moz-box-shadow:    0 3px 15px 0 rgba(0, 0, 0, 0.75);
	box-shadow:         0 3px 15px 0 rgba(0, 0, 0, 0.75);
	z-index:1;
}
	.friend-card__row {
		position:relative;
		width:100%;
		display:flex;
		justify-content:space-between;
		z-index:2;
	}
		.friend-card__cell {
			padding:4px;
			line-height:36px;
		}
		.friend-card__cell h1,
		.friend-card__cell .h1,
		.friend-card__cell h2,
		.friend-card__cell .h2,
		.friend-card__cell h3,
		.friend-card__cell .h3 {
			margin:6px auto;
		}
		.friend-card__cell_ta-right {
			text-align:right;
		}
		.friend-card__cell_ta-center {
			text-align:center;
		}
			.friend-card__cell_ta-center h1,
			.friend-card__cell_ta-center .h1,
			.friend-card__cell_ta-center h2,
			.friend-card__cell_ta-center .h2,
			.friend-card__cell_ta-center h3,
			.friend-card__cell_ta-center .h3 {
				text-align:center;
			}
		.friend-card__cell_double {
			width:100%;
		}
		.friend-card__cell_width-40 {
			width:40%;
		}
		.friend-card__cell_width-60 {
			width:60%;
		}

Гораздо лучше:

Результирующие листинги изменённых файлов
import { Component, Input } from '@angular/core';
import { Friend } from '../friend';

@Component({
	selector: 'app-friend-detail',
	templateUrl: './friend-detail.component.html',
	styleUrls: ['./friend-detail.component.css']
})
export class FriendDetailComponent {

	@Input() friend: Friend;

	constructor() { }

}

<div *ngIf="friend" class="friend-card">

	<div class="friend-card__row">
		<div class="friend-card__cell friend-card__cell_double friend-card__cell_ta-center">
			<h2>{{ friend.name }}</h2>
		</div>
	</div>
	<div class="friend-card__row">
		<div class="friend-card__cell friend-card__cell_width-40 friend-card__cell_ta-right">
			<span>ID:</span>
		</div>
		<div class="friend-card__cell friend-card__cell_width-60">
			<span>{{ friend._id }}</span>
		</div>
	</div>
	<div class="friend-card__row">
		<div class="friend-card__cell friend-card__cell_width-40 friend-card__cell_ta-right">
			<label for="friend-card__name">Имя:</label>
		</div>
		<div class="friend-card__cell friend-card__cell_width-60">
			<input id="friend-card__name" type="text" [(ngModel)]="friend.name" placeholder="Имя друга"/>
		</div>
	</div>

</div>

.friend-card {
	margin:2px;
	position:relative;
	overflow:hidden;
	padding:0 0 12px 0;
}
.friend-card::before {
	display:block;
	content:"";
	position:absolute;
	top:0;
	right:0;
	bottom:0;
	left:0;
	background:#fff;
	opacity:.4;
	-webkit-box-shadow: 0 3px 15px 0 rgba(0, 0, 0, 0.75);
	-moz-box-shadow:    0 3px 15px 0 rgba(0, 0, 0, 0.75);
	box-shadow:         0 3px 15px 0 rgba(0, 0, 0, 0.75);
	z-index:1;
}
	.friend-card__row {
		position:relative;
		width:100%;
		display:flex;
		justify-content:space-between;
		z-index:2;
	}
		.friend-card__cell {
			padding:4px;
			line-height:36px;
		}
		.friend-card__cell h1,
		.friend-card__cell .h1,
		.friend-card__cell h2,
		.friend-card__cell .h2,
		.friend-card__cell h3,
		.friend-card__cell .h3 {
			margin:6px auto;
		}
		.friend-card__cell_ta-right {
			text-align:right;
		}
		.friend-card__cell_ta-center {
			text-align:center;
		}
			.friend-card__cell_ta-center h1,
			.friend-card__cell_ta-center .h1,
			.friend-card__cell_ta-center h2,
			.friend-card__cell_ta-center .h2,
			.friend-card__cell_ta-center h3,
			.friend-card__cell_ta-center .h3 {
				text-align:center;
			}
		.friend-card__cell_double {
			width:100%;
		}
		.friend-card__cell_width-40 {
			width:40%;
		}
		.friend-card__cell_width-60 {
			width:60%;
		}

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FriendsService } from './friends.service';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { FriendDetailComponent } from './friend-detail/friend-detail.component';


@NgModule({
	declarations: [
		AppComponent,
		FriendDetailComponent
	],
	imports: [
		BrowserModule,
		HttpModule,
		FormsModule
	],
	providers: [FriendsService],
	bootstrap: [AppComponent]
})
export class AppModule { }

import { Component, OnInit } from '@angular/core';
import { Friend } from './friend';
import { FriendsService } from './friends.service';


@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
	title = 'app';
	friends: Friend[];
	selectedFriend: Friend;
	constructor (private friendsService: FriendsService) {
		
	}
	ngOnInit() {
		this.getFriends();
	}
	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {
			this.friends = result;
		});
	}
	selectFriend(friend: Friend):void {
		this.selectedFriend = friend;
	}
}


<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="150" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<ul class="friends-list">
	<li *ngFor="let friend of friends">
		<span (click)="selectFriend(friend)">{{friend.name}}</span>
	</li>
</ul>
<app-friend-detail [friend]="selectedFriend"></app-friend-detail>


Дополнительные ссылки