Thursday, September 8, 2016

23. Building Single Page Apps

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




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

    }

}

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.

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 { }

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