~$ git clone https://github.com/Pieter-hogent/recipeapp.git (or git pull) ~$ cd recipeapp ~/recipeapp$ npm install ~/recipeapp$ git checkout -b mybranch 4b40342
let notAMoon = buildDeathStar();
let alderaan = destroyPlanet(notAMoon);
let disturbedForce = aMillionVoicesCriedOut(alderaan);
let notAMoon = buildADeathStarAsync();
let bobbaFett = createACloneArmyAsync();
...
let alderaan = destroyPlanet(notAMoon);
function longAsyncOperation(callbackParameter) {
// loooong operation
return callbackParameter(calculation);
}
let result = longAsyncOperation(doSomethingWithResult);
doSomethingWithResult(result);
doOtherStuffThatDoesntNeedResult();
createGalacticEmpire(function (error, empire) {
if (!error) {
empire.buildADeathStar(function (notAMoon) {
notAMoon.destroyPlanet(function (error, alderaan) {
if (error) {
throw new Exception(error.msg);
} else {
alderaan.eliminateRebels(function (disturbance) {
....
})
}
})
})
} else {
console.log(error.message());
}
})
function slowAsync(callback) {
}
function anotherAsync(callback) {
}
let processAfterBoth = ...
// How can I call processAfterBoth ONLY if both have finished??
slowAsync( ??? )
anotherAsync( ??? )
let deathStarPromise = buildADeathStarAsync();
let army = buildCloneArmyAsync();
deathStarPromise.then(notAMoon => notAMoon.destroyPlanet());
let luke = meetYourDad();
deathStarPromise.catch(error => console.log(error));
let empire = createGalacticEmpire(function (error, empire) {);
if (!error) {
let notAMoon = empire.then(e => e.buildADeathStar(function (notAMoon) {));
let alderaan = notAMoon.then(moon => moon.destroyPlanet(function (error, alderaan) {));
if (error) {
throw new Exception(error.msg);
} else {
let disturbance = alderaan.then(al => al.eliminateRebels(function (disturbance) {));
....
})
}
})
})
} else {
console.log(error.message());
}
})
disturbance.then( () => { ... } );
empire.catch(err => console.log(err.message()));
notAMoon.catch(err => throw new Exception(err.msg));
src/app/recipe/recipe-list/recipe-list.component.ts
import { Subject } from 'rxjs';
export class RecipeListComponent {
public filterRecipeName: string;
public filterRecipe$ = new Subject<string>();
constructor(private _recipeDataService: RecipeDataService) {
this.filterRecipe$.subscribe(
val => this.filterRecipeName = val);
}
}
}
src/app/recipe/recipe-list/recipe-list.component.html
<mat-form-field>
<input matInput (keyup)='filterRecipe$.next($event.target.value)'
type='text'
placeholder='filter recipe name...' #filter>
</mat-form-field>
<button (click)="applyFilter(filter.value)" mat-raised-button>
filter
</button>
src/app/recipe/recipe-list/recipe-list.component.ts
import { distinctUntilChanged, debounceTime,
map, filter } from 'rxjs/operators';
constructor(private _recipeDataService: RecipeDataService) {
this.filterRecipe$
.pipe(
distinctUntilChanged(),
debounceTime(400),
map(val => val.toLowerCase()),
filter(val => !val.startsWith('s'))
)
.subscribe(val => (this.filterRecipeName = val));
}
87dd96b
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/distinctUntilChanged'
this.filterRecipe$
.distinctUntilChanged()
.debounceTime(400)
.map(val => val.toLowerCase());
src/app/recipe/recipe.module.ts
@NgModule({
declarations: [
RecipeComponent,
IngredientComponent,
RecipeListComponent,
AddRecipeComponent,
RecipeFilterPipe
],
imports: [
CommonModule,
HttpClientModule,
MaterialModule
],
exports: [RecipeListComponent]
})
export class RecipeModule {}
./proxy.conf.json
{
"/api": {
"target": {
"host": "localhost",
"protocol": "https:",
"port": 5001
},
"secure": false,
"changeOrigin": true,
"logLevel": "info"
}
}
~$ ng serve --proxy-config proxy.conf.json
package.json
"scripts": {
"start": "ng serve --proxy-config proxy.conf.json",
}
environments/environment.ts
export const environment = {
production: false,
apiUrl: '/api'
};
src/app/recipe.data.service.ts
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class RecipeDataService {
private _recipes = RECIPES;
constructor(private http: HttpClient) {}
get recipes$(): Observable< Recipe[] > {
return this._recipes;
return this.http.get(`${environment.apiUrl}/recipes/`)).pipe(
tap(console.log),
map(
(list: any[]): Recipe[] => {}list.map(Recipe.fromJSON)
)
);
}
}
src/app/recipe/recipe-list/recipe-list.component.html
export class RecipeListComponent {
// ....
get recipes$(): Observable<Recipe[]> {
return this._recipeDataService.recipes$;
}
}
src/app/recipe/recipe-list/recipe-list.component.ts
export class RecipeComponent {
private _recipes: Recipes[];
private _fetchRecipes$: Observable<Recipe[]>
= this._recipeDataService.recipes$;
constructor(private _recipeDataService: RecipeDataService) {
this._recipeDataService.recipe$.subscribe(
res => this._recipes = res
);
}
get recipes$(): Observable<Recipe[]> {
return this._fetchRecipes$this._recipesthis._recipeDataService.recipes$;
}
src/app/app.component.html
<div>
<!-- ... -->
<div fxLayout="row" [...] >
<div fxFlex="0 0 calc(25%-0.5%)"
*ngFor="
let localRecipe of (recipes$ | async | recipeFilter: filterRecipeName)
"
>
<app-recipe [recipe]="localRecipe"></app-recipe>
</div>
</div>
</div>
97466f4
src/app/recipe/recipe-list/recipe-list.component.html
<div *ngIf="(recipes$ | async) as recipes; else loading">
<div
fxLayout="row wrap"
fxLayout.xs="column"
fxLayoutWrap
fxLayoutGap="0.5%"
fxLayoutAlign="left"
>
<div
*ngFor="let localRecipe of (recipes$ | async | recipeFilter: filterRecipeName)"
>
<app-recipe [recipe]="localRecipe"></app-recipe>
</div>
</div>
</div>
<ng-template #loading><mat-spinner></mat-spinner></ng-template>
src/app/recipe.data.service.ts
export class RecipeDataService {
constructor(private http: HttpClient) {}
get recipes$(): Observable<Recipe[]> {
return this.http.get(`${environment.apiUrl}/recipes/`).pipe(
delay(2000),
map((list: any[]): Recipe[] => list.map(Recipe.fromJSON))
);
}
}
eb23d85
src/app/recipe-data.service.ts
export class RecipeDataService {
constructor(private http: HttpClient) {}
get recipes$(): Observable<Recipe[]> {
return this.http.get(`${environment.apiUrl}/recipes/`).pipe(
catchError(handleError),
map((list: any[]): Recipe[] => list.map(Recipe.fromJSON))
);
}
handleError(err: any): Observable<never> {
let errorMessage: string;
if (err instanceof HttpErrorResponse) {
errorMessage = `'${err.status} ${err.statusText}' when accessing '${err.url}'`;
} else {
errorMessage = `an unknown error occurred ${err}`;
}
console.error(err);
return of([]);
return throwError(errorMessage);
}
}
src/app/recipe/recipe-list/recipe-list.component.ts
export class RecipeListComponent implements OnInit {
private _fetchRecipes$: Observable<Recipe[]> = this._recipeDataService
.recipes$;
public errorMessage: string = '';
// [...]
ngOnInit(): void {
this._fetchRecipes$ = this._recipeDataService.recipes$.pipe(
catchError(err => {
this.errorMessage = err;
return EMPTY;
})
);
}
}
src/app/recipe/recipe-list/recipe-list.component.html
<div *ngIf="(recipes$ | async) as recipes; else loadingloadingOrError">
<div
fxLayout="row"
>
<div *ngFor="let localRecipe of (recipes | recipeFilter: filterRecipeName)">
<app-recipe [recipe]="localRecipe"></app-recipe>
</div>
</div>
</div>
<ng-template #loadingOrError>
<mat-card class="error" *ngIf="errorMessage; else loading">
Error loading the recipe list: {{ errorMessage }}. <br/>
Please try again later.
<ng-template #loading>
<mat-spinner></mat-spinner>
</ng-template>
</mat-card>
</ng-template>
4fd078b
src/app/recipe/recipe-data.service.ts
export class RecipeDataService {
constructor(private http: HttpClient) {}
addNewRecipe(recipe: Recipe) {
return this.http
.post(`${environment.apiUrl}/recipes/`, recipe.toJSON())
.pipe(catchError(this.handleError), map(Recipe.fromJSON))
.subscribe();
}
}
src/app/recipe/recipe-data.service.ts
export class RecipeDataService {
private _recipes$ = new BehaviorSubject<Recipe[]>([]);
private _recipes: Recipe[];
constructor(private http: HttpClient) {
this.recipes$.subscribe((recipes: Recipe[]) => {
this._recipes = recipes;
this._recipes$.next(this._recipes);
});
}
get allRecipes$(): Observable<Recipe[]> {
return this._recipes$;
}
get recipes$(): Observable<Recipe[]> {
return this.http.get(`${environment.apiUrl}/recipes/`).pipe(
catchError(this.handleError),
map((list: any[]): Recipe[] => list.map(Recipe.fromJSON))
);
}
addNewRecipe(recipe: Recipe) {
return this.http
.post(`${environment.apiUrl}/recipes/`, recipe.toJSON())
.pipe(catchError(this.handleError),map(Recipe.fromJSON))
.subscribe((rec: Recipe) => {
this._recipes = [...this._recipes, rec];
this._recipes$.next(this._recipes);
});
}