service in spa to retrieve data from catalog api, project structure simplified
This commit is contained in:
parent
e11bbc2b22
commit
7c04274b5a
@ -1,7 +1,6 @@
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateService } from 'ng2-translate/ng2-translate';
|
||||
|
||||
import { DataService } from './shared/services/data.service';
|
||||
|
||||
@ -18,20 +17,15 @@ import { DataService } from './shared/services/data.service';
|
||||
export class AppComponent implements OnInit {
|
||||
|
||||
|
||||
constructor(private translate: TranslateService, private titleService: Title) {
|
||||
// this language will be used as a fallback when a translation isn't found in the current language
|
||||
translate.setDefaultLang('en');
|
||||
constructor(private titleService: Title) {
|
||||
|
||||
// the lang to use, if the lang isn't available, it will use the current loader to get them
|
||||
translate.use('en');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.translate.get('title')
|
||||
.subscribe(title => this.setTitle(title));
|
||||
|
||||
}
|
||||
|
||||
public setTitle(newTitle: string) {
|
||||
this.titleService.setTitle(newTitle);
|
||||
this.titleService.setTitle('eShopOnContainers');
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<div class="container">
|
||||
<hr>
|
||||
<p class="text-muted">
|
||||
© 2015-2016 {{'title' | translate}} Company
|
||||
© 2015-2016 {{'title'}} Company
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { HttpModule, JsonpModule } from '@angular/http';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateModule, TranslateLoader } from 'ng2-translate/ng2-translate';
|
||||
|
||||
import { PageHeadingComponent } from './directives/page-heading.directive';
|
||||
import { DynamicFormComponent } from './forms/dynamic-form.component';
|
||||
@ -17,11 +16,7 @@ import { HeaderComponent } from './layout/header.component';
|
||||
import { FooterComponent } from './layout/footer.component';
|
||||
// Services
|
||||
import { DataService } from './services/data.service';
|
||||
import { ApiGatewayService } from './services/api-gateway.service';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { HttpErrorHandlerService } from './services/http-error-handler.service';
|
||||
import { ApiTranslationLoader } from './services/api-translation-loader.service';
|
||||
import { ContentService } from './services/content.service';
|
||||
import { UtilityService } from './services/utility.service';
|
||||
import { UppercasePipe } from './pipes/uppercase.pipe';
|
||||
|
||||
@ -34,8 +29,7 @@ import { UppercasePipe } from './pipes/uppercase.pipe';
|
||||
NgbModule.forRoot(),
|
||||
// No need to export as these modules don't expose any components/directive etc'
|
||||
HttpModule,
|
||||
JsonpModule,
|
||||
TranslateModule.forRoot({ provide: TranslateLoader, useClass: ApiTranslationLoader })
|
||||
JsonpModule
|
||||
],
|
||||
declarations: [
|
||||
DynamicFormComponent,
|
||||
@ -54,7 +48,6 @@ import { UppercasePipe } from './pipes/uppercase.pipe';
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
NgbModule,
|
||||
TranslateModule,
|
||||
// Providers, Components, directive, pipes
|
||||
DynamicFormComponent,
|
||||
DynamicFormControlComponent,
|
||||
@ -73,11 +66,8 @@ export class SharedModule {
|
||||
ngModule: SharedModule,
|
||||
providers: [
|
||||
// Providers
|
||||
HttpErrorHandlerService,
|
||||
ApiGatewayService,
|
||||
AuthService,
|
||||
DataService,
|
||||
ContentService,
|
||||
FormControlService,
|
||||
UtilityService
|
||||
]
|
||||
|
@ -6,7 +6,7 @@ var extractCSS = new ExtractTextPlugin('styles.css');
|
||||
var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
|
||||
var devConfig = require('./webpack.config.dev');
|
||||
var prodConfig = require('./webpack.config.prod');
|
||||
var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Production';
|
||||
var isDevelopment = process.env.ASPNETCORE_ENVIRONMENT === 'Development';
|
||||
|
||||
console.log("==========Dev Mode = " + isDevelopment + " ============" )
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
"build:prod": "npm run setprod && npm run clean:dist && npm run build:vendor && npm run build:main",
|
||||
"lint": "npm run tslint \"Client/**/*.ts\"",
|
||||
"docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./Client/",
|
||||
"version": "npm run build",
|
||||
"version": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/common": "2.1.2",
|
||||
@ -55,7 +55,6 @@
|
||||
"core-js": "2.4.1",
|
||||
"font-awesome": "4.6.3",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
"ng2-translate": "4.0.0",
|
||||
"normalize.css": "5.0.0",
|
||||
"preboot": "4.5.2",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
|
Loading…
x
Reference in New Issue
Block a user