Angular 5 - Replacement for json().token in HttpClient? [duplicate] - javascript

I am trying to change an existing app from using Http to using HttpClient, however i have an error.
So in my service now you can see the new code vs the old code that's been commented out:
constructor(
// private http: Http
private http: HttpClient
) { }
getSidebar() {
// return this.http.get('http://localhost:3000/sidebar/edit-sidebar')
// .map(res => res.json());
return this.http.get('http://localhost:3000/sidebar/edit-sidebar');
}
And in my page.component.ts I have this
this.sidebarService.getSidebar().subscribe(sidebar => {
this.sidebar = sidebar.content; // this does not work now
});
However for the line above which I commented on I get this error now:
Property 'content'
does not exist on type 'Object'.
However if I console.log(sidebar) I get the following:
{_id: "59dde326c7590a27a033fdec", content: "<h1>sidebar here</h1>"}
So what's the issue?
Once again, Http works but HttpClient does not.

You can specify the type that is being returned, using an interface, class, etc. For example, you can use something like the following:
return this.http.get<Sidebar>('http://localhost:3000/sidebar/edit-sidebar');
As an example, Sidebar might be defined as:
interface Sidebar {
_id: string;
content: string;
}
See Typechecking the response from the Angular docs for further information:
...TypeScript would correctly complain that the Object coming back from HTTP does not have a results property. That's because while HttpClient parsed the JSON response into an Object, it doesn't know what shape that object is.

HttpClient parse automatically the JSON response to an Object and the shape of that object is not known, that's why Typescript show this error
alternative solution, using bracket notation:
this.sidebarService.getSidebar().subscribe(sidebar => {
this.sidebar = sidebar["content"];
});

You can assign the variable(sidebar) an interface to explicitely tell what it will get or assign to it so it doesnt throw compile time error.
this.sidebarService.getSidebar().subscribe((sidebar: any) => {
this.sidebar = sidebar.content;
});

you can do this like that with type <any>
if you do this you will not get that error
this.http.get<any>('http://localhost:3000/sidebar/edit-sidebar');

Using bracket notation instead of dot notation to reference sidebar['content'] instead of sidebar.content will solve the issue.
The Type-checking the response section of the Angular docs on HttpClient mention's this, although it's not all that clear:
The subscribe callback above requires bracket notation to extract the
data values. You can't write data.heroesUrl because TypeScript
correctly complains that the data object from the service does not
have a heroesUrl property.

Error Description
ERROR in src/app/home.component.ts(131,20): error TS2339: Property
'someProperty' does not exist on type '{}'.
why Occured ?
I think this error is in service file , whenever you making http call and that call returns any Object(json) or array of Objects (json) , then yours http call should return Observable or Model of that result type
Home.service.ts
connectToServer(data) {
return this.http.post('http://localhost:5000/connect' , data)
}
Suppose , I have above Post http call , where i connect to server by passing data inside 'data' and this request returns me connection Object (Json).But where u seen that i didn't specify observable or model for holding result and if i access the result Object properties from component.ts it will says result.somepropery does not exist on {} .
Solutions
First :
connectToServer(data) : Observable<any> {
return this.http.post('http://localhost:5000/connect' , data)
}
Note : Don't forget to import Observable
Second :
connectToServer(data) : <Connection> {
return this.http.post('http://localhost:5000/connect' , data)
}
In second case you need to define Interface for declaring Connection Object fields and needs to import here.

Related

How to cast Any[] to CustomType so that IntelliSense can be used to access the Properties in Angular/Typescript

We have one Angular service where we are calling the backend and getting the data and displaying it on UI.
export interface UserDataResponse { id: number; userName: string; }
AngularService_Method1() {
return this.http.get<UserDataResponse[]>(this.appUrl + "/Application/Users")
}
AngularService_Method1() is written in Service layer which calls the Backend Users API to get the data. Here we clearly know how the response is. Hence, we are type casting the response as UserDataResponse[].
In Angular Component, I can get the Response and I can use this UserDataResponse[] and I can easily get the IntelliSense for id & userName properties.
We have another method in Service layer as AngularService_Method2() which returns the Array of Objects.
AngularService_Method2(someParameter:String) {
return this.http.get<any[]>(this.appUrl + "/Application/someendpoint/" + someParameter)
}
and here Response can be of anything any[].
It can be anything like below.
For Example - Response 1
[{"Id": 1,"Name": "Abc1","City": "Xyz1"},{"Id": 1,"Name": "Abc1","City": "Xyz1"}]
Response 2 -
[{"EmployeeId": 100,"Email": "pqr#pqr.com","Rank": "aa1"},{"EmployeeId": 101,"Email": "xyz#pqr.com","Rank": "aa2"}]
Question is How to cast Any[] to CustomType so that IntelliSense can be used to access the Properties in Angular/Typescript.
When Response 1 comes I should be able to access the Id,Name,City properties as IntelliSense in my Angular Component.
While subscriping give the type
Eg:-
this.service.method.subscribe((data:Item[])=>//code);

How do I retrieve the name of a generic class of type T in typescript

I am trying to implement the Service Locator pattern in TypeScript.
Here is my code:
//due to only partial knowledge of TypeScript
private static serviceMap: Map<string, any>;
public static get<T>(): T {
// firstly lazily register all of the necessary services if this is the
// first time calling get.
if(this.serviceMap == undefined){
this.init();
}
let service = this.serviceMap.get(T.name) //issue
if(service == undefined){
throw Error("You must register the service before retrieving it.")
}
return service;
}
The problem is on the line marked issue. As you can see I am trying to retrieve the name of the type of class that I am passing to the method. When I try and call T.name I get this error:
TS2693: 'T' only refers to a type, but is being used as a value here.
How can I retrieve the name of the class of type T.
I am very new to TypeScript so I apologise in advance if the answer is super simple.
Service Locator's get method has to receive something based on which can locate the instance.
If you change the signature to this: public static get<T>(fn: Function): T {
then function type has a prop called name and you can use it inside get like this:
let service = Locator.serviceMap.get(fn.name);
The locator getting class instances can be called with:
const classInstance = Locator.get<ClassC>(ClassC);
Check this stackblitz.

ERROR TypeError: Cannot read property 'list' of undefined

Getting the error as mentioned when I am trying to get the response from API call and display the information in the response object in the template.
ERROR TypeError: Cannot read property 'list' of undefined.
In service.ts, I am making a server call. I am using OpenWeatherMap API.
getDetails(cityName, countryCd): Observable<any>{
return this.httpclient.get(`${this.url}${cityName},${countryCd}&APPID=${this.apiKey}`);
}
Object format returned by the API:
{"city":{"id":1851632,"name":"Shuzenji",
"coord":{"lon":138.933334,"lat":34.966671},
"country":"JP",
"cod":"200",
"message":0.0045,
"cnt":38,
"list":[{
"dt":1406106000,
"main":{
"temp":298.77,
"temp_min":298.77,
"temp_max":298.774,
"pressure":1005.93,
"sea_level":1018.18,
"grnd_level":1005.93,
"humidity":87,
"temp_kf":0.26},
"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],
"clouds":{"all":88},
"wind":{"speed":5.71,"deg":229.501},
"sys":{"pod":"d"},
"dt_txt":"2014-07-23 09:00:00"}
]}
In home.component.ts, I am subscribing to the service API call. I am getting the reposnse which is of type 'object'.
I have an array of objects called cities. I am loading the response object from the server as one of the property in each object of the array cities.
this.cities = this.cityService.getCities().slice();
this.cities.forEach(element => {
this.weatherService.getDetails(element.cityName, element.countryCd).subscribe(response => {
element['cityWeather'] = response;});
});
In home.component.html, I am iterating this cities array and the using the response data from the API. The data is displayed properly without any issues.
But in the console I am seeing an error for every iteration of cities array -
ERROR TypeError: Cannot read property 'list' of undefined.
<div class="col-md-4" *ngFor="let city of cities; let i = index">
<span>{{city.cityWeather.list[0].main.temp | kelvinToCelcius}} ℃</span>
<span>{{city.cityWeather.list[0].wind.speed | mtrPersecToKmPerhr}} km/h</span>
</div>
I am relatively new to Angular, need to know what is this error about and how this can be resolved.
Your issue is that in your home component you refer to
<span>{{city.cityWeather.list[0].main.temp | kelvinToCelcius}} ℃</span>
<span>{{city.cityWeather.list[0].wind.speed | mtrPersecToKmPerhr}} km/h</span>
These values may not be defined at all times, so if city.cityWeather is undefined, accessing the .list property would result in the error
Cannot read property 'list' of undefined.
You can fix this by using the Angular safe navigation operator (also known as the Elvis operator).
The Angular safe navigation operator (?.) is a fluent and convenient way to guard against null and undefined values in property paths
-- Angular docs
By changing . to ?., Angular will not throw an error if it can't find the property on the object.
Note that this only works in component templates, and not in TypeScript files.
You can either use The Angular
safe navigation operator (?.)
city.cityWeather?.list[0]?.main.temp
city.cityWeather?.list[0]?.wind.speed
or you can try using Angular async pipe
https://angular.io/api/common/AsyncPipe
Try to change your code like this:
<div class="col-md-4" *ngFor="let city of cities; let i = index">
<span>{{city?.cityWeather?.list[0]?.main?.temp | kelvinToCelcius}} ℃</span>
<span>{{city?.cityWeather?.list[0]?.wind?.speed | mtrPersecToKmPerhr}} km/h</span>
</div>
This may be due the fact that your getCities() request is completed and your getDetails() call is yet to return response which it will give at some point in time due to the asynchronous nature of code. So when the loop execute cityWeather is not available and you get the error. The ? will not throw error but instead it will show data when it will be available.

Pass generic type as type parameter

I am struggling to come up with a solution to a problem i'm facing, where i am trying to use a generic type as a type parameter.
I have the following two classes/types:
UserModel.ts:
export class UserModel{
private _id : string;
public get id() : string {
return this._id;
}
public set id(v : string) {
this._id = v;
}
....
}
HttpResponse.ts:
export class HttpResponse<T>{
private _success : boolean;
public get success() : boolean {
return this._success;
}
public set success(v : boolean) {
this._success = v;
}
private _model : T;
public get model() : T {
return this._model;
}
public set model(v : T) {
this._model = v;
}
}
As you can guess, I am using this to have a generic type to handle http calls easily. The intended use is to call my http method with the HttpResponse type and whatever the expect result type is as the type parameter. For example, when making a user related http call the type parameter would be HttpResponse<UserModel>> or for a resource related call it would be HttpResponse<ResourceModel>>. However, i don't seem to be having any luck when trying this.
In my data service I have a method that POSTS to the server called 'create' and
create<T>(data: any){
//Angular's HttpClient
return this.http.post<T>(data, ...);
}
I then have a service that extends this class and overloads the create method with some extra bits before it calls super.create. I run into the issue at this point because I want to pass in HttpResponse<Type the service should return> which in the following case would be HttpResponse<UserModel>:
create<HttpResponse<UserModel>>(user: UserModel){
//stuff happens here
return super.create<HttpResponse<UserModel>>(user, ...);
}
However, this returns a syntax error at create<HttpResponse<UserModel>>.
I had a look online and found another way to achieve this as something along the lines of:
create<HttpResponse, UserModel>(user: any){
//stuff happens here
return super.create<HttpResponse<UserModel>>(user, ...);
}
But, again, this returns an error stating "HttpResponse is not generic".
The design idea is that by passing in the types in this manner, the json response from the server can be automatically mapped into the appropriate type, making it simple to use the response throughout the application
Any pointers as to where I am going wrong in my design?
A subclass has to maintain the entire interface of its base class (with some exceptions that aren't relevant here). If your data service base class contains a create method that is generic and works for every T, then your subclass cannot override create with a method that only works for T = HttpResponse<UserModel>. Instead, consider (1) defining a new method with a different name or (2) moving the T parameter from the create method to the base class itself, so that when your subclass extends the base class, it can specify the single T that it works with. (Compare to this recent question.) If this doesn't seem to be what you're looking for, then please provide more information.

How to show TypeScript object in HTML (Ionic/Firebase/Angular)

How do I display an object in my HTML file? (Using Ionic) It seems fairly simple, but it won't work. I retrieve the object like this, from a snapshot of my database path:
this.userPath.subscribe(snapshots => {
this.snapshotValue = snapshots.val()
console.log(this.snapshotValue);
})
And the console looks something like this:
http://i.imgur.com/Kk0A6n5.png (I don't have 'reputation' to post images)
I feel like I've tried everything. Like:
<h1>{{snapshotValue}}</h1> //this returns "[object object]"
<h1>{{snapshotValue.navn}}</h1> //this gives me an error: "cannot read property navn of undefined"
<h1>{{snapshotValue[0]}}</h1> //this gives me an error: "cannot read property '0' of undefined.
How do I get the individual values from this object?
you want to use the async pipe with the elvis operator (?) for observables. Below is using just the elvis.
<h1>{{snapshotValue?.navn}}</h1>
The elvis operator checks if the object exists before checking for a property on the object.
In the future instead of using this
this.userPath.subscribe(snapshots => {
this.snapshotValue = snapshots.val()
console.log(this.snapshotValue);
})
you can do
<h1>{{(userPath | async)?.navn}}</h1>
https://angular.io/api/common/AsyncPipe
The async pipe does the subscribe and gets the latest value for you
This happens when you try to render the view before the data is available.Try the following method
In your component declare a boolean variable
private isDataAvailable:boolean=false;
And in your subscribe method make this true once the data is available
this.userPath.subscribe(snapshots => {
this.snapshotValue = snapshots.val()
console.log(this.snapshotValue);
this.isDataAvailable=true;
})
And reneder the template once the data is available using *ngIf
<div *ngIf="isDataAvailable">
<h1>{{snapshotValue}}</h1>
<h1>{{snapshotValue.navn}}</h1>
<h1>{{snapshotValue[0]}}</h1>
</div>
This should do the trick.You can also use async pipe for the same.Find docs here
<h1>{{snapshotValue | json}}</h1> // this should return you a { stringified json }

Categories

Resources