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

Забираем параметр из URL. Пробрасываем переменную через сервис в другой компонент

Чтобы компонент FriendDetailComponent нормально выводил на редактирование запись из массива friends, нужно предоставить эту запись через сервис. Этот путь вы можете посмотреть в официальной документации. Я же решил сделать так, чтобы был расшарен весь массив Friends. Идентификатор записи будем брать из URL-параметра и по _id будем искать в массиве friends.

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

ng generate service transfer-vars --module=app

Это команда сразу регистрирует наш сервис в ./src/app/app.module.ts. Код внутри сервиса TransferVarsService (./src/app/transfer-vars.service.ts) очень прост:

import { Injectable } from '@angular/core';
import { Friend } from './friend';

@Injectable()
export class TransferVarsService {

	private friends:Friend[] = [];

	public setFriends(friends: Friend[]):void {
		this.friends = friends;
	}

}

Теперь подключим сервис в компоненте FriendDetailComponent. ./src/app/friend-detail/friend-detail.component.ts:

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

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

	id: string;

	friends: Friend[]; // Сюда будем сохранять массив friends, он берётся из сервиса './src/app/friends.service.ts' сервисом './src/app/transfer-vars.service.ts'

	friend: Friend;

	checkReady: number; // Interval проверки существования массива друзей

	getFriends():void { // Забираем массив посредством метода сервиса
		this.friendsService.getFriends().subscribe(result => {this.friends = result;});
	}

	selectFriend(id: string):void { // Собственно поиск в массиве friends по id

		this.friend = this.friends.find(friend => friend._id === id);
		clearInterval(this.checkReady);

	}

	constructor(
		private route: ActivatedRoute, 
		private friendsService: FriendsService,
		private transferVarsService: TransferVarsService
	) { }

	ngOnInit() {

		this.getFriends();

		this.id = this.route.snapshot.paramMap.get('id'); // Забираем параметр из URL

		this.checkReady = setInterval(() => {
			if (this.friends != undefined) {
				clearInterval(this.checkReady);
			}
			this.selectFriend(this.id);
			this.transferVarsService.setFriends(this.friends);
		}, 500);

	}

}


Панель навигации. Изменение заголовка в зависимости от загруженного компонента

В каждом компоненте, который в роутере имеет свой URL, заведём переменную title, которая будет хранить заголовок для корневого компонента.

./src/app/friend-detail/friend-detail.component.ts:

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

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

	id: string;

	friends: Friend[];

	friend: Friend;

	checkReady: number;

	title: string = 'Редактирование';

	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {this.friends = result;});
	}

	selectFriend(id: string):void {

		this.friend = this.friends.find(friend => friend._id === id);
		clearInterval(this.checkReady);

	}

	constructor(
		private route: ActivatedRoute,
		private friendsService: FriendsService,
		private transferVarsService: TransferVarsService
	) { }

	ngOnInit() {

		this.getFriends();

		this.id = this.route.snapshot.paramMap.get('id');

		this.checkReady = setInterval(() => {
			if (this.friends != undefined) {
				clearInterval(this.checkReady);
			}
			this.selectFriend(this.id);
			this.transferVarsService.setFriends(this.friends);
		}, 500);

	}

}


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


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

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

	friends: Friend[];

	constructor (private friendsService: FriendsService) {
		
	}
	ngOnInit() {
		this.getFriends();
	}
	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {
			this.friends = result;
		});
	}

}

Расширим сервис трансфера переменных:

import { Injectable } from '@angular/core';
import { Friend } from './friend';

@Injectable()
export class TransferVarsService {

	private title:string = "";

	private friends:Friend[] = [];

	public setTitle(title: string):void {
		this.title = title;
	}

	public setFriends(friends: Friend[]):void {
		this.friends = friends;
	}

}

Подключим сервис трансфера переменных в корневом компоненте:

import { Component } from '@angular/core';
import { TransferVarsService } from './transfer-vars.service';


@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.css']
})
export class AppComponent {
	
	title:string = 'Менеджер контактов';

	constructor(private transferVarsService: TransferVarsService) {}

}

Изменим шаблон корневого компонента:

<div class="outer">
	<div class="toppanel">
		<a routerLinkActive="active" routerLink="/friends-list" class="button button_round"><i class="fa fa-address-book"></i></a>
	</div>
	<div class="header">
		<div class="header__logo"><img class="logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="></div>
		<div class="header__header"><h1>{{ transferVarsService.title }}</h1></div>
	</div>
	<router-outlet></router-outlet>
</div>

Атрибут routerLinkActive="active" отслеживает, является ли приложение на «странице», прописанной в routerLink в данном элементе, если да, то включается стиль active. Слово «страница» взято в кавычки, т.к. на самом деле технически мы всё время на одной странице находимся.

Добавим стили для корневого компонента:

.outer {
	width:100%;
	height:100%;
	position:relative;
	padding:1px 0;
}
.outer:after {
	display:block;
	content:"";
	clear:both;
}
	.toppanel {
		width:100%;
		height:60px;
		padding:5px;
		position:fixed;
		top:0;
		left:0;
		width:100%;
		display:flex;
		z-index:10;
	}
		.toppanel::before {
			position:absolute;
			display:block;
			content:'';
			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);
		}
		.toppanel > * {
			display:inline-block;
			margin-right:4px;
		}
		.toppanel > *:last-child {
			margin-right:0;
		}
	.header {
		height:140px;
		margin:80px 0 20px 0;
		position:relative;
		display:flex;
		justify-content:space-between;
	}
		.header__logo {
			display:flex;
			justify-content:center;
			align-items:center;
			width:20%;
		}
			.header__logo img {
				display:block;
				max-width:100%;
				margin:auto;
			}
		.header__header {
			width:80%;
			text-align:center;
			display:flex;
			justify-content:center;
			align-items:center;
		}
@media (max-width: 480px) {
	.header {
		height:120px;
	}
}

Допишем глобальные стили:

@import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i&subset=cyrillic');
* {
	-webkit-box-sizing: border-box;
	box-sizing: border-box;
}

html, body {
	margin:0;
	padding:0;
	min-width: 320px;
	height: 100%;
}

body {
	font-size: 18px;
	font-family: Roboto, Calibri, sans-serif;
	background: #182340;
	background: -moz-linear-gradient(45deg, #182340 0%, #3c9ffc 100%);
	background: -webkit-linear-gradient(45deg, #182340 0%,#3c9ffc 100%);
	background: linear-gradient(45deg, #182340 0%,#3c9ffc 100%);
	background-attachment:fixed;
	color:#fff;
}

a:link {color:#fff;}
a:visited {color:#fff;}
a:hover {color:#fff;}
a:active {color:#fff;}

.outer {
	max-width:812px;
	margin:0 auto;
	position:relative;
}

/* Кнопки */
.button {
	padding:14px 10px;
	display:inline-block;
	position:relative;
	text-align:center;
	line-height:22px;
	height:50px;
	cursor:pointer;
}
.button > span {
	position:relative;
	z-index:2;
}
	.button::before {
		position:absolute;
		top:0;
		left:0;
		display:block;
		content:"";
		background:#fff;
		opacity:.2;
		width:100%;
		height:100%;
		z-index:1;
	}
	.button.active {
		color:#4ce4e4;
	}
	.button_round {
		width:50px;
	}
	.button_round:before {
		border-radius:25px;
	}
	.button_low-rounded:before {
		border-radius:12px;
	}
	.button_save {
		border:none;
		color:#fff;
	}
	.button_save:before {
		transition:.3s;
		background:#08d425;
		opacity:.7;
	}
	.button_save:hover:before {
		opacity:1;
	}

/* Список друзей */
ul.friends-list {
	margin:0 0 12px 0;
	padding:0 2px 2px 2px;
	list-style-type:none;
}
	ul.friends-list>li {
		overflow:hidden;
		display:flex;
		position:relative;
		justify-content:space-between;
		padding:12px 20px;
		line-height:24px;
		margin:0 0 2px 0;
	}
	ul.friends-list>li:last-child {
		margin-bottom:0;
	}
	ul.friends-list>li.selected {
		color:#24bbbb;
	}
	ul.friends-list>li.selected a {
		color:#24bbbb;
	}
	ul.friends-list li:before {
		display:block;
		content:"";
		position:absolute;
		top:0;
		right:0;
		bottom:0;
		left:0;
		background:#fff;
		opacity:.3;
		z-index:1;
	}
	ul.friends-list>li.selected:before {
		opacity:.7;
	}
		ul.friends-list>li span {
			position:relative;
			z-index:2;
		}

При помощи метода setTitle() сервиса трансфера переменных, пробросим заголовки компонентов в корневой компонент.

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


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

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

	friends: Friend[];

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

}

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

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

	id: string;

	friends: Friend[];

	friend: Friend;

	checkReady: number;

	title: string = 'Редактирование';

	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {this.friends = result;});
	}

	selectFriend(id: string):void {

		this.friend = this.friends.find(friend => friend._id === id);
		clearInterval(this.checkReady);

	}

	constructor(
		private route: ActivatedRoute,
		private friendsService: FriendsService,
		private transferVarsService: TransferVarsService
	) { }

	ngOnInit() {

		this.getFriends();

		this.id = this.route.snapshot.paramMap.get('id');

		this.checkReady = setInterval(() => {
			if (this.friends != undefined) {
				clearInterval(this.checkReady);
			}
			this.selectFriend(this.id);
			this.transferVarsService.setFriends(this.friends);
		}, 500);

		this.transferVarsService.setTitle(this.title);

	}

}


У нас появилась фиксированная панель сверху и кнопка возврата на главную на ней. Однако, в конце первого урока мы говорили о том, что подключать асинхронные данные посредством счётчика не comme il faut, поэтому код нуждается в некотором рефакторинге. На практике мы делаем так:

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

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

	id: string;

	friends: Friend[];

	friend: Friend;

	title: string = 'Редактирование';

	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {
			this.friends = result;
			this.id = this.route.snapshot.paramMap.get('id'); // Переносим из ngOnInit, т.к. нам нужно знать id друга перед тем, как мы будем его искать в общем списке
			this.selectFriend(this.id); // а вот теперь мы ищем по id
			//this.transferVarsService.setFriends(this.friends); // В данной строке сейчас смысла нет, но вы с её помощью можете попытаться сделать проверку наличия переменной в сервисе, если она там есть, то брать оттуда, а если нет, то запрашивать из файла. Таким образом вы сделаете мемоизацию данных, или кэширование.
		});
	}

	selectFriend(id: string):void {

		this.friend = this.friends.find(friend => friend._id === id);

	}

	constructor(
		private route: ActivatedRoute,
		private friendsService: FriendsService,
		private transferVarsService: TransferVarsService
	) { }

	ngOnInit() {

		this.getFriends();

		this.transferVarsService.setTitle(this.title);

	}

}


Если вы внимательно смотрели листинги, то заметили, что шаблон корневого компонента использует font-awesome иконки: <i class="fa fa-address-book"></i>.

Подключение сторонних стилей и библиотек в Angular 2/5

Давайте для начала установим font-awesome:

Для npm:

npm install font-awesome

Для yarn:

yarn add font-awesome

Теперь откроем ./.angular-cli.json и в блоке "styles" добавим в массив путь до стилей font-awesome:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "event-platform"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.css",
		"../node_modules/font-awesome/css/font-awesome.min.css"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "src/tsconfig.spec.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "e2e/tsconfig.e2e.json",
      "exclude": "**/node_modules/**"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }
}


Результат урока:

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


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

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

	friends: Friend[];

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

}

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

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

	id: string;

	friends: Friend[];

	friend: Friend;

	title: string = 'Редактирование';

	getFriends():void {
		this.friendsService.getFriends().subscribe(result => {
			this.friends = result;
			this.id = this.route.snapshot.paramMap.get('id');
			this.selectFriend(this.id);
		});
	}

	selectFriend(id: string):void {

		this.friend = this.friends.find(friend => friend._id === id);

	}

	constructor(
		private route: ActivatedRoute,
		private friendsService: FriendsService,
		private transferVarsService: TransferVarsService
	) { }

	ngOnInit() {

		this.getFriends();

		this.transferVarsService.setTitle(this.title);

	}

}

import { Injectable } from '@angular/core';
import { Friend } from './friend';

@Injectable()
export class TransferVarsService {

	private title:string = "";

	private friends:Friend[] = [];

	public setTitle(title: string):void {
		this.title = title;
	}

	public setFriends(friends: Friend[]):void {
		this.friends = friends;
	}

}


import { Component } from '@angular/core';
import { TransferVarsService } from './transfer-vars.service';


@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.css']
})
export class AppComponent {
	
	title:string = 'Менеджер контактов';

	constructor(private transferVarsService: TransferVarsService) {}

}


<div class="outer">
	<div class="toppanel">
		<a routerLinkActive="active" routerLink="/friends-list" class="button button_round"><i class="fa fa-address-book"></i></a>
	</div>
	<div class="header">
		<div class="header__logo"><img class="logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="></div>
		<div class="header__header"><h1>{{ transferVarsService.title }}</h1></div>
	</div>
	<router-outlet></router-outlet>
</div>

.outer {
	width:100%;
	height:100%;
	position:relative;
	padding:1px 0;
}
.outer:after {
	display:block;
	content:"";
	clear:both;
}
	.toppanel {
		width:100%;
		height:60px;
		padding:5px;
		position:fixed;
		top:0;
		left:0;
		width:100%;
		display:flex;
		z-index:10;
	}
		.toppanel::before {
			position:absolute;
			display:block;
			content:'';
			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);
		}
		.toppanel > * {
			display:inline-block;
			margin-right:4px;
		}
		.toppanel > *:last-child {
			margin-right:0;
		}
	.header {
		height:140px;
		margin:80px 0 20px 0;
		position:relative;
		display:flex;
		justify-content:space-between;
	}
		.header__logo {
			display:flex;
			justify-content:center;
			align-items:center;
			width:20%;
		}
			.header__logo img {
				display:block;
				max-width:100%;
				margin:auto;
			}
		.header__header {
			width:80%;
			text-align:center;
			display:flex;
			justify-content:center;
			align-items:center;
		}
@media (max-width: 480px) {
	.header {
		height:120px;
	}
}

@import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i&subset=cyrillic');
* {
	-webkit-box-sizing: border-box;
	box-sizing: border-box;
}

html, body {
	margin:0;
	padding:0;
	min-width: 320px;
	height: 100%;
}

body {
	font-size: 18px;
	font-family: Roboto, Calibri, sans-serif;
	background: #182340;
	background: -moz-linear-gradient(45deg, #182340 0%, #3c9ffc 100%);
	background: -webkit-linear-gradient(45deg, #182340 0%,#3c9ffc 100%);
	background: linear-gradient(45deg, #182340 0%,#3c9ffc 100%);
	background-attachment:fixed;
	color:#fff;
}

a:link {color:#fff;}
a:visited {color:#fff;}
a:hover {color:#fff;}
a:active {color:#fff;}

.outer {
	max-width:812px;
	margin:0 auto;
	position:relative;
}

/* Кнопки */
.button {
	padding:14px 10px;
	display:inline-block;
	position:relative;
	text-align:center;
	line-height:22px;
	height:50px;
	cursor:pointer;
}
.button > span {
	position:relative;
	z-index:2;
}
	.button::before {
		position:absolute;
		top:0;
		left:0;
		display:block;
		content:"";
		background:#fff;
		opacity:.2;
		width:100%;
		height:100%;
		z-index:1;
	}
	.button.active {
		color:#4ce4e4;
	}
	.button_round {
		width:50px;
	}
	.button_round:before {
		border-radius:25px;
	}
	.button_low-rounded:before {
		border-radius:12px;
	}
	.button_save {
		border:none;
		color:#fff;
	}
	.button_save:before {
		transition:.3s;
		background:#08d425;
		opacity:.7;
	}
	.button_save:hover:before {
		opacity:1;
	}

/* Список друзей */
ul.friends-list {
	margin:0 0 12px 0;
	padding:0 2px 2px 2px;
	list-style-type:none;
}
	ul.friends-list>li {
		overflow:hidden;
		display:flex;
		position:relative;
		justify-content:space-between;
		padding:12px 20px;
		line-height:24px;
		margin:0 0 2px 0;
	}
	ul.friends-list>li:last-child {
		margin-bottom:0;
	}
	ul.friends-list>li.selected {
		color:#24bbbb;
	}
	ul.friends-list>li.selected a {
		color:#24bbbb;
	}
	ul.friends-list li:before {
		display:block;
		content:"";
		position:absolute;
		top:0;
		right:0;
		bottom:0;
		left:0;
		background:#fff;
		opacity:.3;
		z-index:1;
	}
	ul.friends-list>li.selected:before {
		opacity:.7;
	}
		ul.friends-list>li span {
			position:relative;
			z-index:2;
		}

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "event-platform"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.css",
		"../node_modules/font-awesome/css/font-awesome.min.css"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "src/tsconfig.spec.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "e2e/tsconfig.e2e.json",
      "exclude": "**/node_modules/**"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }
}


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';
import { FriendsListComponent } from './friends-list/friends-list.component';
import { AppRoutingModule } from './app-routing.module';
import { TransferVarsService } from './transfer-vars.service';


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



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