NWMSDoug NWMSDoug - 1 month ago 12
TypeScript Question

Call not loading in view in Angular2 API

New web developer here (so I apologize in advance for the somewhat sloppy code). I've been asked to create a prototype Inventory Page in Angular2.

As you'll see in the code below. Starting with the EXAMPLEapi.service page, we're making a call to our base back-end API ('https://api.EXAMPLE.com/api/v1/inventory?') which is then passed to an ngOnInit on EXAMPLEapi.component to load up our initial inventory on the page.

From here, you should be able to select various Filtering Values (Min/Max Years, Milage, colors, etc). Those values are passed from the form (EXAMPLEapi.component.html) into the refreshInv() function (EXAMPLEapi.component) which turns the values into params using URLSearchParams before finally being passed back into the API call.

The API is built in a way that simply adding in 'min_price=' at the end of the base URL should reflect on the inventory call accordingly.

The problem is: I'm successfully getting a successful status 200 network call when the onSubmit goes through with the filtering values being passed through. Unfortunately none of the inventory loads back up on the page. It's just blank.

EXAMPLEAPI.service.ts

import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';


@Injectable()
export class EXAMPLEAPIService {
constructor(public http: Http){}

baseUrl = "https://api.EXAMPLE.com/api/v1/inventory?";


getVehicles(params) {
return this.http.get(this.baseUrl, params)
.map(res => res.json());
}

click(): Observable<Data[]> {
console.log(value);
}

log(x) {
console.log(x);
}


EXAMPLE.component.ts

import {Component, OnInit} from 'angular2/core';
import {Http, RequestOptions, URLSearchParams, HTTP_PROVIDERS} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import {EXAMPLEService} from './EXAMPLEapi.service';
import {FormsModule} from 'angular2/forms';


@Component({
selector: 'example-container',
styles: [`
* {
display: inline-block;
max-width: 500px;
vertical-align: top;
}
.invbar {
border: 1px solid black;
padding: 5px;
background-color: #6A7372;
border-radius: 15px;
}
.hotel_container{
border: 1px solid black;
background-color: #6A7372;
max-height: 410px;
}
#colorsBox{
display: block;
}
input[type="checkbox"]{
margin-left: 5px;
}
label{
margin-top: 5px;
color: #FF3B37;
}
`],
templateUrl: 'app/exampleapi.component.html',
providers: [HTTP_PROVIDERS, EXAMPLEAPIService]
})

export class EXAMPLEAPIComponent implements OnInit {
constructor(public EXAMPLEAPIService: EXAMPLEAPIService){


this.minPrice = {
value: ''
};
this.maxPrice = {
value: ''
};
this.minYear = {
value: '2013'
};
this.maxYear = {
value: ''
};

}

// On Page-Load this loads the API Call
ngOnInit() {
this.EXAMPLEService.getVehicles()
.subscribe(data => {
this.vehicles = data.data;
console.log(data.data[0].name);
}
}

// Takes the Filter Parameters and passes them back into the API Call
refreshInv() {
let params = new URLSearchParams();
params.set('min_price', this.minPrice.value);
params.set('max_price', this.maxPrice.value);
params.set('min_year', this.minYear.value);
params.set('max_year', this.maxYear.value);
params.set('ext_color', this.colorsChecked);

let options = new RequestOptions({
search: params
});
console.log(options);
return this.EXAMPLEService.getVehicles(options)
.subscribe(data => this.vehicles = data.data);
}

// Keeps the Filter Queries Actively Refreshing on each interaction
onSubmit(value: any): Observable<Data[]> {
console.log(value);
return this.refreshInv()
};

// Dynamic Checkbox Generation for Color Filters
colors = ['Red', 'Blue', 'Yellow', 'Green', 'White', 'Black'];
colorsMap = {
Red: false,
Blue: false,
Yellow: false,
};
colorsChecked = [];

initColorsMap() {
for (var x=0; x<this.colors.length; x++) {
this.colorsMap[this.colors[x]] = true;
}
}

updateCheckedColors(color, event) {
this.colorsMap[color] = event.target.checked;
}

colorUpdate() {
for(var x in this.colorsMap) {
if(this.colorsMap[x]) {
this.colorsChecked.push[x];
}
}
this.colors = this.colorsChecked;
this.colorsChecked = [];
}

// Dynamic Checkbox Generation for Lift Filters
lifts = ['Lifted', 'Unlifted'];
liftsMap = {
Lifted: false,
Unlifted: false,
};
liftsChecked = [];

initLiftsMap() {
for (var x=0; x<this.lifts.length; x++) {
this.liftsMap[this.maps[x]] = true;
}
}

updateCheckedMaps(map, event) {
this.liftsMap[lift] = event.target.checked;
}

liftUpdate() {
for(var x in this.liftMap) {
if(this.liftMap[x]) {
this.liftsChecked.push[x];
}
}
this.lifts = this.liftsChecked;
this.liftsChecked = [];
}

// Dynamic Checkbox Generation for Transmission Filters
trans = ['Manual', 'Automatic'];
transMap = {
Manual: false,
Automatic: false,
};
transChecked = [];

initTransMap() {
for (var x=0; x<this.trans.length; x++) {
this.transMap[this.trans[x]] = true;
}
}

updateCheckedmap(map, event) {
this.transMap[map] = event.target.checked;
}

transUpdate() {
for(var x in this.transMap) {
if(this.transMap[x]) {
this.transChecked.push[x];
}
}
this.trans = this.transChecked;
this.transChecked = [];
}

// Dynamic Checkbox Generation for Interior Filters
ints = ['Cloth', 'Leather', 'Vinyl'];
intsMap = {
Cloth: false,
Leather: false,
Vinyl: false,
};
intsChecked = [];

initIntMap() {
for (var x=0; x<this.ints.length; x++) {
this.intsMap[this.ints[x]] = true;
}
}

updateCheckedmap(map, event) {
this.intsMap[map] = event.target.checked;
}

intsUpdate() {
for(var x in this.intsMap) {
if(this.intsMap[x]) {
this.intsChecked.push[x];
}
}
this.ints = this.intsChecked;
this.intsChecked = [];
}


}

Answer

First off all, if you want an example of how to set up an angular2 plnkr (using unpkg), look here.

As for your code, Your refreshInv returns a subscription (Observable.subscribe() returns a subscription) which has no use but to unsubscribe.

I would like to suggest a different approach which worked nicely for me and will simplify your code. Actually what your data flow is: 'query' -> 'request' -> 'display'. In rxjs this is easily coded as:

let items$ = jsonParams$
  .map(urlParamsMapper)
  .switchMap(urlParams => http.get(baseUrl, urlParams)
  .map(response => response.json().data);

Where urlParamsMapper gets a json based query (like you specified) and convert it to URLSearchParams.

and jsonParams$ is an observable (in our case a Subject) you can update with the relevant query details:

let jsonParams$: Subject<any> = new BehaviorSubject({
  min_price: '',
  max_price: '',
  ...
  ext_color: ''
});

and each time you update your params, you construct your new json (you have the logic in your code) and then do:

jsonParams$.next(currentParams);

Note in rxjs 4 you should use onNext instead of next.

This will trigger the entire chain for you.

The last step of binding it into your template will be something like:

<my-item-renderer *ngFor="let item; of items$ | async;" [item]="item"></my-item>

Note the async which enables you to bind to an observer.

Now since you'd probably want to keep your service architecture, i.e. keeping the http call in a service, you might want to break it like so:

in the service:

createItemsLoader(jsonParams$: Observable<any>): Observable<Array<any>> {
  return jsonParams$.map(urlParamsMapper)
    .switchMap(urlParams => http.get(baseUrl, urlParams))
    .map(response => response.json().data);
}

and then in your component:

let jsonParams$: Subject<any> = new BehaviorSubject({min_price: '' ...});
let items$ = ItemsService.createItemsLoader(jsonParams$);

refreshInv() {
  .... let jsonParams: any = construct json params ....
  this.jsonParams$.next(jsonParams);
}

And this is the beauty of reactive programming, if you build your piping properly, all you have to do is to send new data down the right pipe and whoever is listening on the other end will get it. The code you had above is still in a procedural 'mindset'.