← back to the blog


Angular2 Master/Detail Page Example

Posted in Angular2, TypeScript by dake

This blog demo how to build angualr2 master/detail page, following is the template page for now.

Anything under here will code which make above things happen.

 

package.json

{
  "name": "news-app2",
  "version": "0.0.0",
  "license": "MIT",
  "angular-cli": {},
  "scripts": {
    "start": "ng server",
    "postinstall": "typings install",
    "lint": "tslint \"src/**/*.ts\"",
    "format": "clang-format -i -style=file --glob=src/**/*.ts",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "2.0.0-rc.4",
    "@angular/compiler": "2.0.0-rc.4",
    "@angular/core": "2.0.0-rc.4",
    "@angular/forms": "^0.2.0",
    "@angular/http": "2.0.0-rc.4",
    "@angular/platform-browser": "2.0.0-rc.4",
    "@angular/platform-browser-dynamic": "2.0.0-rc.4",
    "@angular/router": "3.0.0-beta.2",
    "es6-shim": "^0.35.0",
    "ng2-bootstrap": "^1.0.23",
    "reflect-metadata": "0.1.3",
    "rxjs": "5.0.0-beta.6",
    "systemjs": "0.19.26",
    "zone.js": "^0.6.12"
  },
  "devDependencies": {
    "angular-cli": "0.0.*",
    "clang-format": "^1.0.35",
    "codelyzer": "0.0.14",
    "ember-cli-inject-live-reload": "^1.4.0",
    "jasmine-core": "^2.4.1",
    "jasmine-spec-reporter": "^2.4.0",
    "karma": "^0.13.15",
    "karma-chrome-launcher": "^0.2.3",
    "karma-jasmine": "^0.3.8",
    "protractor": "^3.3.0",
    "ts-node": "^0.5.5",
    "tslint": "^3.6.0",
    "typescript": "^1.8.10",
    "typings": "^0.8.1"
  }
}

main.ts

import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { NewsApp2AppComponent, environment } from './app/';
import {provideRouter, RouterConfig} from '@angular/router';

import {LocationStrategy, HashLocationStrategy, PathLocationStrategy} from '@angular/common';
import {NewsDisplayComponent, NewsDetailComponent} from './app/news-display/';
import {HTTP_PROVIDERS} from '@angular/http';

if (environment.production) {
  enableProdMode();
}

const routes: RouterConfig = [
  { path: '', redirectTo: 'news', pathMatch: 'full' },
  { path: 'news', component: NewsDisplayComponent },
  { path: 'detail/:id', component: NewsDetailComponent },
];

bootstrap(NewsApp2AppComponent, [
  provideRouter(routes),
  HTTP_PROVIDERS,
  { 
    provide: LocationStrategy,
    useClass: PathLocationStrategy 
  }
])
.catch(error => {
  console.error(error);
});

news-app2.component.ts

import { Component } from '@angular/core';
import { ROUTER_DIRECTIVES }  from '@angular/router';

@Component({
  moduleId: module.id,
  selector: 'news-app2-app',
  templateUrl: 'news-app2.component.html',
  styleUrls: ['news-app2.component.css'],
  directives: [ROUTER_DIRECTIVES]
})

export class NewsApp2AppComponent {
  title = 'TECH News App';
  constructor() {} 
}

news-app2.component.html

<h1>
  {{title}}
</h1>

<router-outlet></router-outlet>

shortenHtml.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'shortenHtml'
})
export class ShortenHtmlPipe implements PipeTransform {

    transform(value: string, args: number, endString: string) {
        let length = args || 100;
        let _endString = endString || "...";
        if (args !== undefined) {
            length = args;
        }
        var parser = new DOMParser(),
        doc = parser.parseFromString(value, "text/html");

        if (Array.from(doc.body.childNodes).some(node => node.nodeType === 1)) {
            // is html
            value = doc.getElementsByTagName("p")[0].textContent;
        }

        let resValue;

        if (value.length > length) {
            resValue = value.substring(0, args);
        }
        else {
            resValue = value;
        }

        return resValue + _endString;
    }
}

news.json

[
    {
    "Id": "aba3702c-2a8c-460e-898b-002389ecdd14",
    "Title": "20 top examples of JavaScript",
    "SlugTitle": "20-top-examples-of-javascript",
    "Link": "https://javascript.com/news/20-top-examples-of-javascript-vybb-er2e",
    "Description": "JavaScript creates platforms that can engage a user and ensure that they remember your site and continue to revisit. It can be used to create games, APIs, scrolling abilities and much more.",
    "PublishDate": "0001-01-01T00:00:00",
    "Tags": [
      "Javascript"
    ],
    "Category": "Javascript"
  },
  {
    "Id": "0bc93851-7464-44cb-8aba-016f1c7eca7c",
    "Title": "Diagnostics",
    "SlugTitle": "diagnostics",
    "Link": "http://docs.asp.net/en/latest/fundamentals/diagnostics.html",
    "Description": "   Steve Smith    ASP.NET Core includes a number of new features that can assist with diagnosing problems.     Sections      The developer error page     The welcome page     Glimpse     Logging      ...",
    "PublishDate": "2016-06-01T00:00:00",
    "Tags": [
      "ASP.NET Core",
      "ASP.NET"
    ],
    "Category": "ASP.NET"
  },
  {
    "Id": "ee6f4d70-d47c-4f69-a613-021b0d13da4b",
    "Title": "Angular 2 with ES6: How to Set It Up",
    "SlugTitle": "angular-2-with-es6-how-to-set-it-up",
    "Link": "https://javascript.com/news/angular-2-with-es6-how-to-set-it-up-eyautd2me",
    "Description": "Want to start playing with Angular 2, but don't know where to begin? So many new libraries and build tools! Don't worry. We're going to take the easy route, and get a skeleton app up in about 2 minutes.",
    "PublishDate": "0001-01-01T00:00:00",
    "Tags": [
      "Javascript",
      "ES6",
      "Angular2"
    ],
    "Category": "Javascript"
  },
  {
    "Id": "dc97108d-a9b7-4952-a0c3-02a732853d2a",
    "Title": "JSConf 2015 Notes",
    "SlugTitle": "jsconf-2015-notes",
    "Link": "https://javascript.com/news/jsconf-2015-notes-ejue6dbs",
    "Description": "Donato Borrello has published his notes from JSConf 2015 for each session he has attended. If you'd like to see an overview of some interesting talks, check them out.",
    "PublishDate": "0001-01-01T00:00:00",
    "Tags": [
      "Javascript",
      "JSConf 2015"
    ],
    "Category": "Javascript"
  },
  {
    "Id": "c98c445d-be18-48ff-a011-02c72099330b",
    "Title": "How to Learn ES6",
    "SlugTitle": "how-to-learn-es6",
    "Link": "https://javascript.com/news/how-to-learn-es6-vjpsytjol",
    "Description": "We ran a survey at about the time the standard became official in June 2015 to see how many people were using ES6. Nearly half of respondents already were. ES6 includes lots of great enhancements that will make you & your team more effective.\n\nIf you haven’t learned ES6 yet, the time is now.",
    "PublishDate": "0001-01-01T00:00:00",
    "Tags": [
      "ES6"
    ],
    "Category": "Javascript"
  },
  {
    "Id": "8e13f87d-c47e-4ac7-a78a-02df59bb4cfe",
    "Title": "Building a Full Featured Translation Service Using ES6",
    "SlugTitle": "building-a-full-featured-translation-service-using-es6",
    "Link": "https://javascript.com/news/building-a-full-featured-translation-service-using-es6-4yhvpzgal",
    "Description": "Step by step tutorial of building a translation module that supports new languages fetching, string interpolation and pluralization (using ES6).",
    "PublishDate": "0001-01-01T00:00:00",
    "Tags": [
      "ES6"
    ],
    "Category": "Javascript"
  },
  {
    "Id": "3e0e8d16-5a5c-4928-9edc-03363476d805",
    "Title": "Working with Multiple Environments",
    "SlugTitle": "working-with-multiple-environments",
    "Link": "http://docs.asp.net/en/latest/fundamentals/environments.html",
    "Description": "   ASP.NET Core introduces improved support for controlling application behavior across multiple environments, such as development, staging, and production. Environment variables are used to indicate ...",
    "PublishDate": "2016-05-16T00:00:00",
    "Tags": [
      "ASP.NET",
      "ASP.NET Core"
    ],
    "Category": "ASP.NET"
  },
  {
    "Id": "885a2736-fdba-4a69-8bce-03ca3e8cca30",
    "Title": "Gentest - Generative testing for JavaScript",
    "SlugTitle": "gentest-generative-testing-for-javascript",
    "Link": "https://javascript.com/news/gentest-generative-testing-for-javascript-eyge8cqs",
    "Description": "Generative testing for JavaScript. Save time and catch more bugs by letting the computer write test cases for you. WIP",
    "PublishDate": "0001-01-01T00:00:00",
    "Tags": [],
    "Category": "Javascript"
  },
  {
    "Id": "960420d2-7eba-4459-a94f-04941482828a",
    "Title": ".NET Core 1.0 RC2 - Upgrading from previous versions",
    "SlugTitle": "net-core-10-rc2-upgrading-from-previous-versions",
    "Link": "http://feeds.hanselman.com/~/154919056/0/scotthanselman~NET-Core-RC-Upgrading-from-previous-versions.aspx",
    "Description": "<div><p><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~dot.net\"><img title=\".NET Core at http://dot.net\" style=\"float: right; margin: 0px 0px 0px 5px; display: inline\" alt=\".NET Core at http://dot.net\" src=\"http://www.hanselman.com/blog/content/binary/Windows-Live-Writer/5fd0ab1c3362_AAC8/image_81788b76-8729-4954-9565-f30e4612cd2c.png\" width=\"500\" align=\"right\" height=\"170\"></a>.NET Core RC2 is out, it's open source, and it's on multiple platforms. I'm particularly proud of the cool vanity domain we got for it. <a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~dot.net\">http://dot.net</a>. ;) It makes me smile.</p> <p>Here's the important blog posts to check out:</p> <ul> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://na01.safelinks.protection.outlook.com/?url=https%3a%2f%2fblogs.msdn.microsoft.com%2fdotnet%2f2016%2f05%2f16%2fannouncing-net-core-rc2%2f&data=01%7c01%7cDaniel.Roth%40microsoft.com%7ca3021be681b349d8cd2f08d37dbb24f8%7c72f988bf86f141af91ab2d7cd011db47%7c1&sdata=sfjYD2gdccE1SNZVCUK1zQcU%2fBv%2bnLFBBkn7WlhRslA%3d\">Announcing .NET Core RC2 and .NET Core SDK Preview 1</a></li> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://na01.safelinks.protection.outlook.com/?url=https%3a%2f%2fblogs.msdn.microsoft.com%2fwebdev%2f2016%2f05%2f16%2fannouncing-asp-net-core-rc2%2f&data=01%7c01%7cDaniel.Roth%40microsoft.com%7ca3021be681b349d8cd2f08d37dbb24f8%7c72f988bf86f141af91ab2d7cd011db47%7c1&sdata=IrfXwCuiE8mWZaWLHaXc%2fTPzYlijGmsYjVKTJl%2fbKYw%3d\">Announcing ASP.NET Core RC2</a></li> <ul> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://na01.safelinks.protection.outlook.com/?url=https%3a%2f%2fblogs.msdn.microsoft.com%2fvisualstudio%2f2016%2f05%2f16%2fannouncing-updated-web-development-tools-for-asp-net-core-rc2%2f&data=01%7c01%7cDaniel.Roth%40microsoft.com%7ca3021be681b349d8cd2f08d37dbb24f8%7c72f988bf86f141af91ab2d7cd011db47%7c1&sdata=UlQGNrdiT2FtooN9kSnkgScf3ZU8LqwzBH28o1M%2bhMY%3d\">Announcing Updated Web Development Tools for ASP.NET Core RC2</a></li><!--EndFragment--></ul> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-entity-framework-core-rc2/\">Announcing Entity Framework Core RC2</a></li> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~Announcing Updated Web Development Tools for ASP.NET Core RC2\">Docker and .NET Core RC2</a></li> <ul> <ul> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://visualstudiogallery.msdn.microsoft.com/0f5b2caa-ea00-41c8-b8a2-058c7da0b3e4\">Visual Studio Tools for Docker</a></li></ul></ul></ul> <p>Head over to <a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~dot.net\">http://dot.net</a> and check it out. A great aspect of .NET Core is that everything it does is side-by-side. You can work with it without affecting your existing systems. Be sure also explore the <a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://www.microsoft.com/net/download\">complete .NET Downloads Page</a> for all the manual downloads as well as SHA hashes</p> <blockquote style=\"margin-right: 0px\" dir=\"ltr\"> <p>The best way to develop with .NET Core on Windows is to download the <a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://go.microsoft.com/fwlink/?LinkId=798481\">Visual Studio official MSI Installer</a> and the latest <a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://dist.nuget.org/visualstudio-2015-vsix/v3.5.0-beta/NuGet.Tools.vsix\">NuGet Manager extension for Visual Studio</a>. If you don't have Visual Studio already, you can download <a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://www.visualstudio.com/products/visual-studio-community-vs\">Visual Studio Community 2015</a> for free. </p></blockquote> <p>We'll have documentation and insights on how to moving from ASP.NET 4.x over to ASP.NET Core 1.0 soon, but for now I've collected these resources for folks who are upgrading from previous versions of .NET Core and ASP.NET Core (the framework formerly new as ASP.NET 5).</p> <ul> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~dotnet.github.io/docs/core-concepts/dnx-migration.html\">Migrating from DNX to .NET Core</a> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://docs.asp.net/en/latest/migration/rc1-to-rc2.html\">Migrating from ASP.NET 5 RC1 to ASP.NET Core 1.0 RC2</a> <li><a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~https://docs.efproject.net/en/latest/miscellaneous/rc1-rc2-upgrade.html\">Migrating your Entity Framework Code from RC1 to RC2</a></li></ul> <p>Enjoy!</p> <hr>  <p><strong>Sponsor:</strong> Build servers are great at compiling code and running tests, but not so great at deployment. When you find yourself knee-deep in custom scripts trying to make your build server do something it wasn't meant to, <a href=\"http://feeds.hanselman.com/~/t/0/0/scotthanselman/~bit.ly/1W4LRAn\">give Octopus Deploy a try</a>. </p>\n<br/><hr/>© 2016 Scott Hanselman. All rights reserved. \n<br/></div><Img align=\"left\" border=\"0\" height=\"1\" width=\"1\" alt=\"\" style=\"border:0;float:left;margin:0;padding:0\" hspace=\"0\" src=\"http://feeds.hanselman.com/~/i/154919056/0/scotthanselman\">\n<div style=\"clear:both;padding-top:0.2em;\"><a title=\"Like on Facebook\" href=\"http://feeds.hanselman.com/_/28/154919056/scotthanselman\"><img height=\"20\" src=\"http://assets.feedblitz.com/i/fblike20.png\" style=\"border:0;margin:0;padding:0;\"></a> <a title=\"Share on Google+\" href=\"http://feeds.hanselman.com/_/30/154919056/scotthanselman\"><img height=\"20\" src=\"http://assets.feedblitz.com/i/googleplus20.png\" style=\"border:0;margin:0;padding:0;\"></a> <a title=\"Tweet This\" href=\"http://feeds.hanselman.com/_/24/154919056/scotthanselman\"><img height=\"20\" src=\"http://assets.feedblitz.com/i/twitter20.png\" style=\"border:0;margin:0;padding:0;\"></a> <a title=\"Subscribe by email\" href=\"http://feeds.hanselman.com/_/19/154919056/scotthanselman\"><img height=\"20\" src=\"http://assets.feedblitz.com/i/email20.png\" style=\"border:0;margin:0;padding:0;\"></a> <a title=\"Subscribe by RSS\" href=\"http://feeds.hanselman.com/_/20/154919056/scotthanselman\"><img height=\"20\" src=\"http://assets.feedblitz.com/i/rss20.png\" style=\"border:0;margin:0;padding:0;\"></a> </div>",
    "PublishDate": "2016-05-19T12:35:48",
    "Tags": [],
    "Category": ""
  }]

newsModel.ts

export class NewsModel {
    Id: string;
    TItle: string;
    SlugTitle: string;
    Link: string;
    Description: string; 
}

news-data-service.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import 'rxjs/add/operator/map'
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
import {NewsModel} from './newsModel';

@Injectable()
export class NewsDataService {

  constructor(private http: Http) {}


  getData(): Observable<NewsModel[]> {
    return this.http.get('./app/dataServices/news.json')
    .map((res: Response) => res.json())
    .do((d: NewsModel) => console.log('test data', d));
  }

  getOneData(id: NewsModel) : Observable<any> {
    return this.http.get('./app/dataServices/news.json')
      .map(res => res.json())
      .map((list: Array<any>) =>{
        let result: NewsModel = new NewsModel();
        if (list) {
          list.forEach(element => {
            if (element.Id === id) {
              result = element;
            }
          });
          return result;
        }
      })
  }
}

news-tags.component.ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'app-news-tags',
    templateUrl: 'news-tags.component.html'
})
export class NewsTagsComponent implements OnInit {
    
    @Input() selectedNews;
    constructor() { }

    ngOnInit() { 

    }

}

news-tags.component.html

<div *ngIf="selectedNews">
    <h6>
        <span class="label label-primary" *ngFor="let tag of selectedNews.Tags">{{tag}}</span> 
    </h6>
</div>

news-summary.component.ts

import { Component, OnInit, Input } from '@angular/core';
import {ShortenHtmlPipe} from '../shared/pipes/ShortenHtml.pipe';

@Component({
    moduleId: module.id,
    selector: 'app-news-summary',
    templateUrl: 'news-summary.component.html',
    pipes: [ShortenHtmlPipe]
})
export class NewsDetailComponent implements OnInit {
    
    @Input() selectedNews;
    constructor() { }

    ngOnInit() { 

    }

}

news-summary.component.html

<div *ngIf="selectedNews">
    <div [innerHtml]="selectedNews.Description | shortenHtml:100"></div>
</div>

news-display.component.ts

import { Component, OnInit } from '@angular/core';

import {NewsDataService} from '../dataServices/news-data-service.service';
import {NewsDetailComponent} from './news-summary.component';
import {NewsTagsComponent} from './news-tags.component';

import {ROUTER_DIRECTIVES} from '@angular/router';
import {Observable} from 'rxjs/Observable';

@Component({
  moduleId: module.id,
  selector: 'app-news-display',
  templateUrl: 'news-display.component.html',
  styleUrls: ['news-display.component.css'],
  providers: [NewsDataService],
  directives: [NewsDetailComponent, NewsTagsComponent, ROUTER_DIRECTIVES]
  
})
export class NewsDisplayComponent implements OnInit {
  techNewsList$: Observable<any[]>;
  selectedNews: {};

  constructor(private newsDataSvc: NewsDataService) {}

  ngOnInit() {
    this.techNewsList$ = this.newsDataSvc.getData();
  }

  selectEvent(news: any) {
    this.selectedNews = news;
  }
}

news-display.component.html

<h1>News List</h1>
<ol>
    <li *ngFor="let tn of techNewsList$ | async" 
    (click)="selectEvent(tn)"
    [class.selected]="tn === selectedNews">
        <a [routerLink]="['/detail', tn.Id]">{{tn.Title}}</a>
    </li>
    
</ol>
<app-news-tags [selectedNews]="selectedNews"></app-news-tags>
<app-news-summary [selectedNews]="selectedNews"></app-news-summary>

news-detail.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {NewsDataService} from '../dataServices/news-data-service.service';

import 'rxjs/add/operator/map'
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
import {NewsModel} from '../dataServices/newsModel';

@Component({
  moduleId: module.id,
  selector: 'app-news-detail',
  templateUrl: 'news-detail.component.html',
  styleUrls: ['news-detail.component.css'],
  providers: [NewsDataService]
})

export class NewsDetailComponent implements OnInit {
  newsModel: NewsModel = new NewsModel();
  constructor(private route: ActivatedRoute,
  private newsDataSvc: NewsDataService) {

  }

  ngOnInit() {
    this.route.params.subscribe(params =>{
      let id = params['id'];
      let news$ = this.newsDataSvc.getOneData(id);
      news$.subscribe(res => {
          this.newsModel = res;
      })
    });
  }

  goBack() {
     window.history.back();
  }

}

news-detail.component.html

<h1>News Detail</h1>
<button (click)="goBack()">Back</button>
<h2>{{newsModel.Category}}</h2>
<dl>
  <dt>Title: </dt>
  <dd>{{newsModel.Title}}</dd>
  <dt>Description: </dt>
  <dd [innerHtml]="newsModel.Description"></dd>
  <dt>Tags:</dt>
  <dd>
    <span *ngFor="let tag of newsModel.Tags">
      {{tag}}
    </span>
  </dd>
</dl>
<button (click)="goBack()">Back</button>

Where to get this code

github - NewsApp_AngularCli