List users with Angular Rest-APi reqres - rest

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
});
}

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);
});
})
});
}
}

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

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.

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;

Angular 2: Custom Input Component with Validation (reactive/model driven approach)

I have to create a component with custom input element (and more elements inside the component, but its not the problem and not part of the example here) with reactive / model driven approach and validation inside and outside the component.
I already created the component, it works fine, my problem is that both formControl's (inside child and parent) are not in sync when it comes to validation or states like touched. For example if you type in a string with more then 10 characters, the form control inside the form is stil valid.
Plunkr
//our root app component
import {Component, Input} from '#angular/core'
import {
FormControl,
FormGroup,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
Validators
} from '#angular/forms';
#Component({
selector: 'my-child',
template: `
<h1>Child</h1>
<input [formControl]="childControl">
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
]
})
export class Child implements ControlValueAccessor {
childControl = new FormControl('', Validators.maxLength(10));
writeValue(value: any) {
this.childControl.setValue(value);
}
registerOnChange(fn: (value: any) => void) {
this.childControl.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
#Component({
selector: 'my-app',
template: `
<div>
<h4>Hello {{name}}</h4>
<form [formGroup]="form" (ngSubmit)="sayHello()">
<my-child formControlName="username"></my-child>
<button type="submit">Register</button>
</form>
{{form.value | json }}
</div>
`
})
export class App {
form = new FormGroup({
username: new FormControl('username', Validators.required)
});
constructor() {
this.name = 'Angular2';
}
sayHello() {
console.log(this.form.controls['username'])
}
}
I have no clue how to solve this problem in a proper way
There exists a Validator interface from Angular to forward the validation to the parent. You need to use it and provide NG_VALIDATORS to the component decorator's providers array with a forwardRef:
{
provide: NG_VALIDATORS,
useExisting: CustomInputComponent,
multi: true
}
your component needs to implement the Validator interface:
class CustomInputComponent implements ControlValueAccessor, Validator,...
and you have to provide implementations of the Validator interfaces' methods, at least of the validate method:
validate(control: AbstractControl): ValidationErrors | null {
return this.formGroup.controls['anyControl'].invalid ? { 'anyControlInvalid': true } : null;
}
there is also another to sync the validators when their inputs change:
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}