<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MyApp</title>
<!--<base href="/">-->
<script>document.write('<base href="' + document.location + '" />');</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('main.js').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<app-root></app-root>
</body>
</html>
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
/*
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
h1 {
font-size: 1.2em;
color: #999;
margin-bottom: 0;
}
h2 {
font-size: 2em;
margin-top: 0;
padding-top: 0;
}
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">ダッシュボード</a>
<a routerLink="/heroines">ヒロインたち</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
import {Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'ヒロイン一覧';
}
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpClientModule} from '@angular/common/http';
import {HttpClientInMemoryWebApiModule} from 'angular-in-memory-web-api';
import {InMemoryDataService} from './in-memory-data.service';
import {AppComponent} from './app.component';
import {HeroinesComponent} from './heroines/heroines.component';
import {HeroineDetailComponent} from './heroine-detail/heroine-detail.component';
import {HeroineService} from './heroine.service';
import {MessagesComponent} from './messages/messages.component';
import {MessageService} from './message.service';
import {AppRoutingModule} from './app-routing.module';
import {DashboardComponent} from './dashboard/dashboard.component';
import {HeroineSearchComponent} from './heroine-search/heroine-search.component';
@NgModule({
declarations: [
AppComponent,
HeroinesComponent,
HeroineDetailComponent,
MessagesComponent,
DashboardComponent,
HeroineSearchComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, {dataEncapsulation: false}
)
],
providers: [HeroineService, MessageService],
bootstrap: [AppComponent]
})
export class AppModule {}
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
module.exports.translate = function(load){
if (load.source.indexOf('moduleId') != -1) return load;
var url = document.createElement('a');
url.href = load.address;
var basePathParts = url.pathname.split('/');
basePathParts.pop();
var basePath = basePathParts.join('/');
var baseHref = document.createElement('a');
baseHref.href = this.baseURL;
baseHref = baseHref.pathname;
if (!baseHref.startsWith('/base/')) { // it is not karma
basePath = basePath.replace(baseHref, '');
}
load.source = load.source
.replace(templateUrlRegex, function(match, quote, url){
var resolvedUrl = url;
if (url.startsWith('.')) {
resolvedUrl = basePath + url.substr(1);
}
return 'templateUrl: "' + resolvedUrl + '"';
})
.replace(stylesRegex, function(match, relativeUrls) {
var urls = [];
while ((match = stringRegex.exec(relativeUrls)) !== null) {
if (match[2].startsWith('.')) {
urls.push('"' + basePath + match[2].substr(1) + '"');
} else {
urls.push('"' + match[2] + '"');
}
}
return "styleUrls: [" + urls.join(', ') + "]";
});
return load;
};
/**
* WEB ANGULAR VERSION
* (based on systemjs.config.js in angular.io)
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
// Copy of compiler options in standard tsconfig.json
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
'app': 'app',
// angular bundles
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
// other libraries
'rxjs': 'npm:rxjs@5.5.2',
'rxjs/operators': 'npm:rxjs@5.5.2/operators/index.js',
'tslib': 'npm:tslib/tslib.js',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
'typescript': 'npm:typescript@2.4.2/lib/typescript.js',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts',
meta: {
'./*.ts': {
loader: 'systemjs-angular-loader.js'
}
}
},
rxjs: {
defaultExtension: 'js'
}
}
});
})(this);
/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
import {Component, OnInit} from '@angular/core';
import {Heroine} from '../heroine';
import {HeroineService} from '../heroine.service';
@Component({
selector: 'app-heroines',
templateUrl: './heroines.component.html',
styleUrls: ['./heroines.component.css']
})
export class HeroinesComponent implements OnInit {
heroines: Heroine[];
constructor(private heroineService: HeroineService) {}
ngOnInit() {
this.getHeroines();
}
getHeroines(): void {
this.heroineService.getHeroines()
.subscribe(heroines => this.heroines = heroines);
}
add(name: string): void {
name = name.trim();
if (!name) {return;}
// this.heroineService.addHeroine({name} as Heroine)
this.heroineService.addHeroine({name} as Heroine, this.heroines.length)
.subscribe(heroine =>
this.heroines.push(heroine)
);
}
delete(heroine: Heroine): void {
this.heroines = this.heroines.filter(_heroine => _heroine !== heroine);
this.heroineService.deleteHeroine(heroine).subscribe();
}
}
<h2>ヒロインリスト</h2>
<div>
<label>名前:
<input #heroineName />
</label>
<button (click)="add(heroineName.value); heroineName.value=''">
追加
</button>
</div>
<ul class="heroines">
<li *ngFor="let heroine of heroines">
<a routerLink="/detail/{{heroine.id}}">
<span class="badge">{{heroine.id}}</span> {{heroine.name}}
</a>
<button class="delete" title="データを削除"
(click)="delete(heroine)">x</button>
</li>
</ul>
export class Heroine {
id: number;
name: string;
}
/* Application-wide Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: #888;
font-family: Cambria, Georgia;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
.heroines {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroines li {
position: relative;
cursor: pointer;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroines li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroines a {
color: #888;
text-decoration: none;
position: relative;
display: block;
width: 250px;
}
.heroines a:hover {
color:#607D8B;
}
.heroines .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
min-width: 16px;
text-align: right;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
button:hover {
background-color: #cfd8dc;
}
button.delete {
position: relative;
left: 194px;
top: -32px;
background-color: gray !important;
color: white;
}
import {InMemoryDbService} from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const heroines = [
{id: 11, name: 'シータ'},
{id: 12, name: 'ナウシカ'},
{id: 13, name: 'キキ'},
{id: 14, name: '千尋'},
{id: 15, name: 'さつき'},
{id: 16, name: 'ソフィー'},
{id: 17, name: 'マーニー'},
{id: 18, name: '菜穂子'},
{id: 19, name: 'サン'},
{id: 20, name: 'フィオ'}
];
return {heroines};
}
}
<div *ngIf="heroine">
<h2>{{heroine.name}}の情報</h2>
<div><span>番号: </span>{{heroine.id}}</div>
<div>
<label>名前:
<input [(ngModel)]="heroine.name" placeholder="名前"/>
</label>
</div>
<button (click)="goBack()">戻る</button>
<button (click)="save()">保存</button>
</div>
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Location} from '@angular/common';
import {Heroine} from '../heroine';
import {HeroineService} from '../heroine.service';
@Component({
selector: 'app-heroine-detail',
templateUrl: './heroine-detail.component.html',
styleUrls: ['./heroine-detail.component.css']
})
export class HeroineDetailComponent implements OnInit {
heroine:Heroine;
constructor(
private route:ActivatedRoute,
private heroineService:HeroineService,
private location:Location
) {}
ngOnInit(): void {
this.getHeroine();
}
getHeroine(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroineService.getHeroine(id)
.subscribe(heroine => this.heroine = heroine);
}
goBack(): void {
this.location.back();
}
save(): void {
this.heroineService.updateHeroine(this.heroine)
.subscribe(() => this.goBack());
}
}
label {
display: inline-block;
width: 3em;
margin: .5em 0;
color: #607D8B;
font-weight: bold;
}
input {
height: 2em;
font-size: 1em;
padding-left: .4em;
}
button {
margin-top: 20px;
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer; cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #ccc;
cursor: auto;
}
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {of} from 'rxjs/observable/of';
import {catchError, map, tap} from 'rxjs/operators';
import {Heroine} from './heroine';
import {MessageService} from './message.service';
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
@Injectable()
export class HeroineService {
private heroinesUrl = 'api/heroines';
constructor(
private http: HttpClient,
private messageService: MessageService) {}
getHeroines(): Observable<Heroine[]> {
return this.http.get<Heroine[]>(this.heroinesUrl)
.pipe(
tap(heroines => this.log('データを取得')),
catchError(this.handleError('getHeroines', []))
);
}
getHeroine(id: number): Observable<Heroine> {
const url = `${this.heroinesUrl}/${id}`;
return this.http.get<Heroine>(url)
.pipe(
tap(_ => this.log(`番号${id}のデータを取得`)),
catchError(this.handleError<Heroine>(`getHeroine 番号=${id}`))
);
}
searchHeroines(term: string): Observable<Heroine[]> {
if (!term.trim()) {
return of([]);
}
const url = `${this.heroinesUrl}/?name=${term}`;
return this.http.get<Heroine[]>(url)
.pipe(
tap(_ => this.log(`「${term}」のデータを検索`)),
catchError(this.handleError<Heroine[]>('searchHeroines', []))
);
}
addHeroine(heroine: Heroine, numHeroines: number): Observable<Heroine> {
let heroineOvserbable;
if (numHeroines === 0) {
heroine.id = 11;
heroineOvserbable = this.http.put(this.heroinesUrl, heroine, httpOptions)
} else {
heroineOvserbable = this.http.post<Heroine>(this.heroinesUrl, heroine, httpOptions);
}
return heroineOvserbable
.pipe(
tap((heroine: Heroine) => this.log(`番号${heroine.id}にデータを追加`)),
catchError(this.handleError<Heroine>('addHeroine'))
);
}
deleteHeroine (heroine: Heroine | number): Observable<Heroine> {
const id = typeof heroine === 'number' ? heroine : heroine.id;
const url = `${this.heroinesUrl}/${id}`;
return this.http.delete<Heroine>(url, httpOptions)
.pipe(
tap(_ => this.log(`番号${id}のデータを削除`)),
catchError(this.handleError<Heroine>('deleteHeroine'))
);
}
updateHeroine(heroine: Heroine): Observable<any> {
return this.http.put(this.heroinesUrl, heroine, httpOptions)
.pipe(
tap(_ => this.log(`番号${heroine.id}のデータを変更`)),
catchError(this.handleError<any>('updateHeroine'))
);
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
this.log(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
private log(message: string) {
this.messageService.add('HeroineService: ' + message);
}
}
<div *ngIf="messageService.messages.length">
<h2>メッセージ</h2>
<button class="clear" (click)="messageService.clear()">消去</button>
<div *ngFor='let message of messageService.messages'>{{message}}</div>
</div>
import {Component, OnInit} from '@angular/core';
import {MessageService} from '../message.service';
@Component({
selector: 'app-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.css']
})
export class MessagesComponent implements OnInit {
constructor(public messageService: MessageService) {}
ngOnInit() {
}
}
import {Injectable} from '@angular/core';
@Injectable()
export class MessageService {
messages: string[] = [];
// constructor() {}
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
h2 {
color: red;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: crimson;
font-family: Cambria, Georgia;
}
button.clear {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
button.clear {
color: #888;
margin-bottom: 12px;
}
import {Component, OnInit} from '@angular/core';
import {Heroine} from '../heroine';
import {HeroineService} from '../heroine.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
heroines: Heroine[] = [];
constructor(private heroineService: HeroineService) { }
ngOnInit() {
this.getHeroines();
}
getHeroines():void {
this.heroineService.getHeroines()
.subscribe(heroines => this.heroines = heroines.slice(0, 4));
}
}
<h3>トップヒロイン</h3>
<div class="grid grid-pad">
<a *ngFor="let heroine of heroines" class="col-1-4"
routerLink="/detail/{{heroine.id}}">
<div class="module heroine">
<h4>{{heroine.name}}</h4>
</div>
</a>
</div>
<app-heroine-search></app-heroine-search>
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *::after, *::before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center; margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px;
}
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {DashboardComponent} from './dashboard/dashboard.component';
import {HeroinesComponent} from './heroines/heroines.component';
import {HeroineDetailComponent} from './heroine-detail/heroine-detail.component';
const routes: Routes = [
{path: '', redirectTo: '/dashboard', pathMatch: 'full'},
{path: 'dashboard', component: DashboardComponent},
{path: 'detail/:id', component: HeroineDetailComponent},
{path: 'heroines', component: HeroinesComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
import {Component, OnInit} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {of} from 'rxjs/observable/of';
import {
debounceTime, distinctUntilChanged, switchMap
} from 'rxjs/operators';
import {Heroine} from '../heroine';
import {HeroineService} from '../heroine.service';
@Component({
selector: 'app-heroine-search',
templateUrl: './heroine-search.component.html',
styleUrls: ['./heroine-search.component.css']
})
export class HeroineSearchComponent implements OnInit {
heroines$: Observable<Heroine[]>;
private searchTerms = new Subject<string>();
constructor(private heroineService: HeroineService) {}
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroines$ = this.searchTerms
.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((term: string) => this.heroineService.searchHeroines(term)),
);
}
}
<div id="search-component">
<h4>ヒロイン検索</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let heroine of heroines$ | async" >
<a routerLink="/detail/{{heroine.id}}">
{{heroine.name}}
</a>
</li>
</ul>
</div>
.search-result li {
border-bottom: 1px solid gray;
border-left: 1px solid gray;
border-right: 1px solid gray;
width:195px;
height: 16px;
padding: 5px;
background-color: white;
cursor: pointer;
list-style-type: none;
}
.search-result li:hover {
background-color: #607D8B;
}
.search-result li a {
color: #888;
display: block;
text-decoration: none;
}
.search-result li a:hover {
color: white;
}
.search-result li a:active {
color: white;
}
#search-box {
width: 200px;
height: 20px;
}
ul.search-result {
margin-top: 0;
padding-left: 0;
}