/* * * ./app/comments/app.component.ts * * */
// Imports
import { Component } from '@angular/core';
import { CommentComponent } from './comments/components/index'
@Component({
selector: 'my-app',
template: `
<h1>Comments</h1>
<comment-widget></comment-widget>
`,
})
export class AppComponent { }
// main entry point
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
/*
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
*/
<html>
<head>
<title>Scotch HTTP</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--<link rel="stylesheet" href="styles.css">-->
<link rel="stylesheet" href="https://bootswatch.com/readable/bootstrap.css">
<!-- Polyfill(s) for older browsers -->
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.6.25?main=browser"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
<script src="https://unpkg.com/systemjs@0.19.27/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<div class="container">
<my-app>Loading...</my-app>
</div>
</body>
</html>
/**
* PLUNKER 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: {
tsconfig: 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/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.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-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/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
'typescript': 'npm:typescript@2.0.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'
},
rxjs: {
defaultExtension: 'js'
},
'angular-in-memory-web-api': {
main: './index.js',
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
*/
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
/* * * ./app/emitter.service.ts * * */
// https://gist.github.com/sasxa
// Imports
import {Injectable, EventEmitter} from '@angular/core';
@Injectable()
export class EmitterService {
// Event store
private static _emitters: { [ID: string]: EventEmitter<any> } = {};
// Set a new event in the store with a given ID
// as key
static get(ID: string): EventEmitter<any> {
if (!this._emitters[ID])
this._emitters[ID] = new EventEmitter();
return this._emitters[ID];
}
}
/* * * ./app/comments/model/comment.ts * * */
export class Comment {
constructor(
public id: Date,
public author: string,
public text:string
){}
}
/* * * ./app/comments/components/comment.service.ts * * */
// Imports
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Comment } from '../model/comment';
import {Observable} from 'rxjs/Rx';
// Import RxJs required methods
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
@Injectable()
export class CommentService {
// Resolve HTTP using the constructor
constructor (private http: Http) {}
// private instance variable to hold base url
// private commentsUrl = 'http://localhost:3000/api/comments';
private commentsUrl = 'https://scotch-http-api.herokuapp.com/api/comments';
// Fetch all existing comments
getComments() : Observable<Comment[]>{
// ...using get request
return this.http.get(this.commentsUrl)
// ...and calling .json() on the response to return data
.map((res:Response) => res.json())
//...errors if any
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));
}
// Add a new comment
addComment (body: Object): Observable<Comment[]> {
let bodyString = JSON.stringify(body); // Stringify payload
let headers = new Headers({ 'Content-Type': 'application/json' }); // ... Set content type to JSON
let options = new RequestOptions({ headers: headers }); // Create a request option
return this.http.post(this.commentsUrl, body, options) // ...using post request
.map((res:Response) => res.json()) // ...and calling .json() on the response to return data
.catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
}
// Update a comment
updateComment (body: Object): Observable<Comment[]> {
let bodyString = JSON.stringify(body); // Stringify payload
let headers = new Headers({ 'Content-Type': 'application/json' }); // ... Set content type to JSON
let options = new RequestOptions({ headers: headers }); // Create a request option
return this.http.put(`${this.commentsUrl}/${body['id']}/`, body, options) // ...using put request
.map((res:Response) => res.json()) // ...and calling .json() on the response to return data
.catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
}
// Delete a comment
removeComment (id:string): Observable<Comment[]> {
return this.http.delete(`${this.commentsUrl}/${id}`) // ...using put request
.map((res:Response) => res.json()) // ...and calling .json() on the response to return data
.catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
}
}
/* * * ./app/comments/components/comment-box.component.ts * * */
// Imports
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Comment } from '../model/comment'
import { EmitterService } from '../../emitter.service';
import { CommentService } from '../services/comment.service';
// Component decorator
@Component({
selector: 'comment-box',
template: `
<div class="panel panel-default">
<div class="panel-heading">{{comment.author}}</div>
<div class="panel-body">
{{comment.text}}
</div>
<div class="panel-footer">
<button class="btn btn-info" (click)="editComment()"><span class="glyphicon glyphicon-edit"></span></button>
<button class="btn btn-danger" (click)="deleteComment(comment.id)"><span class="glyphicon glyphicon-remove"></span></button>
</div>
</div>
`
// No providers here because they are passed down from the parent component
})
// Component class
export class CommentBoxComponent {
// Constructor
constructor(
private commentService: CommentService
){}
// Define input properties
@Input() comment: Comment;
@Input() listId: string;
@Input() editId:string;
editComment(){
// Emit edit event
EmitterService.get(this.editId).emit(this.comment);
}
deleteComment(id:string){
// Call removeComment() from CommentService to delete comment
this.commentService.removeComment(id).subscribe(
comments => {
// Emit list event
EmitterService.get(this.listId).emit(comments);
},
err => {
// Log errors if any
console.log(err);
});
}
}
/* * * ./app/comments/components/comment-form.component.ts * * */
// Imports
import { Component, EventEmitter, Input, OnChanges } from '@angular/core';
import { NgForm } from '@angular/common';
import {Observable} from 'rxjs/Rx';
import { CommentBoxComponent } from './comment-box.component'
import { CommentService } from '../services/comment.service';
import { EmitterService } from '../../emitter.service';
import { Comment } from '../model/comment'
// Component decorator
@Component({
selector: 'comment-form',
template: `
<form (ngSubmit)="submitComment()">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1"><span class="glyphicon glyphicon-user"></span></span>
<input type="text" class="form-control" placeholder="Author" [(ngModel)]="model.author" name="author">
</div>
<br />
<textarea class="form-control" rows="3" placeholder="Text" [(ngModel)]="model.text" name="text"></textarea>
<br />
<button *ngIf="!editing" type="submit" class="btn btn-primary btn-block">Add</button>
<button *ngIf="editing" type="submit" class="btn btn-warning btn-block">Update</button>
</div>
</form>
`,
})
// Component class
export class CommentFormComponent implements OnChanges {
// Constructor with injected service
constructor(
private commentService: CommentService
){}
// Local properties
private model = new Comment(new Date(), '', '');
private editing = false;
// Input properties
@Input() editId: string;
@Input() listId: string;
submitComment(){
// Variable to hold a reference of addComment/updateComment
let commentOperation:Observable<Comment[]>;
if(!this.editing){
// Create a new comment
commentOperation = this.commentService.addComment(this.model)
} else {
// Update an existing comment
commentOperation = this.commentService.updateComment(this.model)
}
// Subscribe to observable
commentOperation.subscribe(
comments => {
// Emit list event
EmitterService.get(this.listId).emit(comments);
// Empty model
this.model = new Comment(new Date(), '', '');
// Switch editing status
if(this.editing) this.editing = !this.editing;
},
err => {
// Log errors if any
console.log(err);
});
}
ngOnChanges() {
// Listen to the 'edit'emitted event so as populate the model
// with the event payload
EmitterService.get(this.editId).subscribe((comment:Comment) => {
this.model = comment
this.editing = true;
});
}
}
/* * * ./app/comments/components/comment-list.component.ts * * */
// Imports
import { Component, OnInit, Input, OnChanges } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { CommentBoxComponent } from './comment-box.component';
import { Comment } from '../model/comment';
import {CommentService} from '../services/comment.service';
import { EmitterService } from '../../emitter.service';
// Component decorator
@Component({
selector: 'comment-list',
template: `
<comment-box [editId]="editId" [listId]="listId" *ngFor="let comment of comments" [comment]="comment"></comment-box>
`,
})
// Component class
export class CommentListComponent implements OnInit, OnChanges{
// Constructor with injected service
constructor(
private commentService: CommentService
){}
// Local properties
comments: Comment[];
// Input properties
@Input() listId: string;
@Input() editId: string;
loadComments(){
// Get all comments
this.commentService.getComments()
.subscribe(
comments => this.comments = comments, //Bind to view
err => {
// Log errors if any
console.log(err);
});
}
ngOnInit(){
// Load comments
this.loadComments()
}
ngOnChanges(changes:any) {
// Listen to the 'list'emitted event so as populate the model
// with the event payload
EmitterService.get(this.listId).subscribe((comments:Comment[]) => {this.loadComments()});
}
}
/* * * ./app/comments/components/index.ts * * */
// Imports
import { Component} from '@angular/core';
import {EmitterService} from '../../emitter.service';
@Component({
selector: 'comment-widget',
template: `
<div>
<comment-form [listId]="listId" [editId]="editId"></comment-form>
<comment-list [listId]="listId" [editId]="editId"></comment-list>
</div>
`,
})
export class CommentComponent {
// Event tracking properties
private listId = 'COMMENT_COMPONENT_LIST';
private editId = 'COMMENT_COMPONENT_EDIT';
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http';
import { CommentModule } from './comments/comments.module';
import { AppComponent } from './app.component';
import { EmitterService } from './emitter.service';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
JsonpModule,
CommentModule
],
declarations: [
AppComponent,
],
providers: [
EmitterService
],
bootstrap: [ AppComponent ]
})
export class AppModule {
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http';
import { CommentBoxComponent } from './components/comment-box.component';
import { CommentListComponent } from './components/comment-list.component';
import { CommentFormComponent } from './components/comment-form.component';
import { CommentComponent } from './components/index';
import { CommentService } from './services/comment.service';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
JsonpModule,
],
declarations: [
CommentBoxComponent,
CommentFormComponent,
CommentListComponent,
CommentComponent
],
providers: [
CommentService
],
exports:[
CommentBoxComponent,
CommentFormComponent,
CommentListComponent,
CommentComponent
]
})
export class CommentModule {
}