I'm facing a problem with Angular at the moment.
I want to read data from my server API and want to display it with *ngfor in a html document.
I can receive the data from the API, but i can't display it.
I took the example code from the tour of heroes tutorial and changed it:
The data gets through to my angular app. I can console.log it and see it in chrome development console.
I tried to display other data that I get from my api and it is working. You can see the data commented out in heroes.components.ts.
Who can help me with this?
If you want to see some more of the code like imports please tell me. But i guess everything needed imported as there are no error messages, i can get the data from my api and i can display some data (sadly not the data i need).
I tried several ideas to solve this from some other posts, but can't get it working.
Here are some Code Snippets:
This is my hero.service.ts
imports...
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { HttpResponse } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';
import { Hero } from '../model/hero';
import { MessageService } from '../message.service';
import { Response } from '#angular/http/src/static_response';
getHeroes(): Observable<Hero[]> {
console.log("GET HEROES IN HEROES.SERVICE");
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(Hero => console.log(`fetched heroes: `)),
catchError(this.handleError('getHeroes', []))
);
//I also tried to just use return this.http.get<Hero[]>(this.heroesUrl);
This is my
heroes.components.ts
import { Component, OnInit } from '#angular/core';
import { Hero } from '../../model/hero';
import { HeroService } from '../hero.service';
import { CommonModule } from '#angular/common';
import { Pipe } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Response } from '#angular/http/src/static_response';
// For use of map
import 'rxjs/Rx';
#Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Observable<Hero[]>;
// I tried to display some data
// heroes: any[] = [
// {
// "name": "Douglas Pace"
// }
// ];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
// undefined
console.log("ONINIT");
console.log(this.heroes);
}
getHeroes(): void {
console.log("GET HEROES IN HEROES.COMPONENT");
this.heroService.getHeroes()
.subscribe(
function(response: Hero[]) {
console.log("RESPONSE IN HEROES COMPONENT");
console.log(this.heroes);
var res = response["data"];
// console.log(res.json());
this.heroes = res;
console.log(this.heroes);
console.log(response["data"]);
},
function(error) {
console.log("Error happened" + error)
},
function() {
console.log("the subscription is completed")
//This shows me the right data.
console.log(this.heroes[5].id);
console.log(this.heroes[5].titel);
console.log(this.heroes[5].name);
console.log(this.heroes[5].vorname);
}
);
}
My html file:
<h2>My Heroes</h2>
<!-- <input type=text ng-model="hero"> -->
// I gave it a try with and without *ngIf="heroes"
<!-- only show the list IF the data is available -->
<div *ngIf="heroes">
<h3>Heroes are available and are displayed</h3>
<li *ngFor="let hero of heroes">
{{hero.name}}
</li>
</div>
<button (click)="button()">
Suchen
</button>
<div *ngIf="heroes">
<table class="heroes">
<tr>
<th>Id</th>
<th>Titel</th>
<th>Nachname</th>
<th>Vorname</th>
</tr>
//I tried async as the data is maybe not available from the
beginning. Also tried async on hero as heroes is created on init
and single heros are added with the function getHeroes();
<tr *ngFor='let hero of heroes | async'>
<a routerLink="/detail/{{hero.id}}">
<td>{{hero.id}}</td>
<td>{{hero.titel}}</td>
<td>{{hero.name}}</td>
<td>{{hero.vorname}}</td>
</a>
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
</tr>
</table>
</div>
<pre>{{heroes | json}}</pre>
If got a hero interface. Should be my model. Only Last and First name are needed.
export interface Hero {
id?: string;
name: string;
titel?: string;
vorname: string;
}
The JSON I returned from my API. Online Json formatter says it is valid json.
{"status":"Success","data":
[{"id":"36","name":"Hero","vorname":"Super","titel":"Dr.",},
{"id":"34","name":"Man","Spider":"Ines","titel":""}],
"message":"Retrieved all HEROES"}
this.heroService.getHeroes()
.subscribe(
function(response: Hero[]) { }
Your problem could be here. Your response is an object with (let's say, interface):
interface DataResponse {
success: string;
data?: Hero[];
}
Because you set response: Hero[] and there's no data property in your Hero interface, response["data"] returns null and you'll never get your data. If you run response.data, you'll probably get an error saying data is not defined in Hero etc...
Change to the following:
this.heroService.getHeroes()
.subscribe((response: DataResponse) => {
this.heroes = response["data"];
});
Your code seems to be ok but i see an error in your json format here
"titel":"Dr.",},
try to remove the comma after Dr and give it a try
"titel":"Dr."},
Related
I am new to Angular and I am stuck. I can't seem to get this to work and I think I'm just making some mistakes on how I'm implementing the Observable. Currently I am using a local json file as my data source, but in my main project I will connect to an external API. I have stripped everything down to make it as basic as possible and still no luck.
Here's campaign.component.ts
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { CampaignService } from '../campaign.service';
#Component({
selector: 'app-campaign',
templateUrl: './campaign.component.html',
styleUrls: ['./campaign.component.css']
})
export class CampaignComponent implements OnInit {
$campaign: Observable<any>;
constructor(
private campaignService: CampaignService
) {}
ngOnInit(): void {
this.getCampaign();
}
getCampaign(): void {
this.campaignService.getCampaign().subscribe((data) => {
this.$campaign = data;
console.log(this.$campaign);
});
}
}
Here's the template html, campaign.component.html
<div *ngIf="($campaign | async) as campaign; else loading">
<!--this never loads-->
{{campaign.shortName}}
</div>
<ng-template #loading>
<!--this is all I see-->
Loading stuff in ngIf...
</ng-template>
<br>
<br>
<!--this works so I know the data loads and that my json file is formatted correctly-->
<p>Outside of ngIf works: {{$campaign.shortName}}</p>
Here's the service, campaign.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { map} from 'rxjs/operators';
const endpoint = 'assets/api.json';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
#Injectable()
export class CampaignService {
constructor(private http: HttpClient) {}
private extractData(res: Response) {
let body = res;
return body || { };
}
getCampaign(): Observable<any> {
const url = endpoint;
console.log(url);
return this.http.get(url).pipe(
map(this.extractData));
}
}
Thanks for taking the time to help with this.
getCampaign(): void {
this.campaignService.getCampaign().subscribe((data) => {
this.$campaign = data;
console.log(this.$campaign);
});
}
The above assigns the data value to the property this.$campaign but you've declared that property to be an observable.
<div *ngIf="($campaign | async) as campaign; else loading">
<!--this never loads-->
{{campaign.shortName}}
</div>
$campaign is not an observable so the async pipe resolves to undefined. The condition is always false.
<!--this works so I know the data loads and that my json file is formatted correctly-->
<p>Outside of ngIf works: {{$campaign.shortName}}</p>
The above works because $campaign was assigned the data value.
<p>Outside of ngIf works: {{($campaign | async)?.shortName}}</p>
You should always use async in the template for observables.
You can simplify the component by assigning the observable in the constructor.
constructor(private campaignService: CampaignService) {
this.$campaign = campaignService.getCampaign();
}
Alternatively, you don't have to use async if you subscribe and assign the data.
<div *ngIf="campaign; else loading">
<!--this never loads-->
{{campaign.shortName}}
</div>
<p>Outside of ngIf works: {{campaign?.shortName}}</p>
I trying getting my last commit from github. I used for this github api.
But I get error:
Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays."
I can getting data from simple json, and I used service and code like below, but now this not work for me.
Link to json:
JSON
If I getteing data from this JSON, then I don't getting error, and I display what I want.
My githubservice:
import { Injectable } from '#angular/core';
import { Http, Response, HttpModule } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { IpostsGithub } from './ipostsGithub'
#Injectable()
export class GithubService {
private _postsURL = "https://api.github.com/repos/objectprogr/Dashboard/git/refs/heads/v1";
constructor(private http: Http) {
}
getPosts(): Observable<IpostsGithub[]> {
return this.http
.get(this._postsURL)
.map((response: Response) => {
return <IpostsGithub[]>response.json();
})
}
private handleError(error: Response) {
return Observable.throw(error.statusText);
}
}
github component:
import { Component, OnInit } from '#angular/core';
import { GithubService } from './github.service';
import { IpostsGithub } from './ipostsGithub';
#Component({
selector: 'app-github',
templateUrl: './github.component.html',
styleUrls: ['./github.component.css'],
providers: [ GithubService]
})
export class GithubComponent implements OnInit {
_postsArray: IpostsGithub[];
user: string;
constructor(private githubService: GithubService, ) {
}
getPost(): void {
this.githubService.getPosts()
.subscribe(
resultArray => this._postsArray= resultArray,
error => console.log("Error :: " + error)
)
}
ngOnInit(): void {
this.getPost();
}
}
And html:
<table class="table">
<thead>
<th>1</th>
</thead>
<tbody>
<tr *ngFor="let post of _postsArray">
<td>{{post.message}}</td>
</tr>
</tbody>
</table>
This is code from error which I getting and, which I found o browser console:
_postsArray: […]
0: {…}
object: {…}
sha: "f0814bea75841ef7488552d29c6e1b8ad849f558"
type: "commit"
url: "https://api.github.com/repos/objectprogr/Dashboard/git/commits/f0814bea75841ef7488552d29c6e1b8ad849f558"
__proto__: Object { … }
ref: "refs/heads/v1"
url: "https://api.github.com/repos/objectprogr/Dashboard/git/refs/heads/v1"
And I dont have idea, how to fixed it?
Sounds like the API returns an object of objects, ngFor only works with iterables such as an array.
Seems like you are getting an Object instead of an Array, and you want to loop over that? Correct me if I'm wrong.
If that's the case this is how you would go about it:
in your .ts file
export class SomeClass {
Object: Object;
constructor() {
...
}
}
In your .html
<div *ngFor="let item of Object.keys(yourObject); let i = index;">
{{item}}={{yourObject[item]}}
</div>
I have a component named 'customer.component.ts'.
In this component's view there is a button called 'Search'.
What I I am doing is calling a web api method on this 'Search' button click which brings the data from sql db.
For this, I have a customer.service.ts file in which I wrote the below code -
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs';
import "rxjs/add/operator/map";
import 'rxjs/add/operator/toPromise';
import { Customer, CustomerContact, CustomerHeaderAndContactsResponse, CustomerSearchRequestObjectClass, CustomerHeaderAndContactsResponse_Read } from '../models/customer';
#Injectable()
export class CustomerService {
constructor(private _http: Http) {
}
baseUrl: string = 'http://localhost/SampleApi/api/Customer/';
searchCustomers(custReqObj: CustomerSearchRequestObjectClass): observable<CustomerHeaderAndContactsResponse_Read> {
let headers = new Headers();
headers.append('Content-Type', 'application/json; charset=utf-8');
return this._http.post((this.baseUrl + 'search-customer'), JSON.stringify(custReqObj), { headers: headers }).map(res => res.json());
}
}
my customer.component.ts has the search click function -
import { Component, OnInit } from '#angular/core';
import { strictEqual } from 'assert';
import { ChangeDetectorRef } from '#angular/core';
import { stringify } from '#angular/core/src/facade/lang';
import { Customer, CustomerContact, CustomerHeaderAndContactsResponse_Read } from '../models/customer';
import { Observable } from 'rxjs/Observable';
import { CustomerService } from '../services/customer.service';
import { ChangeDetectionStrategy } from '#angular/core/src/change_detection/constants';
import { forEach } from '#angular/router/src/utils/collection';
import { AsyncPipe } from '#angular/common';
import { error } from 'util';
import { parse } from 'path';
import { Subscriber } from 'rxjs/Subscriber';
declare var $: any;
#Component({
selector: 'customer',
templateUrl: 'app/customer/views/customer.component.html',
})
export class CustomerComponent implements OnInit {
constructor(private changeDetectorRef: ChangeDetectorRef, private _custSvc: CustomerService) {
}
ngOnInit() {}
customerSearch: CustomerHeaderAndContactsResponse_Read = new CustomerHeaderAndContactsResponse_Read();
onCustomerSearchClick(): void {
this._custSvc.searchCustomers(this.custSearchReqObj).subscribe(
data => {
this.customerSearch = data;
},
err => console.log(err, 'error has occurred'),
() => console.log(this.customerSearch)
);
console.log(this.customerSearch);
}
And below is my model class -
export class CustomerHeaderAndContactsResponse_Read
{
custHeader:Customer[];
custContact:CustomerContact[];
}
Both Customer and CustomerContact classes contain some properties.
And finally here is my template where I am trying to iterate through the object the table rows simply don't display any data. I have used async (AsyncPipe) also but not helping much.
<tr *ngFor="let custItem of customerSearch.custHeader | async; let rowIndex = index">
<td>
<a (click)="onCustomerItemDetailsClick(custItem.acCustomerName, rowIndex)" class="btn">{{custItem.acCustomerName}}</a>
</td>
<td>{{custItem.acCountryId}}</td>
<td>{{custItem.acAreaId}}</td>
<td>{{custItem.acTel}}</td>
<td>{{custItem.acFax}}</td>
<td>{{custItem.acSalesContact}}</td>
<td>
<a (click)="onCustomerContactItemDeleteClick(rowIndex, 'manage-customer')" class="btn" id="btnIconDelete">
<span class="glyphicon glyphicon-trash"></span>
</a>
</td>
</tr>
Please help as I am not unable to understand what/where I am doing mistake.
Do let me know if more information is required.
Thanks in advance!
nitinthombre1991#gmail.com
EDIT -
Tried with BehaviorSubject approach, but now getting an erro like below -
Observable error
The async pipe is used to bind observables directly to the template. So here's what you can do:
data$: Observable<CustomerHeaderAndContactsResponse_Read>;
search$ = new BehaviourSubject<boolean>(true);
ngOnInit() {
this.data$ = this.search$.switchMap(searchObj => this._custSvc.search...(...));
}
onCustomerSearchClick() {
this.search$.next(true);
}
And the template looks like this
<tr *ngFor="let item of data$ | async>...</tr>
So now every time your search is clicked, it will send a call to the service and the async pipe is taking care of displaying the data in the template
So I figuring out my way around Angular. Just started with a OpenWeather API based application using a simple GET method.
So here is my app.component.ts:
import { Component } from '#angular/core';
import { WeatherService } from './weather.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [WeatherService]
})
export class AppComponent {
title = 'Ng-Weather';
cityName: string;
constructor(private weather: WeatherService) { }
search() {
this.weather.getWeatherbyName(this.cityName);
}
}
As you can guess, the cityName variable is two way binded. The search() function is invoked onclick of a button and the data is passed to the weatherservice. The contents of weather service is:
import { Injectable } from '#angular/core';
import { Http, Response, URLSearchParams } from '#angular/http';
import { Observable } from 'rxjs';
import { Weather } from './weather';
#Injectable()
export class WeatherService {
APIurl = "http://api.openweathermap.org/data/2.5/weather";
Appid = "xxx";
constructor(private Http: Http) { }
getWeatherbyName(name: string): Observable<any> {
let myParams = new URLSearchParams();
myParams.append('q', name);
myParams.append('appid', this.Appid);
// actual http request should look like this: http://api.openweathermap.org/data/2.5/weather?appid=xxx&q=Chennai
return this.Http.get(this.APIurl, { search: myParams})
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
console.log(res.json());
let body = res.json();
return body.data;
}
private handleError(error: Response | any) {
console.error(error.message || error);
return Observable.throw(error.message || error);
}
}
But I get no error in my console or during the compile process. What is being done wrong? Also, how can I map the JSON I get to my class and give back that instance to the app.component?
Following is my class:
export class Weather {
city: String;
max_temp: String;
min_temp: String;
description: String;
}
And this is a sample JSON I receive:
{
"coord":{
"lon":80.28,
"lat":13.09
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04n"
}
],
"base":"stations",
"main":{
"temp":304.15,
"pressure":1008,
"humidity":79,
"temp_min":304.15,
"temp_max":304.15
},
"visibility":6000,
"wind":{
"speed":3.1,
"deg":160
},
"clouds":{
"all":75
},
"dt":1504629000,
"sys":{
"type":1,
"id":7834,
"message":0.0029,
"country":"IN",
"sunrise":1504571272,
"sunset":1504615599
},
"id":1264527,
"name":"Chennai",
"cod":200
}
As you can see all I need is some data from the JSON and not the whole thing.
Your main problem here is that you are not subscribing to the observable that is being produced by your getWeatherbyName function. Observables returned by Http are cold:
Cold observables start running upon subscription, i.e., the observable sequence only starts pushing values to the observers when Subscribe is called. (…) This is different from hot observables such as mouse move events or stock tickers which are already producing values even before a subscription is active.
In order to subscribe to this observable, you can simply update your search function to the following:
search() {
this.weather.getWeatherbyName(this.cityName)
.subscribe();
}
This is by no means the complete solution to your problem - You will want to do something in the subscription, such as assign the information received to properties of your component so that they can be rendered in the UI.
You appear to have other issues in your linked project, but I suggest you ask separate questions on Stack Overflow if needed, or even better, your favorite search engine should be able to help.
Try passing a RequestOptions object to the http get instead:
import { RequestOptions } from '#angular/http';
getWeatherbyName(name: string): Observable<any> {
let myParams = new URLSearchParams();
myParams.append('q', name);
myParams.append('appid', this.Appid);
let options = new RequestOptions({ search: myParams}); //<----- NEW
// actual http request should look like this: http://api.openweathermap.org/data/2.5/weather?appid=xxx&q=Chennai
return this.Http.get(this.APIurl, options) //<<----- NEW
.map(this.extractData)
.catch(this.handleError);
}
I'm stuck here trying to loop the observable object on my users service.
The Chrome's console throws:
error_handler.js:47 EXCEPTION: undefined is not a function
Here's my code:
users.component.ts
import { Component, OnInit } from '#angular/core';
import { UserService } from '../user.service';
import { Observable } from 'rxjs/Rx';
import { User } from '../user';
#Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
people: Observable<User[]>;
constructor( private _userService: UserService) { }
ngOnInit() {
this.people = this._userService.getAll();
console.log(this.people);
}
}
users.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
import { User } from './user';
#Injectable()
export class UserService {
private baseurl: string= 'http://swapi.co/api';
constructor(private http: Http) {
console.log("User service initialized");
}
getAll(): Observable<User[]>{
let users$ = this.http
.get(`${this.baseurl}/people`,{headers: this.getHeaders()})
.map(this.mapUsers);
return users$;
}
private getHeaders(){
let headers = new Headers();
headers.append('Accept', 'application/json');
return headers;
}
mapUsers(response: Response): User[]{
return response.json().results.map(this.toUser);
}
toUser(r:any): User{
let user = <User>({
id: this.extractId(r),
name: r.name
});
console.log('Parsed user'+user.name);
return user;
}
extractId(personData:any){
let extractedId = personData.url.replace('http://swapi.co/api/people/','').replace('/','');
return parseInt(extractedId);
}
}
users.component.html
<ul class="people">
<li *ngFor="let person of people | async " >
<a href="#">
{{person.name}}
</a>
</li>
</ul>
user.ts
export interface User{
id: number;
name: string;
}
When I remove the HTML code from the template, everything works great (no errors on console) so, I guess there's something wrong with 'people' object, and obviously I can't iterative the response. Please guys, a hand would be appreciated here.
The most likely reason is the way you are handling the map callback
getAll(): Observable<User[]>{
let users$ = this.http
.get(`${this.baseurl}/people`,{headers: this.getHeaders()})
.map(this.mapUsers);
}
mapUsers(response: Response): User[]{
return response.json().results.map(this.toUser);
}
toUser() {}
You need to be careful when using this inside callback functions. The context sometimes messes you up. In this case this in .map(this.toUser) does not point to the class instance. You need to bind it, i.e.
let users$ = this.http
.get(`${this.baseurl}/people`,{headers: this.getHeaders()})
.map(this.mapUsers.bind(this));
When you use bind(this) you are saying that any uses of this inside the mapUsers function should be bound to the class instance.
When you use arrow functions, you don't need to worry about this distinction, as it keeps the lexical scope context
let users$ = this.http
.get(`${this.baseurl}/people`,{headers: this.getHeaders()})
.map(res => response.json().results.map(this.toUser));
Also, even passing the toUser function has the same problem, as you are using this.extractId(r). You also need to bind that
mapUsers(response: Response): User[]{
return response.json().results.map(this.toUser.bind(this));
}