Ionic2, Angular2, HTTP and Observables - javascript

After reading almost everything I found about observables, I still don't understand pretty well how they work.
I am doing the http request here:
import { Component, OnInit, Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { NavController } from 'ionic-angular';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
webs: any;
getWebs(): any{
return this.http.get( 'here the url' )
.map((res: Response) => res.json());
}
constructor(public navCtrl: NavController, private http: Http) {}
ngOnInit(){
this.getWebs().subscribe(response => {
this.webs = response;
console.log(this.webs);
});
}
}
On the console, this.webs is correctly printed. That means, the get request ist working fine and I am retrieving the object I want. That is a normal JSON object.
The problem is, on the view, if I try to print some property of the object (the same properties I see on the console) like that
{{ webs.name }}
I get the whole time that error:
Error in ./HomePage class HomePage - caused by: Cannot read property 'name' of undefined
That was sooo easy with Angular 1 :( I already read a lot of tutorials but I can't find any answer to my problem.
Thanks for your help.

The view is shown before the http response is returned.
{{webs?.name}}
should work.
Or do this.webs=getWebs()
and {{webs.name | async}}

It should be something
this.getWebs().then((webs) => {
webs.subscribe(response => {
this.webs = response;
resolve(webs);
console.log(this.webs);
});
})
so after you getWebs do this.This is untested code but you get the logic.
You are calling before you get data.
ngOnInit(){
return new Promise(resolve => {
this.http.get('webs.json')
.map(res => res.json())
.subscribe(webs => {
this.webs = webs;
resolve(this.webs);
});
});
}

Related

Cant understand the error: TypeError: Cannot read property 'subscribe' of undefined

I have written an implementation to obtain data from API calls. However, while testing the functionality I am getting the following errors even before writing any meaningful test cases:
TypeError: Cannot read property 'subscribe' of undefined
at DataComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/dashboard/job/job.component.ts:48:10)
at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:3405:1)
at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:3375:1)
at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:3327:1)
at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:8573:1)
at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:8672:1)
at tickRootContext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9885:1)
at detectChangesInRootView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9910:1)
at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10320:1)
at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/testing.js:243:1)
I'm not sure what I'm missing, any help would be appreciated.
I looked into the following to get some understanding:
Why am I getting a "Failed: Cannot read property 'subscribe' of undefined" while running tests? -> the solution did not work in my case
karma TypeError "Cannot read property 'subscribe' of undefined"
and some other references.
Here are my code files. I have removed certain parts of my code that I felt were not relevant to the issue at hand.
dataService.ts code:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { IData } from '../model/data.model';
import { Observable, throwError } from 'rxjs';
import {catchError, tap} from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) { }
public getData(): Observable<IData[]>{
return this.http.get<IData[]>('url')
.pipe(
tap(data => console.log('Data Received')),
catchError(this.handleError)
);
}
private handleError(err: HttpErrorResponse){
//handle error code
}
}
component I am testing -> data.component.ts code:
import { Component, OnInit, Input} from '#angular/core';
import { IData } from '../../model/data.model';
import {DataService} from '../../service/data.service';
#Component({
selector: 'app-job',
templateUrl: './job.component.html',
styleUrls: ['./job.component.css']
})
export class JobComponent implements OnInit {
#Input() appId: string;
jobs: IData[] = [];
constructor( private dataService: DataService ) {}
ngOnInit(): void {
this.dataService.getData().subscribe({
next: data => {
this.data = data;
},
error: err => this.errorMessage = err
});
}
//other implementation
}
Testing file : data.spec.ts:
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { DataComponent } from './job.component';
import { IData } from 'src/app/model/job.model';
import { DataService } from 'src/app/service/job-data.service';
import { of, Observable } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '#angular/core';
describe('DataComponent', () => {
let component: DataComponent;
let fixture: ComponentFixture<DataComponent>;
let mockDataService ;
let JOBS: IData[];
beforeEach(async () => {
mockJobDataService = jasmine.createSpyObj(['getData']);
await TestBed.configureTestingModule({
declarations: [ DataComponent ],
providers: [
{provide: DataService, useValue: mockDataService}
]
})
.compileComponents()
.then(() => {
DATA = [{id: 'CJH'}];
fixture = TestBed.createComponent(DataComponent);
component = fixture.componentInstance;
fixture.detectChanges(); //updates bindings
});
});
it('should return true',() => {
expect(true).toBe(true);
})
});
When I remove fixture.detectChanges(), the error gets removed. But in my understanding the test cases should work even if I use this call anywhere in my test.
You are creating a spy object with mockJobDataService = jasmine.createSpyObj(['getData']); and are registering it correctly with this {provide: DataService, useValue: mockDataService}. At this point, your component should get created correctly with your fake service injected into it. The problem is that you aren't setting up the expected method call on your fake service.
Something like the following should do the trick as long as you do it before you call fixture.detectChanges(). Since you don't include the shape of the IJob interface, I can't tell you exactly, but I have made it work with typescript through casting it(({} as IJob)).
import { of } from 'rxjs';
mockJobDataService.getData.and.returnValue(of([({} as IJob)]));
This tells jasmine that anything using this fake data service, upon calling the getData method, the return from that method should be of([({} as IJob)])(this is an observable of type IJob array).
As you move forward and write tests that actually are testing your component, you will probably want to move the mocked method and the 'detectChanges' call into each test so that you can provide different fake data for every test.

Can't get deeper into the response data object in subscribe's callback function. Why?

I'm fetching data from RandomUser api with Angular HttpClient. I've created a method in a service calling GET, mapping and returning a Observable. Then I subscribe on this method in a component importing this service and in subscribe's callback I am trying to store the response data in a local variable. The problem is I can't get "deeper" into this response object than:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0];
})
If I'm trying to reach any further element of that response object, and log it to console it I get "undefined". To be precise I cant reference to, for example:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0].name.first;
})
If I store the "data[0]" in a variable first I can get into these unreachable properties. What is the reason of it? Please, help. Let me know what important piece of fundamental JS (or Angular) knowledge I'm not aware of. As far as I know I should be able to do what I am trying to do :)
service looks like these
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class RandomUserService {
url: string = " https://randomuser.me/api/ "
constructor(private http: HttpClient) { }
public getNew(): Observable<any> {
return this.http.get(this.url)
.pipe(map(responseData => {
const returnDataArray = [];
for (const key in responseData) {
returnDataArray.push(responseData[key])
}
return returnDataArray;
}))
}
}
component looks like these:
import { Component, OnInit } from '#angular/core';
import { RandomUserService } from 'src/app/shared/random-user.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-single-character',
templateUrl: './single-character.component.html',
styleUrls: ['./single-character.component.scss']
})
export class SingleCharacterComponent implements OnInit {
userData: object;
fname: string;
constructor(private randomUser: RandomUserService) {
this.randomUser.getNew().subscribe(data => {
this.userData = data[0];
})
}
ngOnInit(): void {
}
}
You are not parsing the returned data correctly in getNew().
The returned data looks like this:
So you need to access the user data like:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0][0]; // note 2nd [0]
})
or for first name:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0][0].name.first;
})
See stackblitz here: https://stackblitz.com/edit/so-http-parse?file=src/app/app.component.ts

Error: Unreachable code detected ionic 3 weather app

Ok, so I am following along the traversy media tutorial on ionic 3, and when i get to the part where you create a provider I get and error that says unreachable code detected in here:
.map((res: Response) => res.json() );
and it also says on typescript
cannot find the name 'map' did you mean 'Map'?
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
#Injectable()
export class WeatherProvider {
apiKey = "89cca14f4ffcd27d602ad5e587f8e17f";
url;
constructor(public http: HttpClient) {
console.log('Hello WeatherProvider Provider');
this.url = "api.openweathermap.org/data/2.5/weather?q=";
}
getWeather(city, country){
return this.http.get(this.url+city+','+country);
.map((res: Response) => res.json() );
}
}
The return statement in getWeather() is making the .map() unreachable. You should make the return statement the last statement in the function.

error TS2339: Property 'map' does not exist on type 'Observable<Response>'

I am trying to get data from mongodb, for which I have written a service. But I am getting an error like error TS2339: Property 'map' does not exist on type 'Observable<Response>'
Please help me to resolve this error...
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class DataService {
result:any;
constructor(private _http: Http) { }
getUsers() {
return this._http.get("/api/users")
.map(result => this.result = result.json().data);
}
}
You have to import and use the map operator differently:
Change
import 'rxjs/add/operator/map';
to
import { map } from 'rxjs/operators';
Then, do
return this._http.get("/api/users")
.pipe(map(result => this.result = result.json().data));
Addiontal suggestion from Vikas
Migrate from the Http service to the HttpClient. see migration guide
To update to HttpClient, you’ll need to replace HttpModule with
HttpClientModule from #angular/common/http in each of your modules,
inject the HttpClient service, and remove any map(res => res.json())
calls, which are no longer needed.

Communicate with backend through Angular

I have a developed a small library (js code) that I want to integrate with angular. the problem is that this library at a certain moment should make request ( ajax ) to push some results to the back-end. How can I make that in angular ? should I develop directives to support binding ?
Sorry I have small knowlege in angular but whats the best way to send data collected by the front end to backend.
thanks
The best way to interact with backend is to use services. For example (the example is for the latest Angular version, previous versions doesn't support HttpClient, it's just Http. I also use toPromise, it's optional, you can deal with observable if you want):
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class YourService {
constructor(
private _http: HttpClient
) { }
public sendDataToServer(data: any): Promise<any> {
return this._http.post(http://fakehost/fakeurl, data)
.toPromise()
.catch(err => console.error(err));
}
And inside your component:
import { Component } from '#angular/core';
import { YourService } from '../../core/your-service.service';
#Component({
selector: 'your-component',
templateUrl: './your-component.component.html',
styles: [``]
})
export class SignUpComponent {
constructor(private _yourService: YourService) {
this.ApiCall();
}
public ApiCall(): void {
this._yourService.sendDataToServer("any data here")
.then(response => {
console.log("Response:", response);
})
.catch(err => console.error(err));
}
}

Categories

Resources