docs: add universal guide (#18707)

- based on original effort in PR 17573

PR Close #18707
This commit is contained in:
Ward Bell
2017-08-15 10:22:55 -07:00
committed by Chuck Jazdzewski
parent 963a4d0dc8
commit 0b0d25fa33
36 changed files with 1200 additions and 336 deletions

View File

@ -9,7 +9,8 @@ const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
{ path: 'heroes', component: HeroesComponent },
{ path: '**', redirectTo: '/dashboard' }
];
@NgModule({

View File

@ -1,4 +1,3 @@
/* #docregion */
h1 {
font-size: 1.2em;
color: #999;

View File

@ -1,5 +1,3 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
@Component({

View File

@ -1,54 +1,62 @@
// #docplaster
// #docregion
// #docregion v1, v2
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
// #docregion simple
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
// #enddocregion v1
// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
// #docregion v1
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard.component';
import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';
// #enddocregion v1, v2
import { HeroSearchComponent } from './hero-search.component';
// #docregion v1, v2
// #enddocregion simple
// #docregion platform-detection
import { PLATFORM_ID, APP_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
// #enddocregion platform-detection
// #docregion simple
@NgModule({
imports: [
BrowserModule.withServerTransition({
appId: 'toh-universal'
}),
// #docregion browsermodule
BrowserModule.withServerTransition({ appId: 'uni' }),
// #enddocregion browsermodule
FormsModule,
HttpModule,
// #enddocregion v1
// #docregion in-mem-web-api
InMemoryWebApiModule.forRoot(InMemoryDataService),
// #enddocregion in-mem-web-api
// #docregion v1
HttpClientModule,
HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService),
AppRoutingModule
],
// #docregion search
declarations: [
AppComponent,
DashboardComponent,
HeroDetailComponent,
HeroesComponent,
// #enddocregion v1, v2
HeroSearchComponent
// #docregion v1, v2
],
// #enddocregion search
providers: [ HeroService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
export class AppModule {
// #enddocregion simple
// #docregion platform-detection
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
@Inject(APP_ID) private appId: string) {
const platform = isPlatformBrowser(platformId) ?
'on the server' : 'in the browser';
console.log(`Running ${platform} with appId=${appId}`);
}
// #enddocregion platform-detection
// #docregion simple
}
// #enddocregion simple

View File

@ -1,4 +1,3 @@
/* #docregion */
[class*='col-'] {
float: left;
padding-right: 20px;

View File

@ -1,7 +1,6 @@
<!-- #docregion -->
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">
<a *ngFor="let hero of heroes | async" [routerLink]="['/detail', hero.id]" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>

View File

@ -1,22 +1,23 @@
// #docregion , search
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'my-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [ './dashboard.component.css' ]
})
// #enddocregion search
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
heroes: Observable<Hero[]>;
constructor(private heroService: HeroService) { }
ngOnInit(): void {
this.heroService.getHeroes()
.then(heroes => this.heroes = heroes.slice(1, 5));
this.heroes = this.heroService.getHeroes()
.map(heroes => heroes.slice(1, 5));
}
}

View File

@ -1,4 +1,3 @@
/* #docregion */
label {
display: inline-block;
width: 3em;
@ -25,6 +24,6 @@ button:hover {
}
button:disabled {
background-color: #eee;
color: #ccc;
color: #ccc;
cursor: auto;
}

View File

@ -1,4 +1,3 @@
<!-- #docregion -->
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div>
@ -8,7 +7,5 @@
<input [(ngModel)]="hero.name" placeholder="name" />
</div>
<button (click)="goBack()">Back</button>
<!-- #docregion save -->
<button (click)="save()">Save</button>
<!-- #enddocregion save -->
</div>

View File

@ -1,4 +1,3 @@
// #docregion
import 'rxjs/add/operator/switchMap';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
@ -27,12 +26,11 @@ export class HeroDetailComponent implements OnInit {
.subscribe(hero => this.hero = hero);
}
// #docregion save
save(): void {
this.heroService.update(this.hero)
.then(() => this.goBack());
this.heroService
.update(this.hero)
.subscribe(() => this.goBack());
}
// #enddocregion save
goBack(): void {
this.location.back();

View File

@ -1,4 +1,3 @@
/* #docregion */
.search-result{
border-bottom: 1px solid gray;
border-left: 1px solid gray;

View File

@ -1,4 +1,3 @@
<!-- #docregion -->
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />

View File

@ -1,20 +1,14 @@
// #docplaster
// #docregion
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
// #docregion rxjs-imports
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
// Observable class extensions
import 'rxjs/add/observable/of';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
// #enddocregion rxjs-imports
import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';
@ -26,27 +20,20 @@ import { Hero } from './hero';
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
// #docregion search
heroes: Observable<Hero[]>;
// #enddocregion search
// #docregion searchTerms
private searchTerms = new Subject<string>();
// #enddocregion searchTerms
constructor(
private heroSearchService: HeroSearchService,
private router: Router) {}
// #docregion searchTerms
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
// #enddocregion searchTerms
// #docregion search
ngOnInit(): void {
this.heroes = this.searchTerms
this.heroes = this.searchTerms.asObservable()
.debounceTime(300) // wait 300ms after each keystroke before considering the term
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => term // switch to new observable each time the term changes
@ -60,7 +47,6 @@ export class HeroSearchComponent implements OnInit {
return Observable.of<Hero[]>([]);
});
}
// #enddocregion search
gotoDetail(hero: Hero): void {
let link = ['/detail', hero.id];

View File

@ -1,20 +1,28 @@
// #docregion
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Hero } from './hero';
import { Hero } from './hero';
// #docregion class
@Injectable()
export class HeroSearchService {
constructor(private http: Http) {}
private searchUrl = 'api/heroes/?name='; // URL to web api
constructor(
private http: HttpClient,
@Optional() @Inject(APP_BASE_HREF) origin: string) {
this.searchUrl = (origin || '') + this.searchUrl;
}
search(term: string): Observable<Hero[]> {
return this.http
.get(`api/heroes/?name=${term}`)
.map(response => response.json().data as Hero[]);
.get(this.searchUrl + term)
.map((data: any) => data.data as Hero[]);
}
}
// #enddocregion class

View File

@ -1,87 +1,64 @@
// #docplaster
// #docregion , imports
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import { Injectable, Inject, Optional } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { HttpHeaders, HttpClient } from '@angular/common/http';
// #docregion rxjs
import 'rxjs/add/operator/toPromise';
// #enddocregion rxjs
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import { Hero } from './hero';
// #enddocregion imports
const headers = new HttpHeaders({'Content-Type': 'application/json'});
@Injectable()
export class HeroService {
// #docregion update
private headers = new Headers({'Content-Type': 'application/json'});
// #enddocregion update
// #docregion getHeroes
private heroesUrl = 'api/heroes'; // URL to web api
constructor(private http: Http) { }
// #docregion ctor
constructor(
private http: HttpClient,
@Optional() @Inject(APP_BASE_HREF) origin: string) {
this.heroesUrl = (origin || '') + this.heroesUrl;
}
// #enddocregion ctor
getHeroes(): Promise<Hero[]> {
getHeroes(): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
// #docregion to-promise
.toPromise()
// #enddocregion to-promise
// #docregion to-data
.then(response => response.json().data as Hero[])
// #enddocregion to-data
// #docregion catch
.map((data: any) => data.data as Hero[])
.catch(this.handleError);
// #enddocregion catch
}
// #enddocregion getHeroes
// #docregion getHero
getHero(id: number): Promise<Hero> {
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get(url)
.toPromise()
.then(response => response.json().data as Hero)
.map((data: any) => data.data as Hero)
.catch(this.handleError);
}
// #enddocregion getHero
// #docregion delete
delete(id: number): Promise<void> {
delete(id: number): Observable<void> {
const url = `${this.heroesUrl}/${id}`;
return this.http.delete(url, {headers: this.headers})
.toPromise()
.then(() => null)
return this.http.delete(url, { headers })
.catch(this.handleError);
}
// #enddocregion delete
// #docregion create
create(name: string): Promise<Hero> {
create(name: string): Observable<Hero> {
return this.http
.post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
.toPromise()
.then(res => res.json().data)
.post(this.heroesUrl, { name: name }, { headers })
.map((data: any) => data.data)
.catch(this.handleError);
}
// #enddocregion create
// #docregion update
update(hero: Hero): Promise<Hero> {
update(hero: Hero): Observable<Hero> {
const url = `${this.heroesUrl}/${hero.id}`;
return this.http
.put(url, JSON.stringify(hero), {headers: this.headers})
.toPromise()
.then(() => hero)
.put(url, hero, { headers })
.catch(this.handleError);
}
// #enddocregion update
// #docregion getHeroes, handleError
private handleError(error: any): Promise<any> {
private handleError(error: any): Observable<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
throw error;
}
// #enddocregion getHeroes, handleError
}

View File

@ -21,31 +21,27 @@ export class HeroesComponent implements OnInit {
getHeroes(): void {
this.heroService
.getHeroes()
.then(heroes => this.heroes = heroes);
.subscribe(heroes => this.heroes = heroes);
}
// #docregion add
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.create(name)
.then(hero => {
.subscribe(hero => {
this.heroes.push(hero);
this.selectedHero = null;
});
}
// #enddocregion add
// #docregion delete
delete(hero: Hero): void {
this.heroService
.delete(hero.id)
.then(() => {
.subscribe(() => {
this.heroes = this.heroes.filter(h => h !== hero);
if (this.selectedHero === hero) { this.selectedHero = null; }
});
}
// #enddocregion delete
ngOnInit(): void {
this.getHeroes();