I'm currently bumbling my way through an Angular 4 project. I've manageed to overcome most errors myself, so far, however I cannot figure out this one.
I am trying to use *ngFor (async) to display a list of Observable objects.
However, I get the error "Cannot assign Course[] to Observable< Course[] >", however I feel like my service is returning an Observable< Course[] >.
course-list.component.ts:
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import { CourseCardComponent } from '../course-card/course-card.component';
import { CourseCardService } from '../course-card/course-card.service';
import { CourseCard } from '../course-card/course-card.model';
import { Observable } from 'rxjs/Observable';
#Component({
selector: 'app-course-list',
templateUrl: './course-list.component.html',
styleUrls: ['./course-list.component.css']
})
export class CourseListComponent implements OnInit {
courseCards : Observable<CourseCard[]>;
loaded = false;
constructor(private http:Http, private coursecardService:CourseCardService) { }
ngOnInit() {
this.coursecardService.getCourses()
.subscribe(
courses => {
this.courseCards = courses;
console.log(this.courseCards);
this.loaded = true;
},
err => {
console.log("Error", err);
}
)
}
}
course-card.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { CourseCard } from './course-card.model';
#Injectable()
export class CourseCardService {
// Returns this JSON data:
// [{"firstName":"Jane"},{"firstName":"John"}]
private URL = '/api/getcourses';
constructor (private http: Http) {}
getCourses(): Observable<CourseCard[]> {
return this.http.get(this.URL)
.map((response) => {
let data = response.text() ? response.json():[{}];
if(data) {
return data;
}
}
)
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));
}
}
And the HTML for the course-list component
Courses
<ul>
<li *ngFor="let course of courses|async">
<app-course-card [name]='course.name' [wordcount]=0></app-course-card>
</li>
</ul>
This part does return an Observable<CourseCard[]>:
this.coursecardService.getCourses()
But then you are manually subscribing to it, and inside of the subscribe, courses is of type CourseCard[]. So when you try to assign this.courseCards = courses;, that's when you're getting the type mismatch.
The async pipe will do the subscription for you, so you can change your code to:
ngOnInit() {
this.courseCards = this.coursecardService.getCourses();
}
Nevermind, I read more about the .subscribe method. It returns a subscription object, I just needed to change it to:
ngOnInit() {
this.courseCards = this.coursecardService.getCourses();
}
Is your list properties name is correct? let course of courses or supposed to be
let course of courseCards?
<ul>
<li *ngFor="let course of courseCards|async">
<app-course-card [name]='course.name' [wordcount]=0></app-course-card>
</li>
</ul>
Try like this :
getCourses(): Observable<CourseCard[]> {
return this.http.get(this.URL)
.map((response) => <CourseCard[]>response.json())
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
Related
I have a component with it's controller and template. I'm using a service to load data from a json file and return it to my component. The returned data should be looped within the template of the component. At the moment, the console.log() in my service shows me the correct data, so it loads everything from the json file. The console.log() in my component is wrong. I know, there are few questions with answers, but they didn't help me. Is there a problem with asynchron loading? Any ideas? I also attached the console errors on the bottom of the question.
test.json
[
{
"Id": 1,
"Name": "Item 1"
},
{
"Id": 2,
"Name": "Item 2"
},
{
"Id": 3,
"Name": "Item 3"
}
]
test.component.ts
import { Component, OnInit } from "#angular/core";
import { NavigationService } from "../../../services/navigation.service";
export type NavigationListModel = { Id: number; Name: string };
#Component({
selector: "test",
templateUrl: "./test.component.html",
styleUrls: ["./test.component.scss"]
})
export class TestComponent implements OnInit {
navigationList: Array<NavigationListModel>;
constructor(private _navService: NavigationService) {
this.navigationList = this._navService.loadNavigationList();
console.log(this.navigationList);
}
ngOnInit() {}
}
test.service.ts
import { Injectable } from "#angular/core";
import { Http, Response } from "#angular/http";
import 'rxjs/add/operator/map';
#Injectable()
export class NavigationService {
constructor(private http: Http) {
}
loadNavigationList() {
return this.http
.get("/assets/mock/test/test.json")
.map(data => data.json() as Array<NavigationService>)
.subscribe(data => {
console.log(data);
return data;
});
}
}
test.component.html
<div *ngFor="let item of navigationList">
{{item.Name}}
</div>
ERROR IN CONSOLE and CONSOLE.LOG() results:
When you create service that has return towards HTTP and it is async, you need to subscribe to that function where you call it. As you can see your console log tells you that it is subscription.
Try :
this._navService.loadNavigationList().subscribe((data: any) => {
console.log(data)
});
Or you can recreate your service that will return new Promise with resolve and error, I usually perfer to do it this way
loadNavigationList() {
return new Promise((error, resolve) => {
this.http
.get("/assets/mock/test/test.json")
.map(data => data.json() as Array<NavigationService>)
.subscribe((data) => {
// if api, check for errr
if(data.code == 500) {
error(something);
return
}
resolve(data)
});
});
}
Where you will call it
loadNavigationList().then((data) => {
}, (err) => {
})
You are looping through the navigationList field which is still undefined because of the asynchronous call (the response is not yet received).
To fix this problem you have to make your loadNavigationList() method return an observable of Array and use it in the test componennt with the async pipe inside the template:
test.service.ts:
import { Injectable } from "#angular/core";
import { Http, Response } from "#angular/http";
import 'rxjs/add/operator/map';
#Injectable()
export class NavigationService {
constructor(private http: Http) {
}
loadNavigationList() {
return this.http
.get("/assets/mock/test/test.json")
.map(data => data.json() as Array<NavigationService>);
}
}
test.component.ts:
import { Component, OnInit } from "#angular/core";
import { NavigationService } from "../../../services/navigation.service";
export type NavigationListModel = { Id: number; Name: string };
#Component({
selector: "test",
templateUrl: "./test.component.html",
styleUrls: ["./test.component.scss"]
})
export class TestComponent implements OnInit {
navigationListObservable$: Observable<Array<NavigationListModel>>;
constructor(private _navService: NavigationService) {
this.navigationList$ = this._navService.loadNavigationList();
}
ngOnInit() {}
}
test.component.html
<div *ngFor="let item of (navigationListObservable$ | async)">
{{item.Name}}
</div>
I'm new to Angular 2, so excuse me if the question is silly.
I have to fetch data from the server and display it in the component. The server has some API methods, so I've created the api.service.ts which looks like this:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
const protocol = 'http';
const domain = 'mydomain.ng';
const port = ':4200';
#Injectable()
export class ApiService {
constructor(private http: HttpClient) { }
buildQuery(apiMethod: string) {
return `${protocol}://${domain}${port}/${apiMethod}`;
}
get(apiMethod: string): Observable<Response> {
const query = this.buildQuery(apiMethod);
return this.http.get<Response>(query)
.map(
resp => {
if (resp.ok) {
return resp;
} else { // Server returned an error
// here I need to show UI error in the component
}
}
)
.catch( // Error is on the client side
err => {
// here I need to show UI error in the component
}
);
}
getGeneralReport(): Observable<Response> {
return this.get('generalReport');
}
}
Server API has a lot of methods, so the get() method is designed to perform the actual request and handle common mistakes. Then I will have methods like getGeneralReport() which will call the get method with the parameter specifying which API method should be used.
Also I have a component called general.component.ts where the api.service is injected:
import { Component, OnInit } from '#angular/core';
import { ApiService } from '../../shared/api/api.service';
#Component({
selector: 'app-general',
templateUrl: './general.component.html',
styleUrls: ['./general.component.scss']
})
export class GeneralComponent implements OnInit {
generalInfo: Response;
constructor(private apiService: ApiService) { }
ngOnInit() {
this.apiService.getGeneralReport().subscribe(
data => {
this.generalInfo = data;
// Display the received data
}
);
}
}
There will be more components like general.component which will use the api.service. Now I'm stuck because I need to pop up the UI window in all the components which use api.service if the error occurs in api.service. Is it possible or should I use some different approach?
Yes it is possible, do it like this:
this.apiService.getGeneralReport().subscribe(
data => {
this.generalInfo = data;
// Display the received data
},
err => {
// yourPopupmethod(err)
}
);
and in service throw error. So update your service by adding HandleError method:
handleError(error: Response | any) {
return Observable.throw(new Error(error.status))
}
get(apiMethod: string): Observable<Response> {
const query = this.buildQuery(apiMethod);
return this.http.get<Response>(query)
.map(
resp => {
if (resp.ok) {
return resp;
} else { // Server returned an error
this.handleError(resp);
}
}
)
.catch(
err => {
this.handleError(err);
}
);
}
I have an Angular 2 interface books.ts
export interface Books {
artists: Object;
tracks: Object;
}
This is the my service file where I am using it with http request searchService.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Books } from 'app/pages/search-results/books';
import 'rxjs/add/operator/map'
#Injectable()
export class SearchService {
constructor(private _http:Http) { }
getBook(keyword): Observable<Books[]>{
return this._http.get('https://api.spotify.com/v1/search?q=' + keyword + '&type=track,artist')
.map((response: Response) => <Books[]> response.json());
}
}
And this is my component where I am using interface searchResults.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { SearchService } from 'app/shared/search/search.service';
import { Books } from 'app/pages/search-results/books';
#Component({
selector: 'app-search-results',
templateUrl: './search-results.component.html',
styleUrls: ['./search-results.component.css'],
providers: [SearchService]
})
export class SearchResultsComponent implements OnInit {
keyword: any;
sub: any;
books: Books[];
errMessage: string;
arists: Object;
constructor(private _route: ActivatedRoute, private _router: Router, private _search: SearchService) { }
ngOnInit() {
this.sub = this._route
.queryParams
.subscribe(params => {
// Defaults to 0 if no query param provided.
this.keyword = params['keyword'] || 0;
this.getBooks(this.keyword);
});
//
}
getBooks(value) {
this._search.getBook(value)
.subscribe(
res => {
this.books = res;
console.log(res.artists);
},
error => { this.errMessage = <any>error }
);
}
}
The error comes when I try to console the res.artists. The error says Property 'artists' does not exist on type 'Books[]'. I am new to Angular 2 and doesn't know how to fix that.
The response is looks like
{artists:{limit: 20, item:[]}, tracks:{limit: 20, item:[]}}
I'm not sure but I think you try to get res.artist from collection of books. You can check it by for or e.g res[0].artist to get concrete artist.
getBook function in class SearchService return an array of Books object (Books[])
so, the res in getBooks function in SearchResultsComponent will be an Array of Books.
You can console.log(res) to see detail, if you want access to artists please try with res[0].artists if the res is not an empty array
The problem is that I am getting Object in response and I am assigning it to an Array which is causing the error. I have simply changes the both types to object and it solved my problem.
From this
books: Books[];
To this
books: Books;
I tried to write application based on tour of heroes.
I have Spring application which shares resources and client app which should get this data. I know that resources get to client app, but I can't print it.
import { HeroesService } from './shared/HeroesService';
import { Observable } from 'rxjs/Observable';
import { Hero } from './shared/Hero';
import { OnInit } from '#angular/core';
import { Component } from '#angular/core';
#Component({
selector: 'app',
template: require('app/app.component.html!text')
})
export class AppComponent implements OnInit {
errorMessage: string;
items: Hero[];
mode: string = 'Observable';
firstItem: Hero;
constructor(private heroesService: HeroesService) { }
ngOnInit(): void {
this.getHeroes();
console.log(this.items);
//this.firstItem = this.items[0];
}
getHeroes() {
this.heroesService.getHeroes()
.subscribe(
heroes => this.items = heroes,
error => this.errorMessage = <any>error
);
}
}
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { Hero } from './Hero';
#Injectable()
export class HeroesService {
private heroesUrl = 'http://localhost:8091/heroes';
constructor(private http: Http) { }
getHeroes(): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
console.log(body);
return body || { };
}
private handleError(error: Response | any) {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
In method extract data when I printed by console.log(body.data) I get undefined, but when I printed console.log(body) I get list of objects, therefore I return body instead body.data.
And when I print objects in extractData I get list of objects, but in AppComponent when I print console.log(this.items) I get undefined.
What's going on?
this.getHeroes() returns an Observable which means that you can't get data out of it unless you subscribe to it. Think about it like a magazine subscription, by calling this.getHeroes(), you have registered for the magazine but you don't actually get the magazine until it gets delivered.
In order to get a console.log of the data that comes back in AppComponent, replace the .subscribe block with the following:
.subscribe(
(heroes) =>{
console.log(heroes);
this.items = heroes;
},
error => this.errorMessage = <any>error
);
To further the magazine analogy, inside the subscribe block, you have received the magazine and here we are console logging its contents.
Hope this helps
In my Angular 2 app, I want to start by loading a number of SVGs, before starting the app proper. To do this, I first load a list of svg locations from the server, then fetch each one in turn.
I have a 'loading' property on my AppComponent thats controlling a couple of ngIfs to show/hide some Loading text. Problem is, once the svgs are all loaded, Angular doesn't update the binding in AppComponent.
Why is this? I thought zones took care of this?
The SvgLoader
import {Injectable, Output, EventEmitter, NgZone} from 'angular2/core';
import {Http, Response} from 'angular2/http';
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
const SVG_LIST:string = 'svg/list.json';
#Injectable()
export class SvgLoader {
#Output() state: EventEmitter<string> = new EventEmitter();
private svgs:{[file:string]:string};
constructor(private http:Http){
this.svgs = {};
}
getSvg(path:string):string {
return this.svgs[path];
}
load():void {
this.http.get(SVG_LIST)
.map(res => {
return <Array<string>> res.json().files;
})
.flatMap((files) => Observable.forkJoin(files.map(file => {
return this.http.get('svg/' + file);
})))
.catch(this.handleError)
.mergeAll()
.subscribe(
res => {
let index = res.url.indexOf('svg');
let path = res.url.substring(index);
this.svgs[path] = res.text();
},
error => console.error(error),
() => {
this.state.emit('loaded');
}
);
}
private handleError(error:Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
AppComponent
export class AppComponent {
// On start, list the game sessions
private state:string;
public loading:boolean;
constructor(private svgLoader:SvgLoader){
this.loading = true;
this.state = 'joining';
}
ngOnInit():void {
this.svgLoader.state.subscribe(this.loaded);
this.svgLoader.load();
}
loaded():void {
console.log('loaded');
this.loading = false;
}
}
The template
<div>
<h1 *ngIf="loading">Loading...</h1>
<div *ngIf="!loading">
Loaded
</div>
</div>