<!DOCTYPE html>
<html>

<head>
  <title>Angular 2 and Redux Tutorial: Contact List</title>
  <link rel="stylesheet" href="main.css">
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
  <link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
  <script src="https://code.angularjs.org/2.0.0-beta.15/angular2-polyfills.js"></script>
  <script src="https://code.angularjs.org/tools/system.js"></script>
  <script src="https://code.angularjs.org/tools/typescript.js"></script>
  <script src="config.js"></script>
  <script src="https://code.angularjs.org/2.0.0-beta.15/Rx.js"></script>
  <script src="https://code.angularjs.org/2.0.0-beta.15/angular2.js"></script>
  <script src="https://code.angularjs.org/2.0.0-beta.15/http.min.js"></script>

  <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
</head>

<body>
  <contact-list>Loading...</contact-list>
</body>

</html>
### Angular 2, Immutable.js and Redux Tutorial: Contact List

- A simple contact list application built with Angular 2, Immutable.js and Redux.
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  //map tells the System loader where to look for things
  map: {
    app: "./app",
    'redux': 'https://cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux.js',
    'immutable': 'https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    }
  }
});
html, body {
  font-family: "Open Sans", sans-serif;
  margin: 0;
  padding: 0;
  font-size: 1em;
  background: #F4F3F0;
}
import { bootstrap }    from 'angular2/platform/browser';
import { ContactList } from './contact-list';
import { ContactStore } from './contact-store';

bootstrap(ContactList, [ContactStore]);
import { Component, Input, ChangeDetectionStrategy} from 'angular2/core';
import { ContactStore, Contact as ContactModel} from './contact-store';
import { removeContact, starContact } from './actions';

@Component({
  selector: 'contact',
  templateUrl: 'app/contact.html',
  styleUrls: ['app/contact.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})

export default class Contact {
  @Input()
  contact: ContactModel;

  constructor(private store: ContactStore) { }

  removeContact(contact) {
    this.store.dispatch(removeContact(contact.id));
  }
  
  starContact(contact) {
    this.store.dispatch(starContact(contact.id));
  }
}

import Immutable = require('immutable');
import { createStore } from 'redux';
import { IContactAction } from './actions';
import { reducer } from './reducer';

export class Contact {
  id: number;
  name: String;
  star: boolean;
}

export class ContactStore {
  store = createStore(reducer, Immutable.List<Contact>());

  get contacts(): Immutable.List<Contact> {
    return this.store.getState();
  }

  dispatch(action: IContactAction) {
    this.store.dispatch(action);
  }
}
<div class="contact-container">
	<i class="fa fa-user fa-2x contact-icon"></i>
	<div class="contact-info">
		<h3 class="heading--name">{{ contact.name }}</h3>
		<div class="contact-item"><i class="fa fa-phone"></i> 647-XXX-XXXX</div>
	</div>
	<button class="contact-action" (click)="starContact(contact)">
		<i class="fa fa-2x" [class.fa-star]="contact.star" [class.fa-star-o]="!contact.star"></i>
	</button>
	<button class="contact-action" (click)="removeContact(contact)">
		<i class="fa fa-trash fa-2x"></i>
	</button>
</div>
.contact-action {
  color: #3F51B5;
  margin-right: 6px;
  background-color: #fff;
  border-radius: 5px;
  font-size: 0.8em;
}

.contact-action:hover {
  cursor: pointer;
  margin-top: 2px;
}

.contact-action:focus {
  outline: none;
}

.contact-list {
  margin: 0;
  padding: 0;
}

.contact-list li {
  display: flex;
  align-items: center;
  color: #919191;
  padding: 10px;
  min-height: 60px;
}

.contact-icon {
  border-radius: 50%;
  float: left;
  margin-left: 10px;
  width: 10%;
  margin-right: 10px;
}

.heading--name {
  color: #444;
  padding: 0;
  margin-top: 0;
  margin-bottom: 0;
}

.contact-info {
  width: 80%;
}

.contact-item {
  display: inline-block;
  font-size: 0.7em;
  padding-top: 5px;
}

.contact-item .fa {
  font-size: 1.3em;
  margin-right: 2px;
  margin-left: 2px;
}

.contact-container {
  display: flex;
  align-items: center;
  color: #919191;
  padding: 10px;
  min-height: 60px;

}
import { Component } from 'angular2/core';
import { ContactStore } from './contact-store';
import Contact from './contact';
import { addContact } from './actions';

@Component({
  selector: 'contact-list',
  templateUrl: 'app/contact-list.html',
  styleUrls: ['app/contact-list.css'],
  directives: [Contact]
})

export class ContactList {
	contactID: number;

	constructor(private store: ContactStore) {
		this.contactID = 0;
	}

  addContact(contact) {
    this.store.dispatch(addContact(contact, this.contactID++));
  }
}
<div id="container">
	<header>
		<div class="user">
			<img src="https://hdjirdeh.github.io/public/me.jpg">
		</div>

		<div class="heading-user">
			<strong>Houssein Djirdeh</strong>
			<br/>
			<small>@hdjirdeh</small>
		</div>
	</header>

	<input #newContact class="add-contact" placeholder="Add Contact"
	      (keyup.enter)="addContact(newContact.value); newContact.value='' ">
	<ul class="contact-list">
	  <li *ngFor="#contact of store.contacts">
	  	<contact [contact]="contact"></contact>
	  </li>
	</ul>
</div>
header {
  background-color: #3F51B5;
  color: #fff;
  padding: 20px;
  box-shadow: 0 2px 2px -2px #444;
}

#container {
  width: 420px;
  display: block;
  position: relative;
  margin: 200px auto;
  background: #fff;
  box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);
  overflow: hidden;
  border-radius: 10px;
}

.user {
  display: inline-block;
  float: left;
  margin-right: 15px;
  width: 10%;
}

.user img {
  width: 40px;
  border-radius: 50%;
  border: #fff 2px solid;
}

.add-contact {
  border: 1px solid #f1f1f1;
  height: 55px;
  padding: 5px;
  padding-left: 25px;
  display: block;
  width: 100%;
  font-size: 1em;
  color: #444;
}

.add-contact:focus {
  outline: none;
}

.contact-list {
  margin: 0;
  padding: 0;
}

.contact-list li {
  display: block;
}
import { Contact as ContactModel} from './contact-store';

export interface IContactAction {
  type: string;
  id: number;
  name?: string;
}

export function addContact(name: string, id: number): IContactAction {
  return {
    type: 'ADD',
    id,
    name
  };
}

export function removeContact(id: number): IContactAction {
  return {
    type: 'REMOVE',
    id
  };
}

export function starContact(id: number): IContactAction {
  return {
    type: 'STAR',
    id
  };
}
import Immutable = require('immutable');
import { IContactAction } from './actions';
import { Contact as ContactModel} from './contact-store';

export function reducer(state: Immutable.List<ContactModel> = Immutable.List<ContactModel>(), action: IContactAction) {
  switch (action.type) {
    case 'ADD':
      return state.push({
        id: action.id,
        name: action.name,
        star: false
      });
    case 'REMOVE':
      return state.delete(findIndexById());
    case 'STAR':
      return (<any>state).update(findIndexById(), (contact) => {
        return {
          id: contact.id,
          name: contact.name,
          star: !contact.star
        };
      });
    default:
      return state;
  }

  function findIndexById() {
    return state.findIndex((contact) => contact.id === action.id);
  }
}