04. Компонент оценки (звёздочки)

Создание компонента «звёздочки»

Используем командную строку для создания компонента (Angular CLI).

ng generate component stars --module=app

Визуально компонент будет представлять из себя 5 звёзд (иконки Font Awesome), по клику будет записываться в localStorage новое значение для конкретного друга. Запись будет представлять из себя ключ (свойство _id), а значение — число.

В самом компоненте число звёзд для отображения будем хранить в переменной stars. Она будет посредником между визуализацией компонента и локальным хранилищем. Т.к. для каждого друга будет вызываться свой экземляр класса StarsComponent, то переменная stars для каждого друга будет своя и не будет такого, что одному другу ставишь оценку « 4 » и у всех одновременно загорается 4 звезды.

Создадим на основе этого описания несложную визуализацию в HTML-шаблоне (./src/app/stars/stars.component.html):

<span class="stars">
	<i (click)="setStars(1)" class="fa fa-star" [class.gold]="stars >= 1"></i>
	<i (click)="setStars(2)" class="fa fa-star" [class.gold]="stars >= 2"></i>
	<i (click)="setStars(3)" class="fa fa-star" [class.gold]="stars >= 3"></i>
	<i (click)="setStars(4)" class="fa fa-star" [class.gold]="stars >= 4"></i>
	<i (click)="setStars(5)" class="fa fa-star" [class.gold]="stars == 5"></i>
</span>

Здесь мы используем обусловленное переключение класса:

<i (click)="setStars(1)" class="fa fa-star" [class.gold]="stars >= 1"></i>

Фрагмент кода [class.gold]="stars >= 1" сообщает транспилеру, что если условие в кавычках верно (в данном случае, если переменная this.stars больше, либо равна 1), то нужно добавить класс gold. Т.е. сделать звезду золотой.

Стилизуем компонент (./src/app/stars/stars.component.css):

.stars { /* Чтобы часть звёзд при нехватке места на строке не переносилась на другую строку, зададим минимальную ширину */
	display:inline-block;
	min-width:102px;
}
.gold {
	color: #e6c664;
}
.fa {
	position:relative;
}

Логика компонента StarsComponent должна быть такой: принимаем из внешнего компонента _id друга и число звёзд, которое надо поставить, затем, сохраняем в localStorage эти данные и в шаблоне показываем нужное число звёзд. ./src/app/stars/stars.component.ts:

import { Component, OnInit, Input, Inject } from '@angular/core';

@Component({
	selector: 'app-stars',
	templateUrl: './stars.component.html',
	styleUrls: ['./stars.component.css']
})
export class StarsComponent implements OnInit {

	@Input() stars: number; // Переменная содержит число звёзд. Декоратор Input() говорит транспилеру, что эту переменную мы получаем от родителя

	@Input() id: string; // Сюда передаём из внешнего компонента id

	setStars(index: number):void { // этот метод будет срабатывать по клику и устанавливать нужное число звёзд

		this.stars = index;
		localStorage.setItem(this.id + "-stars", index.toString()); // Обратите внимание, в localStorage уже может быть запись с ключом, равным _id друга, поэтому дописываем ключу постфикс "-stars", чтобы не затереть "избранность" и сохраняем туда число звёзд. 

	}

	constructor(
		@Inject('LOCALSTORAGE') private localStorage: any
	) { }

	ngOnInit() {
	}

}

Внедрение компонента

Сначала внедрим компонент оценки в общий список друзей. Для этого открываем шаблон списка (./src/app/friends-list/friends-list.component.html) и редактируем его следующим образом:

<ul class="friends-list">
	<li *ngFor="let friend of friends">
		<span><a routerLink="/detail/{{friend._id}}">{{friend.name}}</a></span>
		<span><app-stars [id]="friend._id"></app-stars></span> <!-- Вот так передаём значение перемннгой из компонента-родителя, её в компоненте StarsComponent примет переменная, которую мы объявляли с декоратором: @Input() id: string; -->
	</li>
</ul>

Сейчас будет отображаться поставленное число звёзд и будет сохраняться в localStorage, но при инициализации списка не будут показываться ранее поставленные звёзды. Для наглядности демонстрирую:

В «боевом» проекте мы создали бы сервис, который подхватывал бы число звёзд из localStorage и по мере надобности импортировали бы его в компоненты, где необходимо отобразить оценку участника. Но пока ограничимся методом в компоненте FriendsListComponent.

Объявим класс, который будет описывать модель соответствия оценок друзьям. Для этого в ./src/app создадим файл friends-stars.ts.

export class FriendsStars {
	id: string;
	stars: number;
}

Создаём метод считывания начальных значений оценок:

import { Component, OnInit } from '@angular/core';
import { Friend } from '../friend';
import { FriendsService } from '../friends.service';
import { TransferVarsService } from '../transfer-vars.service';
import { FriendsStars } from '../friends-stars'; // Импортируем только что созданный класс


@Component({
	selector: 'app-friends-list',
	templateUrl: './friends-list.component.html',
	styleUrls: ['./friends-list.component.css']
})
export class FriendsListComponent implements OnInit {

	title: string = 'Список друзей';

	friends: Friend[];

	stars: Array = []; // Создаём массив, где будем хранить соответствия друг-оценка

	constructor (
		private friendsService: FriendsService,
		private transferVarsService: TransferVarsService
	) {
		
	}
	
	ngOnInit() {
		this.getFriends();
		this.transferVarsService.setTitle(this.title);
	}
	
	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {
			this.friends = result;
			this.friends.forEach((item) => { // Перебираем всех друзей и ищем, у кого из них в локальном хранилище есть оценка
				this.stars.push({id: item._id, stars: this.checkStarsInStorage(item._id)});
			});
		});
	}

	checkStarsInStorage(id: string):number { // Этот метод ищет звёзды в хранилище и возвращает оценку, а если число некорректное, возвращает 0

		let stars: number = 0;
		stars = parseInt(localStorage.getItem(id + "-stars"));
		return ((stars < 6)&&(stars >= 0))? stars : 0;

	}

	getStars(id: string):number { // Этот метод будет вызываться из шаблона и искать в уже сфомированном массиве stars оценку по _id друга

		return this.stars.find(friend => friend.id == id).stars;

	}

}

Возвращаемся к шаблону списка друзей и при инициализации для каждого ищем его оценку (./src/app/friends-list/friends-list.component.html):

<ul class="friends-list">
	<li *ngFor="let friend of friends">
		<span><a routerLink="/detail/{{friend._id}}">{{friend.name}}</a></span>
		<span><app-stars [id]="friend._id" [stars]="getStars(friend._id)"></app-stars></span>
	</li>
</ul>

Результирующие листинги изменённых файлов

<span class="stars">
	<i (click)="setStars(1)" class="fa fa-star" [class.gold]="stars >= 1"></i>
	<i (click)="setStars(2)" class="fa fa-star" [class.gold]="stars >= 2"></i>
	<i (click)="setStars(3)" class="fa fa-star" [class.gold]="stars >= 3"></i>
	<i (click)="setStars(4)" class="fa fa-star" [class.gold]="stars >= 4"></i>
	<i (click)="setStars(5)" class="fa fa-star" [class.gold]="stars == 5"></i>
</span>

.stars {
	display:inline-block;
	min-width:102px;
}
.gold {
	color: #e6c664;
}
.fa {
	position:relative;
}

import { Component, OnInit, Input, Inject } from '@angular/core';

@Component({
	selector: 'app-stars',
	templateUrl: './stars.component.html',
	styleUrls: ['./stars.component.css']
})
export class StarsComponent implements OnInit {

	@Input() stars: number;

	@Input() id: string;

	setStars(index: number):void {

		this.stars = index;
		localStorage.setItem(this.id + "-stars", index.toString());

	}

	constructor(
		@Inject('LOCALSTORAGE') private localStorage: any
	) { }

	ngOnInit() {
	}

}


export class FriendsStars {
	id: string;
	stars: number;
}

<ul class="friends-list">
	<li *ngFor="let friend of friends">
		<span><a routerLink="/detail/{{friend._id}}">{{friend.name}}</a></span>
		<span><app-stars [id]="friend._id" [stars]="getStars(friend._id)"></app-stars></span>
	</li>
</ul>

import { Component, OnInit } from '@angular/core';
import { Friend } from '../friend';
import { FriendsService } from '../friends.service';
import { TransferVarsService } from '../transfer-vars.service';
import { FriendsStars } from '../friends-stars';


@Component({
	selector: 'app-friends-list',
	templateUrl: './friends-list.component.html',
	styleUrls: ['./friends-list.component.css']
})
export class FriendsListComponent implements OnInit {

	title: string = 'Список друзей';

	friends: Friend[];

	stars: Array = [];

	constructor (
		private friendsService: FriendsService,
		private transferVarsService: TransferVarsService
	) {
		
	}
	
	ngOnInit() {
		this.getFriends();
		this.transferVarsService.setTitle(this.title);
	}
	
	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {
			this.friends = result;
			this.friends.forEach((item) => {
				this.stars.push({id: item._id, stars: this.checkStarsInStorage(item._id)});
			});
		});
	}

	checkStarsInStorage(id: string):number {

		let stars: number = 0;
		stars = parseInt(localStorage.getItem(id + "-stars"));
		return ((stars < 6)&&(stars >= 0))? stars : 0;

	}

	getStars(id: string):number {

		return this.stars.find(friend => friend.id == id).stars;

	}

}


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