In this article, I will explain Angular 2 Routes in a simple way using example. Components contains UI code and their respective logic. We can tell angular to load particular component on particular URL. Read below example

Example

Observe below diagram. 
  1. Home page shows 3 links ( /students, /teachers, /admin)
  2. admin shows admin component
  3. students page shows list of students with their id as URL
  4. teachers page shows list of teachers with their id as URL

Now we will implement this with angular 2

Project Setup

Read this article on Angular 2 project setup - http://blog.sodhanalibrary.com/2016/09/angular2-project-setup-for-development.html.  Here we will add all app source files to app folder. Observe below screenshot.


Download project

You can download the project from github ( https://github.com/SodhanaLibrary/angular2-routes-example ).  

Admin Flow

Lets start with admin flow. Create admin component like below in app folder.

admin.component.ts

import { Component } from '@angular/core';
@Component({
  selector: 'admin',
  template: '<h1>Admin component</h1>'
})
export class AdminComponent { }
Now we are ready with admin component.  Now we have to define route for this

app.routing.ts

This file contains all routing info of the app. Lets add routing info for admin component.
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AdminComponent } from './admin/admin.component';
import { HomeComponent } from './home.component';

const appRoutes: Routes = [
  {
    path: 'admin',
    component: AdminComponent
  },
  {
    path: '',
    component: HomeComponent
  },
  {
    path: '**',
    component: HomeComponent
  }
];

export const appRoutingProviders: any[] = [

];

export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
Here you can observe the HomeComponent, this is the page which show up to user on home page load.

home.component.ts

This page basically contains all navigation links
import { Component } from '@angular/core';
@Component({
  template: `<nav>
      <a routerLink="/students" routerLinkActive="active">Students</a>
      <a routerLink="/teachers" routerLinkActive="active">Teachers</a>
      <a routerLink="/admin" routerLinkActive="active">Admin</a>
    </nav>`
})
export class HomeComponent { }

app.component.ts

Now we need to loads this components in router-outlet directive.  router-outlet only supports the components specified in routs. Here we add router-outlet
import { Component } from '@angular/core';
@Component({
  selector: 'my-app',
  template: `<router-outlet></router-outlet>`
})
export class AppComponent { }
Now AppComponent is starting component for application. we have to bootstrap this.

app.module.ts

Here we will define app module with all components and routing information. Observe below code.
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { routing,
         appRoutingProviders }  from './app.routing';

import { AdminComponent } from './admin/admin.component';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';

@NgModule({
  imports:      [ BrowserModule, routing ],
  declarations: [ AppComponent, AdminComponent, HomeComponent],
  bootstrap:    [ AppComponent ],
  providers:    [ appRoutingProviders ]
})
export class AppModule { }
AppComponent, AdminComponent, HomeComponent were added declarations. AppComponent is the starting component of the application, so we added this to bootstrap. routing information given to imports.

main.ts

Now we successfully created module with routing information. Its time to give this module to browser. 
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Students Flow

You have seen simple admin flow. Now I will explain the way we can add children to Routes.  This routing information will be in app.routing.ts
{
    path: 'students',
    component: StudentsComponent,
    children: [
      {
        path:'',
        component : StudentListComponent
      },
      {
        path: ':id',
        component : StudentComponent
      }
    ]
}
Here StudentsComponent have only router-outlet directive. This directive loads StudentListComponent which is having of list students. When user clicks on those links,  StudentComponent will display specific student information

Teachers Flow

We can create simply another module like app module and can be imported into app module. Here I have created entire teacher module separately and imported to app.module.ts.

teachers.module.ts

import { NgModule }      from '@angular/core';
import { routing,
         appRoutingProviders }  from './teachers.routing';

import { TeachersComponent } from './teachers.component';
import { TeacherComponent } from './teacher.component';
import { TeacherListComponent } from './teacherList.component';

@NgModule({
  imports:      [ routing ],
  declarations: [ TeachersComponent, TeacherComponent, TeacherListComponent ],
  providers:    [ appRoutingProviders ]
})
export class TeachersModule { }
It is the same way the app module was defined
Read More
Angular2 supports Typescript, Of course Typescript has advantages for better coding. Here I am not going discuss advantages of Typescript.  Lets create basic Angular2 project for both development and production.

Project structure 

Observe below diagram. 
  • app folder - this contains Typescript files for app functionality. Here main.ts is the starting probe of our app
  • css folder - contains styles required for app
  • dist folder - contains production version of the project (generated from development files)
  • production folder - contains production suitable HTML and TS files 
  • index.html - HTML file used in development mode
  • package.json - contains meta data and dependencies
  • systemjs.config.js - SystemJS configuration file
  • tsconfig.json - Typescript configuration file
  • typings.json - Typings dependencies

Download the project

I have uploaded this project to github. You can download it from here

by using git :

git clone https://github.com/SodhanaLibrary/angular2-prod-dev-setup.git

Install dependencies :

npm install
This will install all dependencies including typings

Run this project :

npm start
Above command will transpile Typescript files to JS files and run the lite-server. Now you can see the localhost with angular2.

Generate production version :

npm run build_prod
Above command will create dist folder with all its dependencies and css files.  Now just open index.html of dist folder in browser, You can your angular app running

Index.html - development VS production

index.html code of development and production is different. Observe below code.

Development index.html

Here we need to have systemjs configuration file. SystemJS will load our main angular application based on the configuration
<html>
  <head>
    <title>Angular QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">
    <!-- 1. Load libraries -->
     <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>
  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

Production index.html

Here you don't need to import system js configuration file, you will directly import bundle.min.js file. This bundle js file contains all javascript code including libraries. It will be generated by browserify while generating production version. 
<html>
  <head>
    <title>Angular QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="css/styles.css">
    <!-- 1. Load libraries -->
     <!-- Polyfill(s) for older browsers -->
    <script src="dependencies/shim.min.js"></script>
    <script src="dependencies/zone.js"></script>
    <script src="dependencies/Reflect.js"></script>
    <script src="dependencies/system.src.js"></script>

  </head>
  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
    <script src="bundle.min.js"></script>
  </body>
</html>

Main.ts - development vs production

Maint.ts is the app starting point. development version is in app folder, production version is in production folder.

development main.ts

AppModule is the starting point of the app
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Production main.ts

Here enableProdMode is called. Which will turn off assertions and checks, and improves performance. 
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from '../app/app.module';
import {enableProdMode} from '@angular/core';

enableProdMode();
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Package.json

Finally package.json which will have all commands, metadata and dependencies. Find below for commands
    {
    "clean": "rm -rf dist && mkdir dist && mkdir dist/dependencies",
    "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
    "lite": "lite-server",
    "postinstall": "typings install",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "typings": "typings",
    "build": "npm run clean && tsc",
    "minify": "uglifyjs dist/bundle.js --screw-ie8 --compress --mangle --output dist/bundle.min.js",
    "build_prod": "npm run build && browserify -s main production/main.prod.js > dist/bundle.js && npm run minify && cp -R css dist/css && cp production/index.html dist/ && npm run build_prod_depds",
    "build_prod_depds": "cp node_modules/core-js/client/shim.min.js dist/dependencies/ && cp node_modules/zone.js/dist/zone.js dist/dependencies/ && cp node_modules/reflect-metadata/Reflect.js dist/dependencies/ && cp node_modules/systemjs/dist/system.src.js dist/dependencies/ && cp systemjs.config.js dist/"
    }
Read More
Mouser wheel actions can be detected using onWheel property. Here we will use deltaY property of onWheel event to find direction mouse wheel.

To Run Example Code

execute below commands
npm install
npm start
now open index.html in browser

Program

Observe below program. Whenever user scroll mouse wheel on image, detect scroll direction and increase or decrease width of image based on that

import React from 'react';
import classNames from 'classnames';

class ZoomImage extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      width:this.props.width
    };
    this.hadleMouseWheel = this.hadleMouseWheel.bind(this);
  }

  hadleMouseWheel(evt) {
    if(evt.deltaY > 0) {
        this.setState({
        width:(this.state.width - 5)
      });
    } else if(evt.deltaY < 0) {
      this.setState({
        width:(this.state.width + 5)
      });
    }
  }

  render() {
    const imgStyle = {
      width:this.state.width+'px'
    };
    const {width, ...props} = this.props;
    return (
      <img {...props} style={imgStyle} onWheel={this.hadleMouseWheel}/>
    );
  }
}

ZoomImage.propTypes = {
  width:React.PropTypes.number
}

ZoomImage.defaultProps = {
  width:400
}

export default ZoomImage;
Read More
We can control checking and unchecking of radio buttons and checkboxes using React state. Here we have to bind state variable to checked attribute of radio button or checkbox.

Program

Observe this code. Here we have bound the state variable checked to checked attribute of  radio button or checkbox.
import React from 'react';
import classNames from 'classnames';

class CheckAndUncheck extends React.Component {

  constructor(props) {
    super(props);
    this.checkIt = this.checkIt.bind(this);
    this.unCheckIt = this.unCheckIt.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      checked:false
    };
  }

  checkIt() {
    this.setState({
      checked:true
    });
  }

  unCheckIt() {
    this.setState({
      checked:false
    });
  }

  handleChange(evt) {
     if(this.state.checked !== evt.target.checked) {
        this.setState({
          checked:evt.target.checked
        });
     }
  }

  render() {
    return (
      <div>
        <div>
          <button onClick={this.checkIt}>Check</button> &nbsp;&nbsp;&nbsp; <button onClick={this.unCheckIt}>Uncheck</button>
        </div>
        <br/>
        <div>
          Checkbox :: <input type="checkbox" checked={this.state.checked} onChange={this.handleChange}/>
        </div>
        <br/>
        <div>
          Radio button :: <input type="radio" checked={this.state.checked} onChange={this.handleChange}/>
        </div>
      </div>
    );
  }
}

CheckAndUncheck.propTypes = {
}

CheckAndUncheck.defaultProps = {
}

export default CheckAndUncheck;
Read More
We can control scroll position of IFrame if the source of IFrame is from same domain. We can get reference of element using ref. Observe below program
Note : This program won't work for cross domains for security reasons. 

To Run Example Code

execute below commands
npm install
npm start
now open index.html in browser

Program

Observe below program. we can use contentWindow.scrollTo to scroll IFrame
import React from 'react';
import classNames from 'classnames';

class ScrollDetector extends React.Component {

  constructor(props) {
    super(props);
    this.scrollDown = this.scrollDown.bind(this);
    this.scrollUp = this.scrollUp.bind(this);
    this.scrollPos = {
       xcoord:0,
       ycoord:0
    };
  }

  scrollDown() {
    this.scrollPos = {
       xcoord: Math.min(this.scrollPos.xcoord + 5, this.frame.contentWindow.document.body.offsetHeight),
       ycoord: Math.min(this.scrollPos.ycoord + 5, this.frame.contentWindow.document.body.offsetHeight)
    };
    this.frame.contentWindow.scrollTo(this.scrollPos.xcoord,this.scrollPos.ycoord);
  }

  scrollUp() {
    this.scrollPos = {
       xcoord: Math.max(this.scrollPos.xcoord - 5, 0),
       ycoord: Math.max(this.scrollPos.ycoord - 5, 0)
    };
    this.frame.contentWindow.scrollTo(this.scrollPos.xcoord,this.scrollPos.ycoord);
  }

  render() {
    return (
      <div>
        <button onClick={this.scrollDown}>Scroll down</button>&nbsp;&nbsp;&nbsp;<button onClick={this.scrollUp}>Scroll up</button><br/><br/>
        <iframe ref={(c)=>{this.frame = c}} src="frame.html"></iframe>
      </div>
    );
  }
}

ScrollDetector.propTypes = {
}

ScrollDetector.defaultProps = {
}

export default ScrollDetector;
Read More
In some cases, System has to provide suggestions to user while user typing. Sending request for each and every request to server will be overhead. So sending request to server when user finishes typing will give good performance. We can detect whether user typing or finished his typing by delaying the onChange event.

To Run Example Code

execute below commands
npm install
npm start
now open index.html in browser

Program

Observer below code. Here we are using setTimeout for giving delays
import React from 'react';

class TypeDetector extends React.Component {

  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      value:''
    }
  }

  handleChange(evt) {
    if(this._timeout){ //if there is already a timeout in process cancel it
        clearTimeout(this._timeout);
    }
    const val = evt.target.value;
    this._timeout = setTimeout(()=>{
       this._timeout = null;
       this.setState({
          value:val
       });
    },1000);
  }

  render() {
    return (
      <div>
        <div>
         Value :: <b>{this.state.value}</b><br/><br/>

        </div>
        <input style={{width:'300px', padding:'5px'}} placeholder="Enter text here" onChange={this.handleChange}/>
      </div>
    );
  }
}

TypeDetector.propTypes = {
}

TypeDetector.defaultProps = {
}

export default TypeDetector;
Read More
Whenever user scroll to the bottom, we can show Go to top button to make user reached to top of the page. This program will help you to find out bottom of the page.

To Run Example Code

execute below commands
npm install
npm start
now open index.html in browser

Program Flow

  1. Bind scroll event to window
  2. Calculate whole document height
  3. Get height of the window
  4. Calculate bottom of window
  5. If window bottom is greater than or equal to document height then user reached to bottom

ReactJS Component

import React from 'react';
import classNames from 'classnames';

class ScrollDetector extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      message:'not at bottom'
    };
    this.handleScroll = this.handleScroll.bind(this);
  }

  handleScroll() {
    const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
    const body = document.body;
    const html = document.documentElement;
    const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight,  html.scrollHeight, html.offsetHeight);
    const windowBottom = windowHeight + window.pageYOffset;
    if (windowBottom >= docHeight) {
      this.setState({
        message:'bottom reached'
      });
    } else {
      this.setState({
        message:'not at bottom'
      });
    }
  }

  componentDidMount() {
    window.addEventListener("scroll", this.handleScroll);
  }

  componentWillUnmount() {
    window.removeEventListener("scroll", this.handleScroll);
  }

  render() {
    return (
      <div>
        <div className="fixedDiv">{this.state.message}</div>
        <div className="scrollDiv"></div>
      </div>
    );
  }
}

ScrollDetector.propTypes = {
}

ScrollDetector.defaultProps = {
}

export default ScrollDetector;
Read More
Sometimes we need to pass some actions to parent component. Lets take menu as example. If user clicks on menu item, that menu item style should change, other menu items styles should reset to normal. How will you create this?, we need to attach callback to child components from parent component dynamically. Lets do this

To Run Example Code

execute below commands
npm install
npm start
now open index.html in browser

Markup

Lets take a simple markup of menu. Observe below code. If user clicks on li element, then background color of that li item should be green and other li items background color should be normal grey color. Lets build our components
<ul>
  <li>List Item 1</li>
  <li>List Item 2</li>
  <li>List Item 3</li>
  <li>List Item 4</li>
  <li>List Item 5</li>
</ul>

ChildComponent.js

When user clicks on list item, parent callback function will be called with keyIndex
import React from 'react';
import classNames from 'classnames';

class ChildComponent extends React.Component {

  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.props.handleClick(this.props.keyIndex);
  }

  render() {
    return (
      <li className={classNames({'active':(this.props.selectedIndex === this.props.keyIndex)})} onClick={this.handleClick}>{this.props.children}</li>
    );
  }
}

ChildComponent.propTypes = {
   keyIndex:React.PropTypes.number
}

ChildComponent.defaultProps = {
}

export default ChildComponent;
This component creates li item.  

ParentComponent.js

This component creates ul item. Here in render function, We are cloning li items and attaching handleClick, keyIndex, selectedIndex  properties. Whenever user clicks on li item, handleClick callback will be called with keyIndex. Based on selectedIndex and keyIndex, background color of li item will change
import React from 'react';
import classNames from 'classnames';
import ValidComponentChildren from './ValidComponentChildren';

class ParentComponent extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      selectedIndex:-1
    }
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(keyIndex) {
    this.setState({
      selectedIndex:keyIndex
    });
  }

  render() {
    let keyIndexVal = 0;
    const items = ValidComponentChildren.map(this.props.children, child => {
      const childProps = child.props || {};
      keyIndexVal++;
      return React.cloneElement(child, {
        handleClick: this.handleClick,
        keyIndex:keyIndexVal,
        selectedIndex:this.state.selectedIndex
      }, childProps.children);
    });

    return (
      <ul>
        {items}
      </ul>
    );
   }
}

ParentComponent.propTypes = {
}

ParentComponent.defaultProps = {
}

export default ParentComponent;
Read More

Blogroll

Follow this blog by Email

Popular Posts