ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable - angular4-httpclient

I'm working on autocomplete-search with angular 4. This search bar will get books information from Google Books API. It works fine when I input any search terms. But it causes an error if I remove the entire search term or input a space.This is the error I got
This is my SearchComponent.ts
import { Component, OnInit } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
#Component({
selector: 'app-admin-search',
templateUrl: './admin-search.component.html',
styleUrls: ['./admin-search.component.css']
})
export class AdminSearchComponent implements OnInit {
books: any[] = [];
searchTerm$ = new Subject<string>();
constructor (private bookService: BookService,
private http: HttpClient
) {
this.bookService.search(this.searchTerm$)
.subscribe(results => {
this.books = results.items;
});
}
ngOnInit() {
}
This is my SearchComponent.html
<div>
<h4>Book Search</h4>
<input #searchBox id="search-box"
type="text"
placeholder="Search new book"
(keyup)="searchTerm$.next($event.target.value)"/>
<ul *ngIf="books" class="search-result">
<li *ngFor="let book of books">
{{ book.volumeInfo.title }}
</li>
</ul>
</div>
This is my BookService.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Book } from './book';
import { BOOKS } from './mock-books';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
#Injectable()
export class BookService {
private GoogleBookURL: string = "https://www.googleapis.com/books/v1/volumes?q=";
constructor (private http: HttpClient) { }
search(terms: Observable<string>) {
return terms.debounceTime(300)
.distinctUntilChanged()
.switchMap(term => this.searchEntries(term));
}
searchEntries(searchTerm: string) {
if (searchTerm.trim()) {
searchTerm = searchTerm.replace(/\s+/g, '+');
let URL = this.GoogleBookURL + searchTerm;
return this.http.get(URL);
}
}
}
Can someone help me out? Thanks in advance!

Your method searchEntries returns value (Observable<Response>) only if searchTerm.trim() is true (so it must return non-empty string).
There can be situation that searchEntries will return undefined instead of Obervable<Response> if trim() returns '' (empty string which is false). You can't pass undefined returned from searchEntries into .switchMap(term => this.searchEntries(term));.
For that case your code will look like this:
.switchMap(term => undefined) which is not valid construction.

Related

Fix the TS2345: Argument of type 'HTMLElement' is not assignable to parameter of type 'HTMLInputElement'

I'm trying to setup Google Maps Places Autocomplete in an new Ionic app.
here is the problem. On the first search, I got this error in the console:
TypeError: Cannot read property 'place_id' of undefined
and this error in the terminal:
TS2345: Argument of type 'HTMLElement' is not assignable to parameter of type 'HTMLInputElement'
However, on the second search I get the place_id without any error.
Here is my (simplified) .ts file
import { Component, OnInit } from '#angular/core';
import { google } from "google-maps";
import { Platform } from '#ionic/angular';
#Component({...})
export class AddaddressPage implements OnInit {
autocomplete:any;
constructor(public platform: Platform) {}
ngOnInit() {
this.platform.ready().then(() => {
this.autocomplete = new google.maps.places.Autocomplete(document.getElementById('autocomplete'));
this.autocomplete.setFields(['place_id']);
});
}
fillInAddress() {
var place = this.autocomplete.getPlace();
console.log(place);
console.log(place.place_id);
}
}
and the input I use:
<input id="autocomplete" type="text" (change)="fillInAddress()" />
How should I proceed ?
After playing around, here is the trick! ViewChild and Ion-input are needed.
.html
<ion-input #autocomplete type="text"></ion-input>
.ts
import { Component, OnInit, ViewChild } from '#angular/core';
import { google } from "google-maps";
import { Platform } from '#ionic/angular';
#Component(...)
export class AddaddressPage implements OnInit {
googleAutocomplete:any;
#ViewChild('autocomplete') autocompleteInput: ElementRef;
constructor(public platform: Platform) { }
ngOnInit() {
this.platform.ready().then(() => {
this.autocompleteInput.getInputElement().then((el)=>{
this.googleAutocomplete = new google.maps.places.Autocomplete(el);
this.googleAutocomplete.setFields(['place_id']);
this.googleAutocomplete.addListener('place_changed', () => {
var place = this.googleAutocomplete.getPlace();
console.log(place);
console.log(place.place_id);
});
})
});
}
}

List users with Angular Rest-APi reqres

I am trying to list users from a REST-API reqres. but when I click the button to list users, I get
Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
I can list users in console but not in page. I read that last Angular version does not read map function and I do not know why I am getting this error.
This is my users.component.ts file:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import 'rxjs/add/operator/map'
#Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css'],
})
export class UsersComponent implements OnInit {
users: any;
constructor(private http: HttpClient) {}
ngOnInit() {}
public getUsers() {
this.users = this.http.get('https://reqres.in/api/users')
}
}
And this is my users.component.html file:
<button (click)="getUsers()">list users</button>
<div *ngFor="let user of users">
{{ user | json }}
</div>
this.http.get() returns an Observable. When you assign this.users = this.http.get(), the users object will be an Observable and ngFor won't be able to iterate over it.
ngOnInit() {
this.users = []; // to prevent ngFor to throw while we wait for API to return data
}
public getUsers() {
this.http.get('https://reqres.in/api/users').subscribe(res => {
this.users = res.data;
// data contains actual array of users
});
}

Angular v4 typescript rest api

I'm new in angular v4 and I have problems with parse nested object from de rest api,Its a very simple case I'll let the code speak for me, its a really basic code but I dont find the error.
The error
ERROR TypeError: Cannot read property 'email' of undefined
at Object.eval [as updateRenderer] (CarDetailComponent.html:7)
at Object.debugUpdateRenderer [as updateRenderer] (core.es5.js:12679)
at checkAndUpdateView (core.es5.js:12058)
at callViewAction (core.es5.js:12367)
at execComponentViewsAction (core.es5.js:12313)
at checkAndUpdateView (core.es5.js:12059)
at callViewAction (core.es5.js:12367)
at execComponentViewsAction (core.es5.js:12313)
at checkAndUpdateView (core.es5.js:12059)
at callWithDebugContext (core.es5.js:13041)
Classes
import { JsonObject, JsonProperty } from "json2typescript";
#JsonObject
export class User {
#JsonProperty("id", Number)
public id: number = undefined;
#JsonProperty("email", String)
public email: string = undefined;
constructor(
) { }
}
#JsonObject
export class Car {
#JsonProperty("id", Number)
public id: number = undefined;
#JsonProperty("brand", String)
public brand: string = undefined;
#JsonProperty("createdby", User)
public createdBy: User = undefined
constructor(
) { }
}
(Service: car.service.ts)
import { Injectable } from '#angular/core';
import { Car } from "app/car/car";
import { Observable } from "rxjs";
import 'rxjs/Rx';
import { Http } from "#angular/http";
#Injectable()
export class CarService {
private baseHost: string = "url/to/api";
constructor(private _http: Http) { }
get(id: number) {
return this._http.get(this.baseHost + id + '/show.json')
.map(data => data.json()).toPromise();
}
}
(Component: car-detail.component.ts)
import { Component, OnInit } from '#angular/core';
import { CarService } from "app/car/car.service";
import { Car, User } from "app/car/car";
import { JsonConvert } from "json2typescript"
#Component({
selector: 'app-car-detail',
templateUrl: './car-detail.component.html',
styleUrls: ['./car-detail.component.css']
})
export class CarDetailComponent implements OnInit {
public car:Car = new Car();
public createdBy:User = new User();
constructor(private _carService: CarService) { }
getCar(){
return this._carService
.get(1)
.then(
data =>
{this.car = JsonConvert.deserializeString(JSON.stringify(data), Car);
this.createdBy = JsonConvert.deserializeString(JSON.stringify(data.createdby), User);
console.log(data,JSON.stringify(data))
});
}
ngOnInit() {
this.getCar();
}
}
(car-detail.html)
<p>
car-detail works!
</p>
<p>
{{car.brand}} print: FORD
</p>
<p>
{{car.createdBy.email}} print: email#email.com //It's ok but this line is the problem
</p>
<p>
{{createdBy.email}} print: email#email.com
</p>
The component creates a new Car and assigns it to its car field. The createdBy field of this car object is set to undefined. And the template tries to display the property email of the car's createdBy, which is undefined. Hence the error.
Use
{{car.createdBy?.email}}
or
<p *ngIf="car.createdBy">
{{ car.createdBy.email }}
</p>
to avoid the error.

Route Parameters in Angular 2 and Express API (Get Single Post)

I'm trying to return a single article from a Express API using Angular 2.
The Express app has this section of code for single get request:
router.get('/articles/:articleId', (req, res) => {
let articleId = req.params.articleId;
Article.findById(articleId, (err, article) => {
if(err) {
res.send(err);
} else {
res.json(article)
}
});
});
If I do console.log(article) it returns the whole JSON object in the terminal so it's working.
Next, the Article Service looks like this:
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class ArticleService {
constructor( private http:Http ) {
console.log('Article service initialized...')
}
getArticles() {
}
getArticle(id) {
return this.http.get('http://localhost:3000/api/articles/'+id)
.map(res => res.json());
}
addArticle(newArticle){
}
deleteArticle(id){
return this.http.delete('http://localhost:3000/api/articles/'+id)
.map(res => res.json());
}
}
With the code above the deleteArticle(id) works.
And finally, the ArticleDetailComponent looks like this:
import { Component, OnInit } from '#angular/core';
import { ArticleService } from '../services/article.service';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { ArticleComponent } from '../article/article.component'
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'app-articledetail',
templateUrl: './articledetail.component.html',
styleUrls: ['./articledetail.component.css']
})
export class ArticleDetailComponent implements OnInit {
article: ArticleComponent;
title: string;
text: string;
constructor(
private router: Router,
private route: ActivatedRoute,
private articleService:ArticleService){
}
ngOnInit() {
var id = this.route.params.subscribe(params => {
var id = params['id'];
this.articleService.getArticle(id)
.subscribe(article => {this.article = article});
console.log(id) //returns article id correctly
});
}
}
The articledetail.component.html looks like this:
<div class="article-container">
<div class="col-md-12">
<h2>{{article.title}}</h2>
{{article.text}}
<br><br>
</div>
</div>
When I run the application I can get a list of articles and delete articles by Id, but I can't get single articles to be displayed in the ArticleDetailComponent.
If I do console.log(id) within the ArticleDetailComponent it shows the article id, but I can't get the JSON object in the response and show it in the HTML.
Could somebody please tell me what's missing?
Thanks
I actually see where your mistake is.. you need to initialize article to empty object, because angular is probably throwing errors in the console that it cannot find article.title. The error is probably: cannot find title of undefined. And when angular throws an error like that the whole app freezes, and you cannot do anything. So initialize article like this:
article: any = {} and it will work
The other alternative would be to use the "safe operator" (?) in the template like
{{article?.title}}. This prevents the error, so if article is undefined it wont throw the exception, but its not a good practice rly
The third alternative would be to add *ngIf on the HTML which is throwing errors if article is undefined. Like this:
<div class="article-container" *ngIf="article">
<div class="col-md-12">
<h2>{{article.title}}</h2>
{{article.text}}
<br><br>
</div>
</div>

Search Pipe fails when getting data from MongoDB rather than Mock Data in Angular2

Today I am trying to switch from using mock data stored in a const to using the same data stored on my local MongoDB, but I'm getting the error:
Uncaught (in promise): Error: Error in ./FoodListComponent class FoodListComponent - inline template:2:30 caused by: Cannot read property 'filter' of undefined TypeError: Cannot read property 'filter' of undefined
at SearchPipe.transform (search.pipe.ts:15)
The error occurs because of a search pipe on my *ngFor # inline template:2:30
<div *ngFor="let food of foods | searchPipe: 'mySearchTerm'">
The error message is especially odd to me because the service is returning an Observable, not a Promise.
If I remove that search pipe then every thing works fine, but I have no search functionality. It's as if the template is compiling before the data gets there. How can I correct this?
food-list.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Food } from '../../../interfaces/diet/food'
import { FoodsService } from '../../../services/foods/foods.service';
#Component({
selector: 'food-list',
templateUrl: './food-list.component.html',
styleUrls: ['./food-list.component.scss'],
providers: [ WorkingDataService, FoodsService ]
})
export class FoodListComponent implements OnInit, OnDestroy {
foods: Food[];
constructor ( private _foodsService: FoodsService) { }
ngOnInit(): void {
// this._foodsService.getFoods().subscribe(foods => this.foods = foods); // this worked fine
this._foodsService.getMongoFoods().subscribe(foods => this.foods = foods);
}
}
foods.service.ts
import { Injectable } from '#angular/core';
import { Food } from '../../interfaces/diet/food'
import { FOODS } from './mock-foods';
import { Observable } from "rxjs/Rx";
import { Http, Response } from '#angular/http';
#Injectable()
export class FoodsService {
baseURL: string;
constructor(private http: Http) {
this.baseURL = 'http://localhost:3000/'
}
getFoods(): Observable<Food[]> { // this worked with my search pipe
return Observable.of(FOODS); // I'm returning an observable to a const
}
getMongoFoods(): Observable<Food[]>{
return this.http.get(this.baseURL + 'api/foods')
.map(this.extractData)
.catch(this.handleError);
}
// ... standard response and error handling functions
}
search.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'searchPipe',
pure: false
})
export class SearchPipe implements PipeTransform {
transform(foods: any[], mySearchTerm: string): any[] {
let mySearchTerm = mySearchTerm.toUpperCase();
foods = foods.filter(food => { // The failure hits here because foods isn't defined yet
// my filter logic
});
}
}
Until your observable resolves itself, your foods array is undefined to start with in food-list.component.ts because you haven't initialised it:
foods: Food[];
if you change that to
foods: Food[] = [];
it should work.
Alternatively you can do a check for undefined at the start of your pipe, something like:
if (!foods) return foods;