diff --git a/package-lock.json b/package-lock.json index f2cf6ce..49d16fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -473,6 +473,11 @@ } } }, + "angular-in-memory-web-api": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.5.3.tgz", + "integrity": "sha512-1QPwwXG8R/2s7EbHh13HDiJYsk4sdBHNxHJHZHJ/Kxb4T9OG+bb1kGcXzY9UrJkEVxOtUW0ozvL4p/HmeIEszg==" + }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", diff --git a/package.json b/package.json index 4dfd7c1..31b1eac 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@angular/platform-browser": "^5.2.0", "@angular/platform-browser-dynamic": "^5.2.0", "@angular/router": "^5.2.0", + "angular-in-memory-web-api": "^0.5.3", "core-js": "^2.4.1", "rxjs": "^5.5.6", "zone.js": "^0.8.19" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100644 index 0000000..1b2d9f4 --- /dev/null +++ b/src/app/app-routing.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { HeroesComponent } from './heroes/heroes.component'; +import { DashboardComponent } from './dashboard/dashboard.component'; +import { HeroDetailComponent } from './hero-detail/hero-detail.component'; + +const routes: Routes = [ + { path: 'heroes', component: HeroesComponent }, + { path: 'dashboard', component: DashboardComponent }, + { path: 'detail/:id', component: HeroDetailComponent }, + { path: '', redirectTo: '/dashboard', pathMatch: 'full' } +]; + +@NgModule({ + imports: [ RouterModule.forRoot(routes) ], + exports: [ RouterModule ] +}) +export class AppRoutingModule { } diff --git a/src/app/app.component.css b/src/app/app.component.css index e69de29..bf741e4 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -0,0 +1,29 @@ +/* AppComponent's private CSS styles */ +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; +} diff --git a/src/app/app.component.html b/src/app/app.component.html index fa2706a..d5f2d75 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,20 +1,7 @@ - -
-

- Welcome to {{ title }}! -

- Angular Logo -
-

Here are some links to help you start:

- - +

{{ title }}

+ + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7b0f672..c41599e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -6,5 +6,5 @@ import { Component } from '@angular/core'; styleUrls: ['./app.component.css'] }) export class AppComponent { - title = 'app'; + title = 'Tour of Heroes'; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 926975a..49473b9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,18 +1,48 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; - +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; +import { HeroesComponent } from './heroes/heroes.component'; +import { HeroDetailComponent } from './hero-detail/hero-detail.component'; +import { HeroService } from './hero.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 { HeroSearchComponent } from './hero-search/hero-search.component'; + +import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; +import { InMemoryDataService } from './in-memory-data.service'; @NgModule({ declarations: [ - AppComponent + AppComponent, + HeroesComponent, + HeroDetailComponent, + MessagesComponent, + DashboardComponent, + HeroSearchComponent ], imports: [ - BrowserModule + BrowserModule, + FormsModule, + AppRoutingModule, + HttpClientModule, + + // The HttpClientInMemoryWebApiModule module intercepts HTTP requests + // and returns simulated server responses. + // Remove it when a real server is ready to receive requests. + HttpClientInMemoryWebApiModule.forRoot( + InMemoryDataService, { dataEncapsulation: false } + ) + ], + providers: [ + HeroService, + MessageService, ], - providers: [], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/dashboard/dashboard.component.css b/src/app/dashboard/dashboard.component.css new file mode 100644 index 0000000..3822cc5 --- /dev/null +++ b/src/app/dashboard/dashboard.component.css @@ -0,0 +1,62 @@ +/* DashboardComponent's private CSS styles */ +[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; + } +} diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html new file mode 100644 index 0000000..25ce7d7 --- /dev/null +++ b/src/app/dashboard/dashboard.component.html @@ -0,0 +1,11 @@ +

Top Heroes

+
+ +
+

{{hero.name}}

+
+
+
+ + diff --git a/src/app/dashboard/dashboard.component.spec.ts b/src/app/dashboard/dashboard.component.spec.ts new file mode 100644 index 0000000..9c996c3 --- /dev/null +++ b/src/app/dashboard/dashboard.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts new file mode 100644 index 0000000..4958533 --- /dev/null +++ b/src/app/dashboard/dashboard.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit } from '@angular/core'; +import { Hero } from '../hero'; +import { HeroService } from '../hero.service'; + +@Component({ + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.css'] +}) +export class DashboardComponent implements OnInit { + heroes: Hero[] = []; + + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.getHeroes(); + } + + getHeroes(): void { + this.heroService.getHeroes() + .subscribe(heroes => this.heroes = heroes.slice(1, 5)); + } + +} diff --git a/src/app/hero-detail/hero-detail.component.css b/src/app/hero-detail/hero-detail.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/hero-detail/hero-detail.component.html b/src/app/hero-detail/hero-detail.component.html new file mode 100644 index 0000000..e112eb8 --- /dev/null +++ b/src/app/hero-detail/hero-detail.component.html @@ -0,0 +1,12 @@ +
+

{{ hero.name | uppercase }} Details

+
id: {{ hero.id }}
+
+ +
+
+ + + diff --git a/src/app/hero-detail/hero-detail.component.spec.ts b/src/app/hero-detail/hero-detail.component.spec.ts new file mode 100644 index 0000000..5e34497 --- /dev/null +++ b/src/app/hero-detail/hero-detail.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeroDetailComponent } from './hero-detail.component'; + +describe('HeroDetailComponent', () => { + let component: HeroDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HeroDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HeroDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/hero-detail/hero-detail.component.ts b/src/app/hero-detail/hero-detail.component.ts new file mode 100644 index 0000000..0af3db0 --- /dev/null +++ b/src/app/hero-detail/hero-detail.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Hero } from '../hero'; +import { ActivatedRoute } from '@angular/router'; +import { Location } from '@angular/common'; + +import { HeroService } from '../hero.service'; + +@Component({ + selector: 'app-hero-detail', + templateUrl: './hero-detail.component.html', + styleUrls: ['./hero-detail.component.css'] +}) +export class HeroDetailComponent implements OnInit { + + @Input() hero: Hero; + + constructor( + private route: ActivatedRoute, + private heroService: HeroService, + private location: Location + ) { } + + ngOnInit() { + this.getHero(); + } + + getHero(): void { + const id = +this.route.snapshot.paramMap.get('id'); + this.heroService.getHero(id) + .subscribe(h => this.hero = h); + } + + goBack(): void { + this.location.back(); + } + + save(): void { + this.heroService.updateHero(this.hero) + .subscribe(() => this.goBack()); + } +} diff --git a/src/app/hero-search/hero-search.component.css b/src/app/hero-search/hero-search.component.css new file mode 100644 index 0000000..0f2c8cd --- /dev/null +++ b/src/app/hero-search/hero-search.component.css @@ -0,0 +1,39 @@ +/* HeroSearch private styles */ +.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; +} diff --git a/src/app/hero-search/hero-search.component.html b/src/app/hero-search/hero-search.component.html new file mode 100644 index 0000000..e6f7c65 --- /dev/null +++ b/src/app/hero-search/hero-search.component.html @@ -0,0 +1,13 @@ +
+

Hero Search

+ + + + +
diff --git a/src/app/hero-search/hero-search.component.spec.ts b/src/app/hero-search/hero-search.component.spec.ts new file mode 100644 index 0000000..901bb7f --- /dev/null +++ b/src/app/hero-search/hero-search.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeroSearchComponent } from './hero-search.component'; + +describe('HeroSearchComponent', () => { + let component: HeroSearchComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HeroSearchComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HeroSearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/hero-search/hero-search.component.ts b/src/app/hero-search/hero-search.component.ts new file mode 100644 index 0000000..7d347b7 --- /dev/null +++ b/src/app/hero-search/hero-search.component.ts @@ -0,0 +1,43 @@ +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 { Hero } from '../hero'; +import { HeroService } from '../hero.service'; + +@Component({ + selector: 'app-hero-search', + templateUrl: './hero-search.component.html', + styleUrls: [ './hero-search.component.css' ] +}) + +export class HeroSearchComponent implements OnInit { + heroes$: Observable; + private searchTerms = new Subject(); + + constructor(private heroService: HeroService) {} + + // Push a search term into the observable stream. + search(term: string): void { + this.searchTerms.next(term); + } + + ngOnInit(): void { + this.heroes$ = this.searchTerms.pipe( + // wait 300ms after each keystroke before considering the term + debounceTime(300), + + // ignore new term if same as previous term + distinctUntilChanged(), + + // switch to new search observable each time the term changes + switchMap((term: string) => this.heroService.searchHeroes(term)), + ); + } +} diff --git a/src/app/hero.service.spec.ts b/src/app/hero.service.spec.ts new file mode 100644 index 0000000..3097bd6 --- /dev/null +++ b/src/app/hero.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { HeroService } from './hero.service'; + +describe('HeroService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [HeroService] + }); + }); + + it('should be created', inject([HeroService], (service: HeroService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/hero.service.ts b/src/app/hero.service.ts new file mode 100644 index 0000000..384956e --- /dev/null +++ b/src/app/hero.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@angular/core'; +import { Hero } from './hero'; +import { HEROES } from './mock-heroes'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { MessageService } from './message.service'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { catchError, map, tap } from 'rxjs/operators'; + +const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }) +}; + +@Injectable() +export class HeroService { + + private heroesUrl = 'api/heroes'; + + constructor( + private http: HttpClient, + private messageService: MessageService + ) { } + + getHeroes(): Observable { + return this.http.get(this.heroesUrl) + .pipe( + tap(heroes => this.log('fetched heroes')), + catchError(this.handleError('getHeroes', [])) + ); + } + + getHero(id: number): Observable { + const heroUrl = `${this.heroesUrl}/${id}`; + return this.http.get(heroUrl) + .pipe( + tap(_ => this.log(`fetched hero id=${id}`)), + catchError(this.handleError(`getHero id=${id}`)) + ); + } + + updateHero(hero: Hero): Observable { + return this.http.put(this.heroesUrl, hero, httpOptions) + .pipe( + tap(_ => this.log(`updated hero id=${hero.id}`)), + catchError(this.handleError('updateHero')) + ); + } + + addHero(hero: Hero): Observable { + return this.http.post(this.heroesUrl, hero, httpOptions) + .pipe( + tap(_ => this.log(`added hero id=${hero.id}`)), + catchError(this.handleError('addHero')) + ); + } + + /** DELETE: delete the hero from the server */ + deleteHero (hero: Hero | number): Observable { + const id = typeof hero === 'number' ? hero : hero.id; + const url = `${this.heroesUrl}/${id}`; + + return this.http.delete(url, httpOptions).pipe( + tap(_ => this.log(`deleted hero id=${id}`)), + catchError(this.handleError('deleteHero')) + ); + } + + searchHeroes(term: string): Observable { + term = term.trim(); + if (!term) { return of([]); } + return this.http.get(`api/heroes/?name=${term}`) + .pipe( + tap(_ => this.log(`found heroes matching "${term}"`)), + catchError(this.handleError('searchHeroes', [])) + ); + } + + private log(message: string) { + this.messageService.add('HeroService: ' + message); + } + + /** + * Handle Http operation that failed. + * Let the app continue. + * @param operation - name of the operation that failed + * @param result - optional value to return as the observable result + */ + private handleError (operation = 'operation', result?: T) { + return (error: any): Observable => { + + // TODO: send the error to remote logging infrastructure + console.error(error); // log to console instead + + // TODO: better job of transforming error for user consumption + this.log(`${operation} failed: ${error.message}`); + + // Let the app keep running by returning an empty result. + return of(result as T); + }; + } + +} diff --git a/src/app/hero.ts b/src/app/hero.ts new file mode 100644 index 0000000..e3eac51 --- /dev/null +++ b/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/src/app/heroes/heroes.component.css b/src/app/heroes/heroes.component.css new file mode 100644 index 0000000..05b8221 --- /dev/null +++ b/src/app/heroes/heroes.component.css @@ -0,0 +1,73 @@ +/* HeroesComponent's private CSS styles */ +.heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 15em; +} +.heroes li { + position: relative; + cursor: pointer; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} + +.heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} + +.heroes a { + color: #888; + text-decoration: none; + position: relative; + display: block; + width: 250px; +} + +.heroes a:hover { + color:#607D8B; +} + +.heroes .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 { + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; + font-family: Arial; +} + +button:hover { + background-color: #cfd8dc; +} + +button.delete { + position: relative; + left: 194px; + top: -32px; + background-color: gray !important; + color: white; +} diff --git a/src/app/heroes/heroes.component.html b/src/app/heroes/heroes.component.html new file mode 100644 index 0000000..f9148a0 --- /dev/null +++ b/src/app/heroes/heroes.component.html @@ -0,0 +1,21 @@ +

My Heroes

+ +
+ + + +
+ + diff --git a/src/app/heroes/heroes.component.spec.ts b/src/app/heroes/heroes.component.spec.ts new file mode 100644 index 0000000..66518e4 --- /dev/null +++ b/src/app/heroes/heroes.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeroesComponent } from './heroes.component'; + +describe('HeroesComponent', () => { + let component: HeroesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HeroesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HeroesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/heroes/heroes.component.ts b/src/app/heroes/heroes.component.ts new file mode 100644 index 0000000..db03e34 --- /dev/null +++ b/src/app/heroes/heroes.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { Hero } from '../hero'; +import { HeroService } from '../hero.service'; + +@Component({ + selector: 'app-heroes', + templateUrl: './heroes.component.html', + styleUrls: ['./heroes.component.css'] +}) +export class HeroesComponent implements OnInit { + + heroes: Hero[]; + + selectedHero: Hero; + + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.getHeroes(); + } + + onSelect(h: Hero): void { + this.selectedHero = h; + } + + getHeroes():void { + this.heroService.getHeroes() + .subscribe(heroes => this.heroes = heroes); + } + + add(name: string): void { + name = name.trim(); + if (!name) { return; } + this.heroService.addHero( { name } as Hero) + .subscribe(hero => this.heroes.push(hero)); + } + + delete(hero: Hero): void { + this.heroes = this.heroes.filter(h => h !== hero); + this.heroService.deleteHero(hero).subscribe(); + } + +} diff --git a/src/app/in-memory-data.service.ts b/src/app/in-memory-data.service.ts new file mode 100644 index 0000000..ff1f190 --- /dev/null +++ b/src/app/in-memory-data.service.ts @@ -0,0 +1,19 @@ +import { InMemoryDbService } from 'angular-in-memory-web-api'; + +export class InMemoryDataService implements InMemoryDbService { + createDb() { + const heroes = [ + { id: 11, name: 'Mr. Nice' }, + { id: 12, name: 'Narco' }, + { id: 13, name: 'Bombasto' }, + { id: 14, name: 'Celeritas' }, + { id: 15, name: 'Magneta' }, + { id: 16, name: 'RubberMan' }, + { id: 17, name: 'Dynama' }, + { id: 18, name: 'Dr IQ' }, + { id: 19, name: 'Magma' }, + { id: 20, name: 'Tornado' } + ]; + return {heroes}; + } +} diff --git a/src/app/message.service.spec.ts b/src/app/message.service.spec.ts new file mode 100644 index 0000000..63ecfd8 --- /dev/null +++ b/src/app/message.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { MessageService } from './message.service'; + +describe('MessageService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [MessageService] + }); + }); + + it('should be created', inject([MessageService], (service: MessageService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/message.service.ts b/src/app/message.service.ts new file mode 100644 index 0000000..59ac8fe --- /dev/null +++ b/src/app/message.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class MessageService { + + messages: string[] = []; + + add(message: string) { + this.messages.push(message); + } + + clear() { + this.messages = []; + } + +} diff --git a/src/app/messages/messages.component.css b/src/app/messages/messages.component.css new file mode 100644 index 0000000..d08d9be --- /dev/null +++ b/src/app/messages/messages.component.css @@ -0,0 +1,35 @@ +/* MessagesComponent's private CSS styles */ +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; +} diff --git a/src/app/messages/messages.component.html b/src/app/messages/messages.component.html new file mode 100644 index 0000000..1df7dfd --- /dev/null +++ b/src/app/messages/messages.component.html @@ -0,0 +1,8 @@ +
+ +

Messages

+ +
{{message}}
+ +
diff --git a/src/app/messages/messages.component.spec.ts b/src/app/messages/messages.component.spec.ts new file mode 100644 index 0000000..66109cc --- /dev/null +++ b/src/app/messages/messages.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MessagesComponent } from './messages.component'; + +describe('MessagesComponent', () => { + let component: MessagesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MessagesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MessagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/messages/messages.component.ts b/src/app/messages/messages.component.ts new file mode 100644 index 0000000..95165a5 --- /dev/null +++ b/src/app/messages/messages.component.ts @@ -0,0 +1,16 @@ +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() { + } + +} diff --git a/src/app/mock-heroes.ts b/src/app/mock-heroes.ts new file mode 100644 index 0000000..e84c2fd --- /dev/null +++ b/src/app/mock-heroes.ts @@ -0,0 +1,14 @@ +import { Hero } from './hero'; + +export const HEROES: Hero[] = [ + { id: 11, name: 'Mr. Nice' }, + { id: 12, name: 'Narco' }, + { id: 13, name: 'Bombasto' }, + { id: 14, name: 'Celeritas' }, + { id: 15, name: 'Magneta' }, + { id: 16, name: 'RubberMan' }, + { id: 17, name: 'Dynama' }, + { id: 18, name: 'Dr IQ' }, + { id: 19, name: 'Magma' }, + { id: 20, name: 'Tornado' } +]; diff --git a/src/styles.css b/src/styles.css index 90d4ee0..c2be593 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1 +1,23 @@ /* You can add global styles to this file, and also import other style files */ +/* 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; +}