@ -1,15 +1,28 @@ | |||
import { Component, OnInit } from '@angular/core'; | |||
import { CatalogService } from './catalog.service'; | |||
import { Catalog } from '../shared/models/catalog.model'; | |||
import { CatalogItem } from '../shared/models/catalogItem.model'; | |||
@Component({ | |||
selector: 'appc-catalog', | |||
styleUrls: ['./catalog.component.scss'], | |||
templateUrl: './catalog.component.html' | |||
selector: 'appc-catalog', | |||
styleUrls: ['./catalog.component.scss'], | |||
templateUrl: './catalog.component.html' | |||
}) | |||
export class CatalogComponent implements OnInit { | |||
constructor() { } | |||
private brands: any[]; | |||
private types: any[]; | |||
private items: CatalogItem[]; | |||
private filteredItems: any[]; | |||
private catalog: Catalog; | |||
ngOnInit() { | |||
console.log('catalog component loaded'); | |||
} | |||
constructor(private service: CatalogService) { } | |||
ngOnInit() { | |||
console.log('catalog component loaded'); | |||
this.service.getCatalog().subscribe((catalog: Catalog) => { | |||
this.catalog = catalog; | |||
this.items = catalog.data; | |||
console.log(this.items.length + ' catalog items retrieved from api'); | |||
}); | |||
} | |||
} |
@ -1,11 +1,13 @@ | |||
import { NgModule } from '@angular/core'; | |||
import { NgModule } from '@angular/core'; | |||
import { CatalogComponent } from './catalog.component'; | |||
import { routing } from './catalog.routes'; | |||
import { CatalogComponent } from './catalog.component'; | |||
import { routing } from './catalog.routes'; | |||
import { CatalogService } from './catalog.service'; | |||
@NgModule({ | |||
imports: [routing], | |||
declarations: [CatalogComponent] | |||
declarations: [CatalogComponent], | |||
providers: [CatalogService] | |||
}) | |||
export class CatalogModule { } |
@ -0,0 +1,15 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { DataService } from '../shared/services/data.service'; | |||
@Injectable() | |||
export class CatalogService { | |||
private catalogUrl: string = 'http://eshopcontainers:5101/api/v1/catalog/items'; | |||
constructor(private service: DataService) { | |||
} | |||
getCatalog(){ | |||
return this.service.get(this.catalogUrl); | |||
} | |||
} |
@ -0,0 +1,6 @@ | |||
import {CatalogItem} from './catalogItem.model'; | |||
export class Catalog { | |||
constructor(public pageIndex: number, public data: CatalogItem[], public pageSize: number, public count: number) { | |||
} | |||
} |
@ -0,0 +1,3 @@ | |||
export class CatalogItem { | |||
constructor(public Id: string, public Name: string, public Description: string, public Price: number, public PictureUri: string, public CatalogBrandId: number, public CatalogBrand: string, public CatalogTypeId: number, public CatalogType: string) { } | |||
} |
@ -1,13 +0,0 @@ | |||
import { User } from './user.model'; | |||
// todo: I dont think user follows angular style guides | |||
describe('User Model', () => { | |||
it('has displayName', () => { | |||
let userModel: User = {displayName: 'test', roles: ['1']}; | |||
expect(userModel.displayName).toEqual('test'); | |||
}); | |||
it('has displayName', () => { | |||
let userModel: User = {displayName: 'test', roles: ['admin']}; | |||
expect(userModel.roles[0]).toEqual('admin'); | |||
}); | |||
}); |
@ -1,207 +0,0 @@ | |||
// CREDIT: | |||
// The vast majority of this code came right from Ben Nadel's post: | |||
// http://www.bennadel.com/blog/3047-creating-specialized-http-clients-in-angular-2-beta-8.htm | |||
// | |||
// My updates are mostly adapting it for Typescript: | |||
// 1. Importing required modules | |||
// 2. Adding type notations | |||
// 3. Using the 'fat-arrow' syntax to properly scope in-line functions | |||
// | |||
import 'rxjs/add/operator/map'; | |||
import 'rxjs/add/operator/catch'; | |||
import 'rxjs/add/operator/finally'; | |||
import { Injectable } from '@angular/core'; | |||
import { Http, Response, RequestOptions, RequestMethod, URLSearchParams } from '@angular/http'; | |||
import { Observable } from 'rxjs/Observable'; | |||
import { Subject } from 'rxjs/Subject'; | |||
import { HttpErrorHandlerService } from './http-error-handler.service'; | |||
// Import the rxjs operators we need (in a production app you'll | |||
// probably want to import only the operators you actually use) | |||
// | |||
export class ApiGatewayOptions { | |||
method: RequestMethod; | |||
url: string; | |||
headers: any = {}; | |||
params = {}; | |||
data = {}; | |||
} | |||
@Injectable() | |||
export class ApiGatewayService { | |||
// Define the internal Subject we'll use to push the command count | |||
private pendingCommandsSubject = new Subject<number>(); | |||
private pendingCommandCount = 0; | |||
// Provide the *public* Observable that clients can subscribe to | |||
private pendingCommands$: Observable<number>; | |||
constructor(private http: Http, private httpErrorHandler: HttpErrorHandlerService) { | |||
this.pendingCommands$ = this.pendingCommandsSubject.asObservable(); | |||
} | |||
// I perform a GET request to the API, appending the given params | |||
// as URL search parameters. Returns a stream. | |||
get(url: string, params: any): Observable<Response> { | |||
let options = new ApiGatewayOptions(); | |||
options.method = RequestMethod.Get; | |||
options.url = url; | |||
options.params = params; | |||
return this.request(options); | |||
} | |||
// I perform a POST request to the API. If both the params and data | |||
// are present, the params will be appended as URL search parameters | |||
// and the data will be serialized as a JSON payload. If only the | |||
// data is present, it will be serialized as a JSON payload. Returns | |||
// a stream. | |||
post(url: string, data: any, params: any): Observable<Response> { | |||
if (!data) { | |||
data = params; | |||
params = {}; | |||
} | |||
let options = new ApiGatewayOptions(); | |||
options.method = RequestMethod.Post; | |||
options.url = url; | |||
options.params = params; | |||
options.data = data; | |||
return this.request(options); | |||
} | |||
private request(options: ApiGatewayOptions): Observable<any> { | |||
options.method = (options.method || RequestMethod.Get); | |||
options.url = (options.url || ''); | |||
options.headers = (options.headers || {}); | |||
options.params = (options.params || {}); | |||
options.data = (options.data || {}); | |||
this.interpolateUrl(options); | |||
this.addXsrfToken(options); | |||
this.addContentType(options); | |||
// TODO add auth token when available | |||
// this.addAuthToken(options); | |||
let requestOptions = new RequestOptions(); | |||
requestOptions.method = options.method; | |||
requestOptions.url = options.url; | |||
requestOptions.headers = options.headers; | |||
requestOptions.search = this.buildUrlSearchParams(options.params); | |||
requestOptions.body = JSON.stringify(options.data); | |||
let isCommand = (options.method !== RequestMethod.Get); | |||
if (isCommand) { | |||
this.pendingCommandsSubject.next(++this.pendingCommandCount); | |||
} | |||
let stream = this.http.request(options.url, requestOptions) | |||
.catch((error: any) => { | |||
this.httpErrorHandler.handle(error); | |||
return Observable.throw(error); | |||
}) | |||
.map(this.unwrapHttpValue) | |||
.catch((error: any) => { | |||
return Observable.throw(this.unwrapHttpError(error)); | |||
}) | |||
.finally(() => { | |||
if (isCommand) { | |||
this.pendingCommandsSubject.next(--this.pendingCommandCount); | |||
} | |||
}); | |||
return stream; | |||
} | |||
private addContentType(options: ApiGatewayOptions): ApiGatewayOptions { | |||
if (options.method !== RequestMethod.Get) { | |||
options.headers['Content-Type'] = 'application/json; charset=UTF-8'; | |||
} | |||
return options; | |||
} | |||
private addAuthToken(options: ApiGatewayOptions): ApiGatewayOptions { | |||
options.headers.Authorization = 'Bearer ' + JSON.parse(sessionStorage.getItem('accessToken')); | |||
return options; | |||
} | |||
private extractValue(collection: any, key: string): any { | |||
let value = collection[key]; | |||
delete (collection[key]); | |||
return value; | |||
} | |||
private addXsrfToken(options: ApiGatewayOptions): ApiGatewayOptions { | |||
let xsrfToken = this.getXsrfCookie(); | |||
if (xsrfToken) { | |||
options.headers['X-XSRF-TOKEN'] = xsrfToken; | |||
} | |||
return options; | |||
} | |||
private getXsrfCookie(): string { | |||
let matches = document.cookie.match(/\bXSRF-TOKEN=([^\s;]+)/); | |||
try { | |||
return (matches && decodeURIComponent(matches[1])); | |||
} catch (decodeError) { | |||
return (''); | |||
} | |||
} | |||
private addCors(options: ApiGatewayOptions): ApiGatewayOptions { | |||
options.headers['Access-Control-Allow-Origin'] = '*'; | |||
return options; | |||
} | |||
private buildUrlSearchParams(params: any): URLSearchParams { | |||
let searchParams = new URLSearchParams(); | |||
for (let key in params) { | |||
if (params.hasOwnProperty(key)) { | |||
searchParams.append(key, params[key]); | |||
} | |||
} | |||
return searchParams; | |||
} | |||
private interpolateUrl(options: ApiGatewayOptions): ApiGatewayOptions { | |||
options.url = options.url.replace(/:([a-zA-Z]+[\w-]*)/g, ($0, token) => { | |||
// Try to move matching token from the params collection. | |||
if (options.params.hasOwnProperty(token)) { | |||
return (this.extractValue(options.params, token)); | |||
} | |||
// Try to move matching token from the data collection. | |||
if (options.data.hasOwnProperty(token)) { | |||
return (this.extractValue(options.data, token)); | |||
} | |||
// If a matching value couldn't be found, just replace | |||
// the token with the empty string. | |||
return (''); | |||
}); | |||
// Clean up any repeating slashes. | |||
options.url = options.url.replace(/\/{2,}/g, '/'); | |||
// Clean up any trailing slashes. | |||
options.url = options.url.replace(/\/+$/g, ''); | |||
return options; | |||
} | |||
private unwrapHttpError(error: any): any { | |||
try { | |||
return (error.json()); | |||
} catch (jsonError) { | |||
return ({ | |||
code: -1, | |||
message: 'An unexpected error occurred.' | |||
}); | |||
} | |||
} | |||
private unwrapHttpValue(value: Response): any { | |||
return (value.json()); | |||
} | |||
} |
@ -1,23 +0,0 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { Observable } from 'rxjs/Rx'; | |||
import { TranslateLoader } from 'ng2-translate/ng2-translate'; | |||
import { MissingTranslationHandler, MissingTranslationHandlerParams } from 'ng2-translate/ng2-translate'; | |||
import { ContentService } from './content.service'; | |||
@Injectable() | |||
export class ApiTranslationLoader implements TranslateLoader { | |||
constructor(private cs: ContentService) { } | |||
getTranslation(lang: string): Observable<any> { | |||
return this.cs.get(lang); | |||
} | |||
} | |||
@Injectable() | |||
export class CustomMissingTranslationHandler implements MissingTranslationHandler { | |||
handle(params: MissingTranslationHandlerParams) { | |||
return params.key; | |||
} | |||
} |
@ -1,13 +0,0 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { DataService } from './data.service'; | |||
@Injectable() | |||
export class ContentService { | |||
constructor(public dataService: DataService) { } | |||
get(lang?: string): any { | |||
return this.dataService.get('api/content?lang=' + (lang ? lang : 'en')); | |||
} | |||
} |
@ -1,17 +1,47 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { Http, Response, RequestOptionsArgs, RequestMethod, Headers } from '@angular/http'; | |||
import { ApiGatewayService } from './api-gateway.service'; | |||
import { Observable } from 'rxjs/Observable'; | |||
import 'rxjs/add/observable/throw'; | |||
import { Observer } from 'rxjs/Observer'; | |||
import 'rxjs/add/operator/map'; | |||
import 'rxjs/add/operator/catch'; | |||
@Injectable() | |||
export class DataService { | |||
constructor(public http: ApiGatewayService) { } | |||
constructor(public http: Http) { } | |||
get(url: string, params?: any) { | |||
return this.http.get(url, undefined); | |||
let options: RequestOptionsArgs = {}; | |||
options.headers = new Headers(); | |||
this.addCors(options); | |||
return this.http.get(url, options).map(((res: Response) => { | |||
return res.json(); | |||
})).catch(this.handleError); | |||
} | |||
post(url: string, data: any, params?: any) { | |||
return this.http.post(url, data, params); | |||
} | |||
private addCors(options: RequestOptionsArgs): RequestOptionsArgs { | |||
options.headers.append('Access-Control-Allow-Origin', '*'); | |||
return options; | |||
} | |||
private handleError(error: any) { | |||
console.error('server error:', error); | |||
if (error instanceof Response) { | |||
let errMessage = ''; | |||
try { | |||
errMessage = error.json().error; | |||
} catch (err) { | |||
errMessage = error.statusText; | |||
} | |||
return Observable.throw(errMessage); | |||
} | |||
return Observable.throw(error || 'Node.js server error'); | |||
} | |||
} |
@ -1,25 +0,0 @@ | |||
// CREDIT: | |||
// The vast majority of this code came right from Ben Nadel's post: | |||
// http://www.bennadel.com/blog/3047-creating-specialized-http-clients-in-angular-2-beta-8.htm | |||
// | |||
// My updates are mostly adapting it for Typescript: | |||
// 1. Importing required modules | |||
// 2. Adding type notations | |||
// 3. Using the 'fat-arrow' syntax to properly scope in-line functions | |||
// | |||
import { Injectable } from '@angular/core'; | |||
import { Router } from '@angular/router'; | |||
@Injectable() | |||
export class HttpErrorHandlerService { | |||
constructor(private _router: Router) { } | |||
handle(error: any) { | |||
if (error.status === 401) { | |||
sessionStorage.clear(); | |||
// window.location.href = 'login'; | |||
this._router.navigate(['Login']); | |||
} | |||
} | |||
} |