Will explore Routers which are used in building Single Page applications.
The first step to building SPA is to enable routing. THis involves three steps.
1. Need to add a reference to the router script in index.html
This is because routing is not part of the core of the angular script. Thus, need to add it explicitly.
2. Set <base href>
3. Register router providers
in main.ts,
download seep app. angular-RC5-SPA.
import {ROUTER_PROVIDERS} from '@angular/router'
Configuring Routes
ss
Code Code HQ
Thursday, September 8, 2016
Monday, September 5, 2016
Appendix 1 - Modules
Modules
Once app grows, maintenance becomes more importantt and challenging. Better to divide a large app into smaller parts - Each focusing on one specific functionality. In each part, we have a group of classes that are highly related and they work together to fulfil a goal.
like facebook, different funcitonalities, messaging, games, news feed, we have classes highly related yet are different frmo other parts. We call each area a module, component, package.
Module: a block of highly related classes.
Ecah app has at least one module AppModule.
When it grows, can refactor:
AppModule
- Module, users
- Module, posts
- Module
In each class, add a decorator called NgModule
// provide metadata to angular about this module
@NgModule({
imports: [...], // dependencies of this module
declarations: [...], //which classes are part of this module, components, directives, types
exports: [...], // which classes are available to outside modules
providers: [...] // providers for dependency injection
})
export class UsersModule{
}
Need Seed Project (RC5)
Changes:
new file app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { NotFoundComponent } from './not-found.component';
import { MessagesModule } from './messages/messages.module';
import { PhotosModule } from './photos/photos.module';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
MessagesModule,
PhotosModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
main.ts
purely boilerplate food. Doesn't change angular concepts that we have learned.
Working with Modules
old way of implementation
app.component.ts
import {MessagesComponent} from './messages.component';
@Component({
selector: 'my-app',
template: '<messages></messages>',
directives: [ MessagesComponent]
})
export class AppComponent {}
Problem was that we have to always call in directives, MessagesComponent. With modules, we register messages component at module level, and it will be available to components in this module.
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { MessagesModule } from './messages/messages.module';
import { MessagesServices } from './messages/messages.service';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
MessagesComponent
],
providers: [MessagesService],
bootstrap: [ AppComponent ]
})
export class AppModule { }
app.component.ts
import {MessagesComponent} from './messages.component';
@Component({
selector: 'my-app',
template: '<messages></messages>',
directives: [ MessagesComponent],
providers: [ MessagesService ]
})
export class AppComponent {}
messages.component.ts
import { Component } from '@angular/core';
import { MessagesService } from './messages.service';
@Component({
selector: 'messages',
template: `<h1>Messages</h1>
<input [(ngModel)]="title">
<ul>
<li *ngFor="let m of messages">{{ m }}</li>
</ul>
`
})
export class MessagesComponent {
messages;
title = "New Message"
constructor(service : MessagesService) {
this.messages = service.getMessages();
}
}
import { Component } from '@angular/core';
@Component({
title = "New Message"
constructor(service : MessagesService) {
Once app grows, maintenance becomes more importantt and challenging. Better to divide a large app into smaller parts - Each focusing on one specific functionality. In each part, we have a group of classes that are highly related and they work together to fulfil a goal.
like facebook, different funcitonalities, messaging, games, news feed, we have classes highly related yet are different frmo other parts. We call each area a module, component, package.
Module: a block of highly related classes.
Ecah app has at least one module AppModule.
When it grows, can refactor:
AppModule
- Module, users
- Module, posts
- Module
In each class, add a decorator called NgModule
// provide metadata to angular about this module
@NgModule({
imports: [...], // dependencies of this module
declarations: [...], //which classes are part of this module, components, directives, types
exports: [...], // which classes are available to outside modules
providers: [...] // providers for dependency injection
})
export class UsersModule{
}
Need Seed Project (RC5)
Changes:
new file app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { NotFoundComponent } from './not-found.component';
import { MessagesModule } from './messages/messages.module';
import { PhotosModule } from './photos/photos.module';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
MessagesModule,
PhotosModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
main.ts
purely boilerplate food. Doesn't change angular concepts that we have learned.
Working with Modules
old way of implementation
app.component.ts
import {MessagesComponent} from './messages.component';
@Component({
selector: 'my-app',
template: '<messages></messages>',
directives: [ MessagesComponent]
})
export class AppComponent {}
Problem was that we have to always call in directives, MessagesComponent. With modules, we register messages component at module level, and it will be available to components in this module.
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { MessagesModule } from './messages/messages.module';
import { MessagesServices } from './messages/messages.service';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
MessagesComponent
],
providers: [MessagesService],
bootstrap: [ AppComponent ]
})
export class AppModule { }
app.component.ts
@Component({
selector: 'my-app',
template: '<messages></messages>',
})
export class AppComponent {}
messages.component.ts
import { Component } from '@angular/core';
import { MessagesService } from './messages.service';
@Component({
selector: 'messages',
template: `<h1>Messages</h1>
<input [(ngModel)]="title">
<ul>
<li *ngFor="let m of messages">{{ m }}</li>
</ul>
`
})
export class MessagesComponent {
messages;
title = "New Message"
constructor(service : MessagesService) {
this.messages = service.getMessages();
}
}
Extracting Modules continue from #158
add new folder 'messages'. add new file 'messages.module.ts'
import { NgModule } from '@angular/code';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { MessagesComponent } from './messages/messages.component';
import { MessagesServices } from './messages/messages.service';
@NgModule({
imports: [
FormsModule,
CommonModule
],
declarations: [
MessagesComponent
],
exports: [
MessagesComponent // only export elements used in template <messages></messages>
],
providers: [
MessagesService
]
})
export class MessagesModule{
}
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { MessagesModule } from './messages/messages.module';
import { AppComponent } from './app.component';
import { MessagesComponent } from './messages/messages.component';
import { MessagesServices } from './messages/messages.service';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
FormsModule
MessagesModule
],
declarations: [
AppComponent
MessagesComponent
],
providers: [MessagesService],
bootstrap: [ AppComponent ]
})
export class AppModule { }
After refactoring, notice how our app.module becomes smaller? And it only changes when we add new modules to this app. So messages module can grow on its own, we can add to it, new classes, new components, new pipes, new directives and non of this impact our app.module. This is the benefit of modularity.
add new folder 'messages'. add new file 'messages.module.ts'
import { NgModule } from '@angular/code';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { MessagesComponent } from './messages/messages.component';
import { MessagesServices } from './messages/messages.service';
@NgModule({
imports: [
FormsModule,
CommonModule
],
declarations: [
MessagesComponent
],
exports: [
MessagesComponent // only export elements used in template <messages></messages>
],
providers: [
MessagesService
]
})
export class MessagesModule{
}
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MessagesModule } from './messages/messages.module';
import { AppComponent } from './app.component';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
MessagesModule
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
After refactoring, notice how our app.module becomes smaller? And it only changes when we add new modules to this app. So messages module can grow on its own, we can add to it, new classes, new components, new pipes, new directives and non of this impact our app.module. This is the benefit of modularity.
messages.component.ts
import { Component } from '@angular/core';
import { MessagesService } from './messages.service';
@Component({
selector: 'messages',
template: `<h1>Messages</h1>
<input [(ngModel)]="title">
<ul>
<li *ngFor="let m of messages">{{ m }}</li>
</ul>
`
})
export class MessagesComponent {
messages;
title = "New Message"
constructor(service : MessagesService) {
this.messages = service.getMessages();
}
}
Appendix 2 Routing
Basic Routes
To enable routing, make sure that in index.html, you have
<base href="/">
Define route in a separate file.
New file, app.routing.ts
app.routing.ts
import { Router, RouterModule } from '@angular/router';
import {HomeComponent} from './home.component';
import {PhotosComponent} from './photos/photos.component';
import {MessagesComponent} from './messages/messages.component';
import {NotFoundComponent} from './not-found.component';
// how to define route
export const routing = RouterModule.forRoot([
{ path: '', component: HomeComponent},
{ path: 'messages', component: MessagesComponent},
{ path: 'photos', component: PhotosComponent},
{ path: '**', component: NotFoundComponent} // wildcard for any other route
]);
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { NotFoundComponent } from './not-found.component';
import { MessagesModule } from './messages/messages.module';
import { PhotosModule } from './photos/photos.module';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
MessagesModule,
PhotosModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<ul>
<li><a routerLink="">Home</a></li>
<li><a routerLink="messages">Messages</a></li>
<li><a routerLink="photos">Photos</a></li>
</ul>
<router-outlet></router-outlet>
`
})
export class AppComponent {
}
Parameterised Routes
app.routing.ts
import { Router, RouterModule } from '@angular/router';
import {HomeComponent} from './home.component';
import {PhotosComponent} from './photos/photos.component';
import {PhotoDetailsComponent} from './photos/photo-details.component';
import {MessagesComponent} from './messages/messages.component';
import {NotFoundComponent} from './not-found.component';
// how to define route
export const routing = RouterModule.forRoot([
{ path: '', component: HomeComponent},
{ path: 'messages', component: MessagesComponent},
{ path: 'photos/:id', component: PhotoDetailsComponent},
{ path: 'photos', component: PhotosComponent},
{ path: '**', component: NotFoundComponent} // wildcard for any other route
]);
app.component.ts
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'my-app',
template: `
<ul>
<li><a routerLink="">Home</a></li>
<li><a routerLink="messages">Messages</a></li>
<li><a routerLink="photos">Photos</a></li>
<li><a [routerLink]="['photos',1]">Photo Details</a></li>
</ul>
<button (click)="onClick()">Click Me</button>
<router-outlet></router-outlet>
`
})
export class AppComponent {
constructor(private _router: Router){
}
onClick(){
this._router.navigate(['photos',2]);
}
}
photo-details.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
template: `<h1>Photo Details {{ id }}</h1>
`
})
export class PhotoDetailsComponent implements OnInit, OnDestroy{
id;
subscription;
constructor(private _route: ActivatedRoute){
}
ngOnInit(){
this.subscription = this._route.params.subscribe(params => {
this.id = params["id"];
});
}
ngOnDestroy(){
this.subscription.unsubscribe();
}
}
Feature Module Routes
Move routes in each feature area to its corresponding module. We have two modules now, messages, and photos.
In photos folder, add a new file, photos.routing.ts
import { Router, RouterModule } from '@angular/router';
import {PhotosComponent} from './photos.component';
import {PhotoDetailsComponent} from './photo-details.component';
// this is a child module, We use forRoot only for app.module which is the root of our app
export const photosRouting = RouterModule.forChild([
{ path: 'photos/:id', component: PhotoDetailsComponent},
{ path: 'photos', component: PhotosComponent}
]);
app.routing.ts
import { Router, RouterModule } from '@angular/router';
import {HomeComponent} from './home.component';
import {PhotosComponent} from './photos/photos.component';
import {PhotoDetailsComponent} from './photos/photo-details.component';
import {MessagesComponent} from './messages/messages.component';
import {NotFoundComponent} from './not-found.component';
export const routing = RouterModule.forRoot([
{ path: '', component: HomeComponent},
{ path: 'messages', component: MessagesComponent},
{ path: 'photos/:id', component: PhotoDetailsComponent},
{ path: 'photos', component: PhotosComponent},
{ path: '**', component: NotFoundComponent}
]);
This makes our app routing cleaner and more mainable.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { NotFoundComponent } from './not-found.component';
import { MessagesModule } from './messages/messages.module';
import { PhotosModule } from './photos/photos.module';
import {routing} from './app.routing';
import {photosRouting} from './photos/photos.routing';
@NgModule({
// always import feature module routes before main routes
imports: [
BrowserModule,
MessagesModule,
PhotosModule,
photosRouting,
routing
],
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
To enable routing, make sure that in index.html, you have
<base href="/">
Define route in a separate file.
New file, app.routing.ts
app.routing.ts
import { Router, RouterModule } from '@angular/router';
import {HomeComponent} from './home.component';
import {PhotosComponent} from './photos/photos.component';
import {MessagesComponent} from './messages/messages.component';
import {NotFoundComponent} from './not-found.component';
// how to define route
export const routing = RouterModule.forRoot([
{ path: '', component: HomeComponent},
{ path: 'messages', component: MessagesComponent},
{ path: 'photos', component: PhotosComponent},
{ path: '**', component: NotFoundComponent} // wildcard for any other route
]);
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { NotFoundComponent } from './not-found.component';
import { MessagesModule } from './messages/messages.module';
import { PhotosModule } from './photos/photos.module';
import {routing} from './app.routing';
@NgModule({
imports: [
BrowserModule,
MessagesModule,
PhotosModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<ul>
<li><a routerLink="">Home</a></li>
<li><a routerLink="messages">Messages</a></li>
<li><a routerLink="photos">Photos</a></li>
</ul>
<router-outlet></router-outlet>
`
})
export class AppComponent {
}
Parameterised Routes
app.routing.ts
import { Router, RouterModule } from '@angular/router';
import {HomeComponent} from './home.component';
import {PhotosComponent} from './photos/photos.component';
import {PhotoDetailsComponent} from './photos/photo-details.component';
import {MessagesComponent} from './messages/messages.component';
import {NotFoundComponent} from './not-found.component';
// how to define route
export const routing = RouterModule.forRoot([
{ path: '', component: HomeComponent},
{ path: 'messages', component: MessagesComponent},
{ path: 'photos/:id', component: PhotoDetailsComponent},
{ path: 'photos', component: PhotosComponent},
{ path: '**', component: NotFoundComponent} // wildcard for any other route
]);
app.component.ts
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'my-app',
template: `
<ul>
<li><a routerLink="">Home</a></li>
<li><a routerLink="messages">Messages</a></li>
<li><a routerLink="photos">Photos</a></li>
<li><a [routerLink]="['photos',1]">Photo Details</a></li>
</ul>
<button (click)="onClick()">Click Me</button>
<router-outlet></router-outlet>
`
})
export class AppComponent {
constructor(private _router: Router){
}
onClick(){
this._router.navigate(['photos',2]);
}
}
photo-details.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
template: `<h1>Photo Details {{ id }}</h1>
`
})
export class PhotoDetailsComponent implements OnInit, OnDestroy{
id;
subscription;
constructor(private _route: ActivatedRoute){
}
ngOnInit(){
this.subscription = this._route.params.subscribe(params => {
this.id = params["id"];
});
}
ngOnDestroy(){
this.subscription.unsubscribe();
}
}
Feature Module Routes
Move routes in each feature area to its corresponding module. We have two modules now, messages, and photos.
In photos folder, add a new file, photos.routing.ts
import { Router, RouterModule } from '@angular/router';
import {PhotosComponent} from './photos.component';
import {PhotoDetailsComponent} from './photo-details.component';
// this is a child module, We use forRoot only for app.module which is the root of our app
export const photosRouting = RouterModule.forChild([
{ path: 'photos/:id', component: PhotoDetailsComponent},
{ path: 'photos', component: PhotosComponent}
]);
app.routing.ts
import { Router, RouterModule } from '@angular/router';
import {HomeComponent} from './home.component';
import {MessagesComponent} from './messages/messages.component';
import {NotFoundComponent} from './not-found.component';
export const routing = RouterModule.forRoot([
{ path: '', component: HomeComponent},
{ path: 'messages', component: MessagesComponent},
{ path: '**', component: NotFoundComponent}
]);
This makes our app routing cleaner and more mainable.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { NotFoundComponent } from './not-found.component';
import { MessagesModule } from './messages/messages.module';
import { PhotosModule } from './photos/photos.module';
import {routing} from './app.routing';
import {photosRouting} from './photos/photos.routing';
@NgModule({
// always import feature module routes before main routes
imports: [
BrowserModule,
MessagesModule,
PhotosModule,
photosRouting,
routing
],
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Thursday, September 1, 2016
4. Managing App State with Redux
Reducers
Reducers: is a function that return a piece of the application state. Our application can have many pieces of state, we can have many different reducers.
e.g
list of books -- state, reducer #1
currently selected book -- state reducer #2.
our application state is a plain js object.
// application state = generated by reducers
{
books: [{title:'Harry Potter'},{title:'Javascript'}], // generated by book reducer
activeBook:{title: 'Javascript: The Good parts'} // generated by activeBook reducer
}
reducers produce value of our state. keys can be whatever we want. Reducer is concerned only with value of state.
Will write a function that produce a value of the state. For books state, will produce an array of object.
Create new folder in src, 'reducers'
Create new file reducer_books.js
// other files can import this file and receive the reducer_books (note naming convention).
export default function (){
return [
{title: 'javascript: the good parts'},
{title: 'Harry Potter'},
{title: 'Pokemon'},
{title: 'React with Redux'}
]
}
Next, need to wire up reducer to application. Create new file index.js in reducer folder
import {combineReducers} from 'redux';
import BooksReducer from './reducer_books';
const rootReducer = combineReducers({
books: BooksReducer
});
export default rootReducer;
key is the piece of state. value is reducer itself. When pass object to combineReducers
we tell redux how to create our applicaiton state.
If we ran it now, we have a single piece of state called books and value is the list of book objects.
Containers - connecting Redux to React
create new folder, containers. create new file book_list.js
import React, {Component} from 'react';
export default class BookList extends Component {
renderList(){
return this.props.books.map((book) => {
return (
<li key={book.title} className="list-group-item">{book.title}</li>
);
});
}
render(){
// bootstrap classes
return (
<ul className="list-group col-sm-4">
{this.renderList()}
</ul>
)
}
}
a container is a promoted react component that has a direction connection to a state
managed by redux
React and redux are two separate libraries. Through 'react-redux' library bridges the two.
How to decide which components become containers?
'Booklist' cares about when the list of books changes
'Book details' cares about when the active book changes.
Therefore both components should have direction connection to redux.
Which components should be dumb component?
Implementation of a Container class.
When we forge a component to redux, we change it into a container or a 'smart component'.
app.js
import React, { Component } from 'react';
import BookList from '../containers/book_list.js'
export default class App extends Component {
render() {
return (
<div>
<BookList />
</div>
);
}
}
book_list.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
class BookList extends Component {
renderList(){
return this.props.books.map((book) => {
return (
<li key={book.title} className="list-group-item">{book.title}</li>
);
});
}
render(){
// bootstrap classes
return (
<ul className="list-group col-sm-4">
{this.renderList()}
</ul>
)
}
}
// take application state as argument, list of books, selected book
// if state changes, this container will instantly re-render with new list of books
function mapStateToProps(state){
// whatever is returned will show up as props inside of booklist
return{
books: state.books
};
}
export default connect(mapStateToProps)(BookList);
Containers and Reducers Review
Redux constructs app state, and react provides view to presents the state.
Application state is generated by reducer functions. We created books_reducer. always return an array of books.
combineReducers: add a key to our global application state where key is books and value is bookreducer(array of books).
We created component booklist. Promoted to a container since it has to be aware of state. We promote it by importing connect,
function mapstatetoprops, and export connect.
Actions and Action Creators.
Our books reducer always listen tot the same books. State always the change.
Changing state: action and action creator purpose.
user clicks a button, calls action creator. Action creator returns an action object (action has action type, e.g. book_selected. can also contain data, like title of book.). That action automatically sent to all reducers. Reducers can choose to return a different piece of state depending on what the action is (normally implemented using a switch statement. if action type is book_selected, return action.book (new value of state), or default, don't care about action. return current state.).
Newly returned piece of state is then passed into the app state which is passed into our react app which causes all containers/components to re-render with new props(data).
Binding Action Creators
action creator is just a function that returns an action. An action is just an object that flows through all our reducers. reducers can then use that action to produce a diff value for its particular piece of state.
Click on book and see details of book.
in actions folder, create index.js
// action creator
export function selectBook(book){
console.log('book has been selected', book.title);
}
// let this action creator be wired up to redux, run thru all reducers.
book-list.js
import {selectBook} from '../actions/index';
import {bindActionCreators} from 'redux';
// anything returned from this function will end up as props on the // booklist container
function mapDispatchToProps(dispatch){
// whenever selectBook is called, result should be passed to all our reducers.
return bindActionCreators({selectBook: selectBook},dispatch)
}
// promote booklist from a component to container, need to know new dispatch method selectbook. make it available as a prop.
export default connect(mapStateToProps, mapDispatchToProps)
Creating an Action
in book_list.js, renderList
onClick={() => this.props.selectBook(book)}
//calling action creator
in action/index.js
// action creator
export function selectBook(book){
// need to return an action
// an object with a type property
return {
type: 'BOOK_SELECTED', // always uppercase string
payload: book
};
}
Consuming Actions in Reducers
Create reducer_active_book.js in reducers folder.
// state is not application state but only the state this reducer is responsible for.
e.g.
// if state is undefined, set to null
export default function(state = null, action){
switch(action.type){
case 'BOOK_SELECTED':
return action.payload;
}
return state;
}
in reducers/index.js combineReducer
import {combineReducers} from 'redux';
import BooksReducer from './reducer_books';
import ActiveBook from './reducer_active_book';
const rootReducer = combineReducers({
books: BooksReducer,
activeBook: ActiveBook
});
// action creator
export default rootReducer;
In containers, create book_detail.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class BookDetail extends Component {
render(){
if(!this.props.book){
return (
<div>Select a book to get Started.</div>
);
}
return (
<div>
<h3>Details for:</h3>
<div>{this.props.book.title}</div>
</div>
);
}
}
function mapStateToProps(state){
return {
book: state.activeBook
}
}
export default connect(mapStateToProps)(BookDetail);
In app.js
import React, { Component } from 'react';
import BookList from '../containers/book_list'
import BookDetail from '../containers/book_detail';
export default class App extends Component {
render() {
return (
<div>
<BookList />
<BookDetail />
</div>
);
}
}
Reducers: is a function that return a piece of the application state. Our application can have many pieces of state, we can have many different reducers.
e.g
list of books -- state, reducer #1
currently selected book -- state reducer #2.
our application state is a plain js object.
// application state = generated by reducers
{
books: [{title:'Harry Potter'},{title:'Javascript'}], // generated by book reducer
activeBook:{title: 'Javascript: The Good parts'} // generated by activeBook reducer
}
reducers produce value of our state. keys can be whatever we want. Reducer is concerned only with value of state.
Will write a function that produce a value of the state. For books state, will produce an array of object.
Create new folder in src, 'reducers'
Create new file reducer_books.js
// other files can import this file and receive the reducer_books (note naming convention).
export default function (){
return [
{title: 'javascript: the good parts'},
{title: 'Harry Potter'},
{title: 'Pokemon'},
{title: 'React with Redux'}
]
}
Next, need to wire up reducer to application. Create new file index.js in reducer folder
import {combineReducers} from 'redux';
import BooksReducer from './reducer_books';
const rootReducer = combineReducers({
books: BooksReducer
});
export default rootReducer;
key is the piece of state. value is reducer itself. When pass object to combineReducers
we tell redux how to create our applicaiton state.
If we ran it now, we have a single piece of state called books and value is the list of book objects.
Containers - connecting Redux to React
create new folder, containers. create new file book_list.js
import React, {Component} from 'react';
export default class BookList extends Component {
renderList(){
return this.props.books.map((book) => {
return (
<li key={book.title} className="list-group-item">{book.title}</li>
);
});
}
render(){
// bootstrap classes
return (
<ul className="list-group col-sm-4">
{this.renderList()}
</ul>
)
}
}
a container is a promoted react component that has a direction connection to a state
managed by redux
React and redux are two separate libraries. Through 'react-redux' library bridges the two.
How to decide which components become containers?
'Booklist' cares about when the list of books changes
'Book details' cares about when the active book changes.
Therefore both components should have direction connection to redux.
Which components should be dumb component?
Implementation of a Container class.
When we forge a component to redux, we change it into a container or a 'smart component'.
app.js
import React, { Component } from 'react';
import BookList from '../containers/book_list.js'
export default class App extends Component {
render() {
return (
<div>
<BookList />
</div>
);
}
}
book_list.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
class BookList extends Component {
renderList(){
return this.props.books.map((book) => {
return (
<li key={book.title} className="list-group-item">{book.title}</li>
);
});
}
render(){
// bootstrap classes
return (
<ul className="list-group col-sm-4">
{this.renderList()}
</ul>
)
}
}
// take application state as argument, list of books, selected book
// if state changes, this container will instantly re-render with new list of books
function mapStateToProps(state){
// whatever is returned will show up as props inside of booklist
return{
books: state.books
};
}
export default connect(mapStateToProps)(BookList);
Containers and Reducers Review
Redux constructs app state, and react provides view to presents the state.
Application state is generated by reducer functions. We created books_reducer. always return an array of books.
combineReducers: add a key to our global application state where key is books and value is bookreducer(array of books).
We created component booklist. Promoted to a container since it has to be aware of state. We promote it by importing connect,
function mapstatetoprops, and export connect.
Actions and Action Creators.
Our books reducer always listen tot the same books. State always the change.
Changing state: action and action creator purpose.
user clicks a button, calls action creator. Action creator returns an action object (action has action type, e.g. book_selected. can also contain data, like title of book.). That action automatically sent to all reducers. Reducers can choose to return a different piece of state depending on what the action is (normally implemented using a switch statement. if action type is book_selected, return action.book (new value of state), or default, don't care about action. return current state.).
Newly returned piece of state is then passed into the app state which is passed into our react app which causes all containers/components to re-render with new props(data).
Binding Action Creators
action creator is just a function that returns an action. An action is just an object that flows through all our reducers. reducers can then use that action to produce a diff value for its particular piece of state.
Click on book and see details of book.
in actions folder, create index.js
// action creator
export function selectBook(book){
console.log('book has been selected', book.title);
}
// let this action creator be wired up to redux, run thru all reducers.
book-list.js
import {selectBook} from '../actions/index';
import {bindActionCreators} from 'redux';
// anything returned from this function will end up as props on the // booklist container
function mapDispatchToProps(dispatch){
// whenever selectBook is called, result should be passed to all our reducers.
return bindActionCreators({selectBook: selectBook},dispatch)
}
// promote booklist from a component to container, need to know new dispatch method selectbook. make it available as a prop.
export default connect(mapStateToProps, mapDispatchToProps)
Creating an Action
in book_list.js, renderList
onClick={() => this.props.selectBook(book)}
//calling action creator
in action/index.js
// action creator
export function selectBook(book){
// need to return an action
// an object with a type property
return {
type: 'BOOK_SELECTED', // always uppercase string
payload: book
};
}
Consuming Actions in Reducers
Create reducer_active_book.js in reducers folder.
// state is not application state but only the state this reducer is responsible for.
e.g.
// if state is undefined, set to null
export default function(state = null, action){
switch(action.type){
case 'BOOK_SELECTED':
return action.payload;
}
return state;
}
in reducers/index.js combineReducer
import {combineReducers} from 'redux';
import BooksReducer from './reducer_books';
import ActiveBook from './reducer_active_book';
const rootReducer = combineReducers({
books: BooksReducer,
activeBook: ActiveBook
});
// action creator
export default rootReducer;
In containers, create book_detail.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class BookDetail extends Component {
render(){
if(!this.props.book){
return (
<div>Select a book to get Started.</div>
);
}
return (
<div>
<h3>Details for:</h3>
<div>{this.props.book.title}</div>
</div>
);
}
}
function mapStateToProps(state){
return {
book: state.activeBook
}
}
export default connect(mapStateToProps)(BookDetail);
In app.js
import React, { Component } from 'react';
import BookList from '../containers/book_list'
import BookDetail from '../containers/book_detail';
export default class App extends Component {
render() {
return (
<div>
<BookList />
<BookDetail />
</div>
);
}
}
Monday, August 29, 2016
3. Modeling Application State
Redux, challenging, rapidly evolving ecosystem. relies on packages on the side which changes quickly.
on top of redux, you have whole host of technologies...
need ES6 syntax.
What is redux?
What is it different from backbone, angular?
Redux: predictable state container for JS applications.
Suppose we have an app that displays list of books on the left and details on the main (author, pages, genre).
Consider structure of app. Both on view layer, and on data layer.
Data layer (data contained in the appliation): List of books and currently selected book
View layer: List View, List item, Detail View.
We want to separate view from data. Both come together to make a working, usable app.
Data layer - Redux is the data contained in this application box
View - React - views contained in the application box.
Redux, a state container, a collection of all the data that describes the app. Not only list of books, but also meta level properties, like what is the currently selected book.
React represent views, which translate the app's data as something that can be displayed on the screen and user can interact with.
What's different? Difference is that we centralize all the application data in a single object. Other js libraries always have collection of data. Backbone has collections, flux has different stores...angular..
redux centralize all these data in a central single object which we refer to as the state. Application level state. Global, big object which contain all data of our application.
Example, a counter. Add one button. Remove one button. Label count.
Data contained in app: current count
Views contained in app: current count, plus and minus button.
React in charge of showing the buttons components. Redux keep tracks of current count.
on top of redux, you have whole host of technologies...
need ES6 syntax.
What is redux?
What is it different from backbone, angular?
Redux: predictable state container for JS applications.
Suppose we have an app that displays list of books on the left and details on the main (author, pages, genre).
Consider structure of app. Both on view layer, and on data layer.
Data layer (data contained in the appliation): List of books and currently selected book
View layer: List View, List item, Detail View.
We want to separate view from data. Both come together to make a working, usable app.
Data layer - Redux is the data contained in this application box
View - React - views contained in the application box.
Redux, a state container, a collection of all the data that describes the app. Not only list of books, but also meta level properties, like what is the currently selected book.
React represent views, which translate the app's data as something that can be displayed on the screen and user can interact with.
What's different? Difference is that we centralize all the application data in a single object. Other js libraries always have collection of data. Backbone has collections, flux has different stores...angular..
redux centralize all these data in a central single object which we refer to as the state. Application level state. Global, big object which contain all data of our application.
Example, a counter. Add one button. Remove one button. Label count.
Data contained in app: current count
Views contained in app: current count, plus and minus button.
React in charge of showing the buttons components. Redux keep tracks of current count.
Sunday, August 28, 2016
2. Ajax Requests with React
Youtube Search Response
Need to make a call to youtube api to get information.
Which component should retrieve the list of videos? Downwards data flow: the most parent component should fetch the data.
index is the most parent component we have.
import React from 'react';
import ReactDOM from 'react-dom';
import SearchBar from './components/search_bar';
import YTSearch from 'youtube-api-search';
const API_KEY = 'AIzaSyC2AOEumcukxg2H2E5TOYBfeV5--GWOeyU';
YTSearch({key: API_KEY, term: 'surfboards'},function(data){
// call back function
console.log(data);
// check console.
});
const App = () => {
return (
<div>
<SearchBar />
</div>
);
}
ReactDOM.render(<App />,document.querySelector('.container'));
Refactoring Functional Components to Class Components
const API_KEY = 'AIzaSyC2AOEumcukxg2H2E5TOYBfeV5--GWOeyU';
class App extends Component {
constructor(props){
super(props);
this.state = {videos: []}; // array of videos
YTSearch({key: API_KEY, term: 'surfboards'}, (videos) => {
this.setState({ videos });
// this.setState({videos: videos});
});
}
render(){
return (
<div>
<SearchBar />
</div>
);
}
}
ReactDOM.render(<App />,document.querySelector('.container'));
Props
Will focus on list of videos
video_list.js
import React from 'react';
// add some classnames to add some styling
// add boostrap link in index.html
const VideoList = (props) => {
return (
<ul className="col-md-4 list-group">
{props.videos.length}
</ul>
);
}
export default VideoList;
in index.js
import VideoList from './components/video_list';
// passing props
render(){
return (
<div>
<SearchBar />
<VideoList videos={this.state.videos} />
</div>
);
}
Building Lists with Map
video_list.js
import React from 'react';
import VideoListItem from './video_list_item'
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return <VideoListItem video={video} />
});
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
);
};
export default VideoList;
/*
map example
var array = [1,2,3];
array.map((number) => {return number * 2})
array.map((number) => {return '<div>' + number + '</div>'})
*/
video_list_item.js
import React from 'react'
const VideoListItem = (props) => {
return <li>Video</li>
};
export default VideoListItem;
List item keys
in video_list.js add. Improve search efficiency
return <VideoListItem key={video.etag} video={video} />
Video List Items
video_list_item.js
import React from 'react'
const VideoListItem = ({video}) => {
const imageUrl = video.snippet.thumbnails.default.url;
return (
<li className="list-group-item">
<div className='video-list media'>
<div className="media-left">
<img className="media-object" src={imageUrl}/>
</div>
<div className="media-body">
<div className="media-heading">{video.snippet.title}</div>
</div>
</div>
</li>
)
};
export default VideoListItem;
Detail Component and Template Strings
Video Selection
index.js
class App extends Component {
constructor(props){
super(props);
this.state = {
videos: [],
selectedVideo: null
};
YTSearch({key: API_KEY, term: 'surfboards'}, (videos) => {
// when state is set, render will be called again
this.setState({
videos: videos,
selectedVideo: videos[0]
});
});
}
// passing props
render(){
return (
<div>
<SearchBar />
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo => this.setState({selectedVideo})}
videos={this.state.videos} />
</div>
);
}
}
ReactDOM.render(<App />,document.querySelector('.container'));
define a function that updates app state. takes a video and updates the selected video
pass this as a property 'onVideoSelect' into videolist
video_list.js
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return (
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.etag}
video={video} />
)
});
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
);
};
export default VideoList;
videolist takes the onVideoSelect property, and pass it into videolistitem
video_list_item.js
import React from 'react'
const VideoListItem = ({video, onVideoSelect}) => {
const imageUrl = video.snippet.thumbnails.default.url;
return (
<li onClick={() => onVideoSelect(video)} className="list-group-item">
<div className='video-list media'>
<div className="media-left">
<img className="media-object" src={imageUrl}/>
</div>
<div className="media-body">
<div className="media-heading">{video.snippet.title}</div>
</div>
</div>
</li>
)
};
export default VideoListItem;
videolistitem takes the onVideoSelect property and says,
whenever i am clicked, call that function with the video that i was passed.
Such a call back shouldn't be more than two level deep'
it can get confusing from index.js all the way to video_list_item
Styling with CSS
style.css
.search-bar{
margin: 20px;
text-align: center;
}
.video-item input{
width: 64px;
}
.video-detail .details{
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.list-group-item{
cursor: pointer;
}
.list-group-item:hover{
background-color:#eee;
}
Make sure in search_bar.js, you have
<div className="search-bar">
Searching for videos
Add ability to search for new videos.
class App extends Component {
constructor(props){
super(props);
this.state = {
videos: [],
selectedVideo: null
};
this.videoSearch('surfboards');
}
// passing props
render(){
return (
<div>
<SearchBar onSearchTermChange={(term => this.videoSearch(term))} />
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo => this.setState({selectedVideo})}
videos={this.state.videos} />
</div>
);
}
videoSearch(term){
YTSearch({key: API_KEY, term: term}, (videos) => {
// when state is set, render will be called again
this.setState({
videos: videos,
selectedVideo: videos[0]
});
});
}
}
ReactDOM.render(<App />,document.querySelector('.container'));
Refactored youtube search in its own method, VideoSearch. Passed this method onto SearchBar under the property
onSearchTermChange. So Searchbar has to call props.onSearchTermChange.
, pass in a function, will call videoSearch with term.
When onSearchTermChange is called...
search_bar.js
import React from 'react';
class SearchBar extends React.Component{
constructor(props){
super(props);
this.state = {term: 'Starting Value'};
}
render(){
return (
<div className="search-bar">
<input
value = {this.state.term}
onChange={event => this.onInputChange(event.target.value)}
/>
</div>
)
}
onInputChange(term){
this.setState({term});
// fires off the callback function onsearchtermchange.
this.props.onSearchTermChange(term);
}
}
export default SearchBar;
Throttling Search Term Input
Its now laggy because search is called whenever we type something. UPdates are too often and overwhelming for users. WIll be good if we can throttlng.
1. need to install lodash
in terminal,
npm install --save lodash
2.
import _ from 'lodash';
3.
render(){
const videoSearch = _.debounce((term) => {this.videoSearch(term)},300);
return (
<div>
<SearchBar onSearchTermChange={videoSearch} />
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo => this.setState({selectedVideo})}
videos={this.state.videos} />
</div>
);
}
want to call function once every ... milliseconds
we created a fat arrow function here and passed it to debounce. Debounce takes this inner function and returns a new function
that can only be called once every 300 milliseconds.
We then pass it into SearchBar.
Need to make a call to youtube api to get information.
Which component should retrieve the list of videos? Downwards data flow: the most parent component should fetch the data.
index is the most parent component we have.
import React from 'react';
import ReactDOM from 'react-dom';
import SearchBar from './components/search_bar';
import YTSearch from 'youtube-api-search';
const API_KEY = 'AIzaSyC2AOEumcukxg2H2E5TOYBfeV5--GWOeyU';
YTSearch({key: API_KEY, term: 'surfboards'},function(data){
// call back function
console.log(data);
// check console.
});
const App = () => {
return (
<div>
<SearchBar />
</div>
);
}
ReactDOM.render(<App />,document.querySelector('.container'));
Refactoring Functional Components to Class Components
const API_KEY = 'AIzaSyC2AOEumcukxg2H2E5TOYBfeV5--GWOeyU';
class App extends Component {
constructor(props){
super(props);
this.state = {videos: []}; // array of videos
YTSearch({key: API_KEY, term: 'surfboards'}, (videos) => {
this.setState({ videos });
// this.setState({videos: videos});
});
}
render(){
return (
<div>
<SearchBar />
</div>
);
}
}
ReactDOM.render(<App />,document.querySelector('.container'));
Props
Will focus on list of videos
video_list.js
import React from 'react';
// add some classnames to add some styling
// add boostrap link in index.html
const VideoList = (props) => {
return (
<ul className="col-md-4 list-group">
{props.videos.length}
</ul>
);
}
export default VideoList;
in index.js
import VideoList from './components/video_list';
// passing props
render(){
return (
<div>
<SearchBar />
<VideoList videos={this.state.videos} />
</div>
);
}
Building Lists with Map
video_list.js
import React from 'react';
import VideoListItem from './video_list_item'
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return <VideoListItem video={video} />
});
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
);
};
export default VideoList;
/*
map example
var array = [1,2,3];
array.map((number) => {return number * 2})
array.map((number) => {return '<div>' + number + '</div>'})
*/
video_list_item.js
import React from 'react'
const VideoListItem = (props) => {
return <li>Video</li>
};
export default VideoListItem;
List item keys
in video_list.js add. Improve search efficiency
return <VideoListItem key={video.etag} video={video} />
Video List Items
video_list_item.js
import React from 'react'
const VideoListItem = ({video}) => {
const imageUrl = video.snippet.thumbnails.default.url;
return (
<li className="list-group-item">
<div className='video-list media'>
<div className="media-left">
<img className="media-object" src={imageUrl}/>
</div>
<div className="media-body">
<div className="media-heading">{video.snippet.title}</div>
</div>
</div>
</li>
)
};
export default VideoListItem;
Detail Component and Template Strings
Video Selection
index.js
class App extends Component {
constructor(props){
super(props);
this.state = {
videos: [],
selectedVideo: null
};
YTSearch({key: API_KEY, term: 'surfboards'}, (videos) => {
// when state is set, render will be called again
this.setState({
videos: videos,
selectedVideo: videos[0]
});
});
}
// passing props
render(){
return (
<div>
<SearchBar />
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo => this.setState({selectedVideo})}
videos={this.state.videos} />
</div>
);
}
}
ReactDOM.render(<App />,document.querySelector('.container'));
define a function that updates app state. takes a video and updates the selected video
pass this as a property 'onVideoSelect' into videolist
video_list.js
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return (
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.etag}
video={video} />
)
});
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
);
};
export default VideoList;
videolist takes the onVideoSelect property, and pass it into videolistitem
video_list_item.js
import React from 'react'
const VideoListItem = ({video, onVideoSelect}) => {
const imageUrl = video.snippet.thumbnails.default.url;
return (
<li onClick={() => onVideoSelect(video)} className="list-group-item">
<div className='video-list media'>
<div className="media-left">
<img className="media-object" src={imageUrl}/>
</div>
<div className="media-body">
<div className="media-heading">{video.snippet.title}</div>
</div>
</div>
</li>
)
};
export default VideoListItem;
videolistitem takes the onVideoSelect property and says,
whenever i am clicked, call that function with the video that i was passed.
Such a call back shouldn't be more than two level deep'
it can get confusing from index.js all the way to video_list_item
Styling with CSS
style.css
.search-bar{
margin: 20px;
text-align: center;
}
.video-item input{
width: 64px;
}
.video-detail .details{
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.list-group-item{
cursor: pointer;
}
.list-group-item:hover{
background-color:#eee;
}
Make sure in search_bar.js, you have
<div className="search-bar">
Searching for videos
Add ability to search for new videos.
class App extends Component {
constructor(props){
super(props);
this.state = {
videos: [],
selectedVideo: null
};
this.videoSearch('surfboards');
}
// passing props
render(){
return (
<div>
<SearchBar onSearchTermChange={(term => this.videoSearch(term))} />
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo => this.setState({selectedVideo})}
videos={this.state.videos} />
</div>
);
}
videoSearch(term){
YTSearch({key: API_KEY, term: term}, (videos) => {
// when state is set, render will be called again
this.setState({
videos: videos,
selectedVideo: videos[0]
});
});
}
}
ReactDOM.render(<App />,document.querySelector('.container'));
Refactored youtube search in its own method, VideoSearch. Passed this method onto SearchBar under the property
onSearchTermChange. So Searchbar has to call props.onSearchTermChange.
, pass in a function, will call videoSearch with term.
When onSearchTermChange is called...
search_bar.js
import React from 'react';
class SearchBar extends React.Component{
constructor(props){
super(props);
this.state = {term: 'Starting Value'};
}
render(){
return (
<div className="search-bar">
<input
value = {this.state.term}
onChange={event => this.onInputChange(event.target.value)}
/>
</div>
)
}
onInputChange(term){
this.setState({term});
// fires off the callback function onsearchtermchange.
this.props.onSearchTermChange(term);
}
}
export default SearchBar;
Throttling Search Term Input
Its now laggy because search is called whenever we type something. UPdates are too often and overwhelming for users. WIll be good if we can throttlng.
1. need to install lodash
in terminal,
npm install --save lodash
2.
import _ from 'lodash';
3.
render(){
const videoSearch = _.debounce((term) => {this.videoSearch(term)},300);
return (
<div>
<SearchBar onSearchTermChange={videoSearch} />
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo => this.setState({selectedVideo})}
videos={this.state.videos} />
</div>
);
}
want to call function once every ... milliseconds
we created a fat arrow function here and passed it to debounce. Debounce takes this inner function and returns a new function
that can only be called once every 300 milliseconds.
We then pass it into SearchBar.
Subscribe to:
Comments (Atom)