Cannot recieve DATA from RESTFUL API using IONIC 2 generated provider - json

So, i'm currently studying Ionic 2 to make hybrid applications. I'm following a course on Udemy but the course's content about HTTP requests to WEB API's is obsolete(it's from the ionic 2 Beta). This is a long question but some of you's who are more experienced on the Ionic 2 framework can just skip to step 8 to save some time. Thanks a lot guys!
I'm trying to retrieve data from this URL:
https: //viacep.com.br/ws/01001000/json/.
It has a space after https:// because stackoverflow won't allot me to post more than one link.
But I'm missing something to save this data on a variable I created.
What I did to this point is:
1) I generated the provider which I called ConnectionService using the CLI ionic generator.
ionic g provider ConnectionService
2) Created a method called getCEP() inside the ConnectionService Provider, which makes an HTTP GET Request
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import 'rxjs/add/operator/map';
/*
Generated class for the ConnectionService provider.
See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
#Injectable()
export class ConnectionService {
constructor(public http: Http) {
console.log('Hello ConnectionService Provider');
}
getCep(): Promise<Response>{
let response: any = this.http.get("https://viacep.com.br/ws/01001000/json/");
console.log("Response: " + response);
let responsePromise: any = response.toPromise();
console.log("ResponsePromise: " + responsePromise);
return responsePromise;
}
}
P.S.: Here you can see i'm loggin in two steps of the request: The first one is the response before I turn it into a Promise, so I can return it to the page. The second one is after i cast it to a Promise using the toPromise() method.
3)In my view I have a button which has the (click) directive calling the method buscarCEP()
<ion-header>
<ion-navbar>
<ion-title>Teste</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<button (click)="buscarCep()">Request CEP</button>
</ion-content>
4.1) My TypeScript file has imported the ConnectionService Provider and named it ConnectionService.
4.2) I declared the ConnectionService inside the #Component directive under the "providers:" label
4.3) I create an instance of Connection Provider that I call conServ on the constructor's declaration. Also I created a variable called CEP, to store the data that I pull from it.
import { Component } from '#angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { ConnectionService } from '../../providers/connection-service';
/*
Generated class for the MenuTest page.
See http://ionicframework.com/docs/v2/components/#navigation for more info on
Ionic pages and navigation.
*/
#Component({
selector: 'page-menu-test',
templateUrl: 'menu-test.html',
providers: [ConnectionService]
})
export class MenuTestPage {
public CEP: any;
constructor(public navCtrl: NavController, public navParams: NavParams, public conServ: ConnectionService) {
}
6)Then I modify the method buscarCEP() so that it gets that conServe instance and calls the getCEP() method which makes an HTTP Request to the URL given above.
buscarCep(): void{
this.conServ.getCep().then(data =>{
console.log("DATA:" + data);
this.CEP = data;
}).catch(err => {
console.log("ERRO: " + err);
});
console.log("CEP: " + this.CEP);
}
PS.: As you can see, i'm logging three steps into the request: The data when the getCEP() method executes, a possible error called 'err' and by the end of it, the variable CEP that I created before and saved the data value to.
7)When I run the application and click on the button, I get the following screen with the console.logs:
Image of ionic page with snips of the Chrome console
8) As you can see, my logs are returning as follows:
8.1) The "Hello ConnectionService Provider" is from the console.log inside the provider's constructor, so the import of the provider is fine and it is being instantiated.
8.2) the "Response: [object Object]" is from the first console.log() inside the getCEP() method in the provider itself.
8.3) the "RespondePromise: [object Object]" is from the second console.log() inside the getCEP() method in the provider itself, after i casted the response to a Promise.
8.4)"CEP: undefined" comes from the console.log inside the buscarCEP() method, which is called after I click on the Request CEP Button
8.5)"DATA:Response with status: 200 OK for URL: https://viacep.com.br/ws/01001000/json/" comes from the console.log() inside the buscarCEP() method.
9) From this i'm taking that the getCEP() method is being able to connect to the URL, hence why the Response and ResponsePromise logs have an Object attached to them. Also the DATA log tells me that i recieved an OK Response from the server. My question is in regard to CEP: Undefined log. I can't seem to store that object in the variable I created.
I know that this is a long one but I wanted to lay all my cards on the board and explain everything as thoroughly as I could because i'm new to this framework.
Any help is appreciated, thank you for your time!

The Response object is stored in this.CEP. The issue is console.log(this.CEP) is called before the response from the HTTP request is returned within then.Promises are asynchronous.You can check the contents by doing console.log(this.CEP) within then.
So if you were to print the data in the html side, use safe navigation operator ?. e.g: {{CEP?.property}}.
A couple of issues with your code:
You should extract the json data from your response object. I suggest you do:
this.CEP = data.json();
If you want to print the contents of the object you can try console.log(JSON.stringify(data,null,2)).

Related

Overriding previous observable

I have an events app which has an all-events component which lists all the created events. When the all-events component is loaded, the events are retrieved from my database.
This is the relevant code from the all-events Typescript file:
eventParams: EventParams;
getAllEventsObs: Observable<PaginatedResult<BeachCleanEvent[]>>;
ngOnInit() {
this.eventParams = new EventParams();
}
getEvents() {
this.getAllEventsObs = this.eventService.getAllEvents(this.eventParams);
console.log("getting events");
}
pageChanged(event: any) {
this.eventParams.pageNumber = event.page;
this.eventService.setEventParams(this.eventParams);
this.getEvents();
}
Here is the html from the all-events component:
<div class="background-img">
</div>
<div class="container">
<div class="events-container" *ngIf="getAllEventsObs | async as getAllEventsObs">
<div class="event-item" *ngFor="let event of getAllEventsObs.result">
<app-event-card [existingEvent]="event"></app-event-card>
</div>
<pagination *ngIf="getAllEventsObs.pagination as eventPagination"
[boundaryLinks]="true"
[totalItems]="eventPagination.totalItems"
[itemsPerPage]="eventPagination.itemsPerPage"
[(ngModel)]="eventPagination.currentPage"
(pageChanged)="pageChanged($event)">
</pagination>
</div>
</div>
The getAllEvents method is just a simple http request. I won't show this code as I know it works correctly.
This is the PaginatedResult class:
export interface Pagination {
currentPage: number;
itemsPerPage: number;
totalItems: number;
totalPages: number;
}
export class PaginatedResult<T> {
result: T;
pagination: Pagination;
}
This is the EventParams class:
export class EventParams {
pageNumber = 1;
pageSize = 4;
}
When the page initially loads, it loads correctly and displays the first four events:
The issue I am having is that when clicking on the next page, it gets stuck and won't load the next four events. No error is displayed on the console but the "getting events" console.log I created in the getEvents method above just keeps firing:
I suspect this is something to do with the way I am consuming the observable in the html code (using the async pipe). How would I go about resolving this? Is this where I should be using the switchMap RXJS operator? If so, how would I use it in this scenario?
You are on the right track... and yes, you should use switchMap :-)
Instead of re-assigning your source observable inside getEvents(), you could simply define it to depend on your params, and just push new params when they change. switchMap will execute the service method each time the params change.
But if the EventService is keeping track of the params anyway, it's probably the simplest to have it expose the events$ based on those params.
If you aren't already, define the params as a BehaviorSubject with default value and add a method for consumers to modify them. Then expose a single events$ observable that represents the events based on the specific params:
service:
private params$ = new BehaviorSubject(new EventParams());
public setEventParams(params) {
this.params$.next(params);
}
public events$ = this.params$.pipe(
switchMap(params => this.getAllEvents(params))
);
component:
events$ = this.eventService.events$;
pageChanged(params) {
// build proper params object if necessary
this.eventService.setEventParams(params);
}
Your component code becomes a lot simpler since it doesn't need to be concerned with managing the observable and keeping track of params.

Pass JSON data from App Component to another component in Angular 6

I have two components,
1. App Component
2. Main Component
app.component.ts
ngOnInit () {
this.httpService.get('./assets/batch_json_data.json').subscribe(data => {
this.batchJson = data as string [];
}
I am able to get the JSON from a file into 'batchJson' and need to pass this to my main component for further operations.
There is no event or anything that triggers this.
I have not implemented anything yet, I am trying to read #Input, #Output etc but do not understand how it works and need to go through it some more.
I have just declared basics in the main.component.ts
import { Component, OnInit, ViewChild, Input } from '#angular/core';
import { AppComponent } from '../app.component';
export class MainComponent implements OnInit {
}
Please help me out, I am an absolute rookie in Angular and am unable to try anything because my concepts are not clear and I did browse Stack Overflow, the answers are not matching my requirements.
One solution could be to use a public BehaviorSubject in your app.component.ts.
public batchJson$ = new BehaviorSubject<JSON>(null);
ngOnInit () {
this.httpService.get('./assets/batch_json_data.json').subscribe(data => {
this.batchJson = data as string [];
this.batchJson$.next(JSON.parse(data));
}
Then in your main.component.ts
constructor(private _appComponent : AppComponent )
ngOnInit(){
this._appComponent.batchJson$.subscribe((data)=>{
if(data != null){
//set data to local variable here
}
})
}
Typically I store this kind of logic in a Service, using this in a component will definitely get you pointed in the right direction to learning this concept. Preferably your component should be responsible for interacting with the UI and rendering data, while your services handle retrieving and distributing data.
you can implement common service which does all related http operations and you can inject this service in any component u want and read the json.
Make sure you return the http.get and you subscribe to it where ever you call this method.
If you are not aware of services , you can read about creating and injecting services in angular
You can use rxjs subject to emit the data through out the app and fetch it anywhere by using subject.getValue() method.
First of all you should spare time on understanding the concept of any technology before you start working on it. Else you would be spending most of the time seeking help.
I had created demo here - https://stackblitz.com/edit/angular-lko7pa. I hope it will help you out.

what would be the data type in this json like data?

I am creating an HTTP request to the back end thru this angular implementation.
I prepared the app.module by first importing the {HttpClientModule} from '#angular/common/http' into the app.module and then adding it to the imports array making it available to the rest of the app.
then, I use the get method on HttpClient to access the data in my server
That's the game plan.
The question is about determining the data type to be returned by the server.
The component has this code... I cut the not-needed stuff for brevity.
#Component(...)
export class MyComponent implements OnInit {
// results: string[]; // I am not sure about this
constructor(private http: HttpClient) {}
ngOnInit(): void {
url = 'http://example.com/authentication/get-username.php';
http
.get<MyJsonData>(url)
.subscribe...
}
My question is..
What should I type-cast where it says ? in the above code?
Little bit more backgrond:
the URL returns something like this:
{"wordpress_username":"admin_joe","user_role":"admin"}
or if no user logged in, this
{"wordpress_username":"","user_role":""}
now, here, we got a a format like {"x":"y", "j":"k"}
this is a json string. but, it is also an object
And this is where my confusion starts.
Would it be better to build an interface for this?
If so, what would be the interface look like?
Using Typescript is preferred for its strong typing also on the level of the Typescript. It is better to have a type for that.
In both cases you have the same shape of your object with two properties wordpress_username and user_role. Your interface can look like this
export interface MyJsonData {
wordpress_username: string,
user_role: string
}
Also if you can have a response with only one property, you can make them optional appending ? at the end of the property name
export interface MyJsonData {
wordpress_username?: string,
user_role?: string
}

angular 2 optimal way to map incoming api json into a array of class objects

I'm working with Angular 2. I'm trying to figure out the correct way to map a incoming json object into a array of class
./book.ts
export class book{
constructor(
public id: number,
public title: string,
public publication_date: string){}
}
./book.service.ts
import { Injectable } from '#angular/core';
import { RequestOptions, URLSearchParams, QueryEncoder, Http, Headers, Response } from '#angular/http';
import { Trip } from './trip';
import {Observable} from 'rxjs/Observable';
import 'rxjs/Rx';
#Injectable()
export class BookService {
constructor(private http: Http) { }
fetchbooks(): Observable<book[]>{
return this.http.get('some_url')
.map((res: Response) => <book[]>res.json().books);
}
}
but the
fetchbooks()
doesn't map the incoming json to the book class object.. so i cant do type check.
is there any other way to do this??
Indeed your thoughts on this matter are correct. Because the objects are created by JSON.parse it really makes no sense to use a class to describe their shape. The instance of tests will of course fail and it results in a misleading API.
However there is a simple solution. Typescript has a notion interfaces which are used to describe the shape of various API but have no run-time manifestation. This is a perfect use case for an interface and not a class. Please note that interfaces in typescript are not at all related to interfaces in languages such as Java.
I am aware that my advice is in conflict with the official style guide on this point. In general it makes some good recommendations but it is wrong on this one. Do not use classes to describe the shape of objects returned to remote services via JSON.
You actually don't need to test the types of the objects coming back if you know and trust the end point that is serving them. Your current approach which asserts the type of the response using a generic is a good approach. Just use interface instead of a class.
you can of course associate specific objects with specific interface types. You could correlate this based on server-side serialized fields so you have the option if you need to but you still wouldn't want to use a class for that on the client.

Dynamic path segment OR 404

I have an app that needs to check with a backend API before rendering 404. The routing flow works something like this:
Request comes in to /{INCOMING_PATH}, and the application attempts to fetch and render data from api.com/pages/{INCOMING_PATH}.
If the API returns 404, then the app should return 404. If not, the data is rendered.
I'm not sold on using for this use case. {INCOMING_PATH} will be dynamic, potentially with slashes and extensions in the path. Is this possible to implement in React Router (with proper SSR behavior too)? If so, how should I proceed?
(This question was originally posted on github by another user. They were requested to post it here as it is a support request. But it doesn't seem they did. I am now stuck on exactly the same issue.)
I've solved this with the React Nested Status module.
I'm using https://github.com/erikras/react-redux-universal-hot-example so this code is geared towards that. See React Nested Status for a more generic solution.
Edits to server.js:
at the top
import NestedStatus from 'react-nested-status';
at the bottom replace:
const status = getStatusFromRoutes(routerState.routes);
if (status) {
res.status(status);
}
res.send('<!doctype html>\n' +
ReactDOM.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}/>));
with:
const repsonse = ReactDOM.renderToString(
<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}/>
);
const status = getStatusFromRoutes(routerState.routes);
if (status) {
res.status(status);
}
const nestedStatus = NestedStatus.rewind();
if (nestedStatus !== 200) {
res.status(nestedStatus);
}
res.send('<!doctype html>\n' + repsonse);
Then in what ever container/component you need to serve a 404 :
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import connectData from 'helpers/connectData';
import { fetchApiData } from 'redux/modules/foo/fetchApiData';
import { NotFound } from 'containers';
#connectData(null, (getReduxState, dispatch, state, params) => {
return dispatch(fetchApiData(params.fooId));
})
#connect(
(reduxState) => ({
fooData: reduxState.foo.data,
})
)
export default class ProductType extends Component {
static propTypes = {
fooData: PropTypes.object,
}
render() {
let content;
// ... whatever your api sends back to indicate this is a 404
if (!this.props.fooData.exists) {
content = <NotFound/>;
} else {
content = (
<div className={styles.productType}>
Normal content...
</div>
);
}
return content;
}
}
Finally replace /src/containers/NotFound/NotFound.js
import React, { Component } from 'react';
import NestedStatus from 'react-nested-status';
export default class NotFound extends Component {
render() {
return (
<NestedStatus code={404}>
<div className="container">
<h1>Error 404! Page not found.</h1>
</div>
</NestedStatus>
);
}
}
I'm not sure what kind of state implementation you are using. But, if you are using redux, then I think the simplest way is to use redux-simple-router. With it, your Routes are synchronized within your state, so you can dispatch action creators to change the router path. I would try to update satate with action creators instead of pushing the state directly from a component. The truth point must be always the state, in your case I would act as follows:
The component that requires to fetch the data will be subscribed to the "dataReducer" which is the isolated state part that this component should care about. Maybe the initial state of dataReducer is an empty array. Then, in componentWillMount you dispatch an action like: dispatch(fetchDataFromApi)) If the response code is 404, then in the action fetchDataFromApi you can dispatch another action, that is just an object like this one:
{type:SET_NOT_FOUND_ERROR}
That action will be handled by the reducer dataReducer, and will return a new state with an object (consider Immutability) that will have a property error, which will be a string with the reason, or whatever you want.
Then, in componentWillReceiveProps method, you, can check if the nextProps have or not have an error. If Error, you can render your error component, or even dispatch an action to go to the error page handled by react-router.
If no error, then you can dispatch an action (thanks to redux-simple-router) to go to the path y