I'm having trouble with a .net core SPA app.
- Results are being passed back by the API call
- the SPA is not handling the results.
here is the pertinent code:
SPA ts:
class TestLibraryItem {
private _apiPath: string;
private _http: HttpClient;
public name: string;
public testResults: TestResult;
constructor(name: string, apiPath: string, http: HttpClient) {
this.name = name;
this._apiPath = apiPath;
this._http = http;
}
RunTests() {
this._http.get<TestResult>(this._apiPath)
.subscribe(result => {
this.testResults = result;
console.log(this.testResults);
console.log(this.testResults.CheckName);
});
}
}
class TestResult {
CheckName: string;
Checks: CheckResult[];
}
class CheckResult {
Test: string;
Pass: boolean;
}
and the console results when RunTests() is fired:
{"CheckName":"Check One","Checks":[{"Test":"Test one","Pass":true},{"Test":"Test two","Pass":true}]}
undefined
As far as I can tell, I'm getting valid json back from the API (indicated by console.log spitting it out, but it is not actually building the object which results in the undefined.
I think your properties in the JSON are parsed from upper case to lower case - CheckName -> checkName. As Javascript/Typescript is a case sensitive language you need to different property names.
Try to log with lower case and also change your property names to start with lower case. It is a common standard in Javascript/Typescript to start function and variable/property names via lower case.
console.log(this.testResults.checkName);
You are getting undefined because this console.log(this.testResults) is fired first
RunTests() {
this._http.get<TestResult>(this._apiPath)
.subscribe(result => {
this.testResults = result;
console.log(this.testResults);
console.log(this.testResults.CheckName === undefined ? '' : this.testResults['CheckName']);
});
}
or use SetTimeOut
RunTests() {
this._http.get<TestResult>(this._apiPath)
.subscribe(result => {
this.testResults = result;
console.log(this.testResults);
setTimeout(()=>{console.log(this.testResults['CheckName'])},2000);
});
}
I had a similar issue i.e. it looked lika valid json response but in fact it was a "text" response. Give the following a try:
getdData(inParams) {
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/json');
// need responseType = text (non object)
return this.http.get(environment.url, {
headers,
responseType: 'text'
});
}
Related
I have created an API using laravel to check if an email existed in database or not.
/**
*
* #param string $email
*/
public function checkUserEmail($email)
{
$userCount = User::where('email', $email);
if($userCount->count()){
return response()->json("true");
}else{
return response()->json("false");
}
}
I have tested this API in postman and it works as expected
Test using Postman
But, when I have use it in frontend it return an object not string!!
Frontend:
checkUserEmail(email : string){
return this.http.get<any>("http://127.0.0.1:8000/api/auth/user-email/"+email);
}
Browser console
In order to get the returned string using HttpClient in Angular you have to subscribe to Observable that returns.
Or you can do the async way using firstValueFrom from RxJS. Notice that response is in string because contains quotes ("false"), is better to just send true or false as boolean.
Here's the example:
public checkUserEmail(email: string): Observable<string> {
return this.http.get("http://127.0.0.1:8000/api/auth/user-email/" + email)
.pipe(
map((response: string) => response as string),
catchError( (errorObject) => {
return throwError(() => errorObject);
}
)
);
}
And now you can call this function subscribing or using async way:
Subscribe way (don't forget to unsubscribe):
checkIfEmailIsInDatabase(email: string): void {
this.checkUserEmail("some#email.com").subscribe(
(response: string) => {
// Do your stuff here, like setting variables or whatever
console.log("Response: ", response);
}
)
}
Async way:
async checkIfEmailIsInDatabase(email: string): Promise<void> {
let responseFromServer: string = await firstValueFrom(this.checkUserEmail(email));
console.log("Response from server: ", responseFromServer);
}
Hope it helps.
In Angular the return type of the httpClient's methods are Observables. So in your client code you have to subscribe to the observable. I also suggest so change "any" to string
I realize that the new updates don't require conversion of a response to JSON.
The tutorial I am following puts this into the api.service.ts
export class ApiService {
constructor(private http: HttpClient){}
messages = []
getMessage(){
this.http.get('http://localhost:3000/newroute').subscribe(res =>{
this.messages = res.json()
})
}
}
However, with the new update, "res.json()" does not work. I get the following error: Property 'json' does not exist on type 'Object'.
How can I solve this?
Here is the data I'm trying to loop through:
var posts = [
{message:'Hello World'},
{greeting:'Whats going on'}
]
Simply do:
getMessage(){
this.http.get('http://localhost:3000/newroute').subscribe(res =>{
this.messages = res;
})
}
Angular allows you to do type checking on the response. You don't have to manually parse JSON.
Here is a code snippet from your example (I recommend using types):
messages: Message[] = []
getMessage() {
this.http.get<Message[]>('http://localhost:3000/newroute').subscribe((res: Message[]) => {
this.messages = res;
});
}
(Update)
Based on your provided data, an interface for Message could look like this:
export interface Message {
[s: string]: string;
}
However, if "Messages" can only have specific keys like "message" and "greeting", then you can add any of those as optional properties as well.
export interface Message {
message?: string;
greeting?: string;
}
HttpClient gives you json object only, so no need to do .json() again.So do the following code it works for you
getMessage(){
this.http.get('http://localhost:3000/newroute').subscribe(res =>{
let messages: any = res;
this.messages = messages;
})
}
I am handling Http result in a reducer function of an Observable. While a type of the parameter jwt is set as { id: string, auth_token: string, expires_in }, the jwt argument turns out to be a string. I thought TypeScript does parsing automatically. Do I have to do JSON.parse(JSON.stringify(jwt)) by myself?
.mergeMap((jwt: { id: string, auth_token: string, expires_in }) => {})
Type checking external code
There is no relationship between the TypeScript source code and the JavaScript outputs that gets executed at run-time. TypeScript is only effective at catching compile-time errors if the compiled types match the run-time types.
Normally, this isn't a problem. But in scenarios where you call out to external code (i.e. AJAX call to fetch data from the server), there is no guarantee that the response will be the type you expect. So you must be cautious in these scenarios.
Your specific example
I suspect that your code has a variable jwt with type any and you just assigned the type to { id: string, auth_token: string, expires_in } when in fact, jwt was of type string as far as javascript is concerned.
In this case, you already found your solution, JSON.parse(str). This converts the json string into a javascript object.
Now that you have an object, you can use duck typing to infer the run-time type and let typescript know about the type at compile-time via type guards.
Solution
function isDate(obj: any): obj is Date {
return typeof obj === 'object' && 'toISOString' in obj;
}
function isString(obj: any): obj is string {
return typeof obj === 'string';
}
interface JWT {
id: string;
auth_token: string;
expires_in: Date;
}
function isJwt(obj: any): obj is JWT {
const o = obj as JWT;
return o !== null
&& typeof o === 'object'
&& isString(o.id)
&& isString(o.auth_token)
&& isDate(o.expires_in);
}
function print(jwt: any) {
if (typeof jwt === 'string') {
try {
jwt = JSON.parse(jwt);
} catch (e) {
console.error(`String is not JSON: ${jwt}`);
}
}
if (isJwt(jwt)) {
console.log(`Found jwt: ${jwt.id} ${jwt.auth_token} ${jwt.expires_in}`);
} else {
console.error(`Object is not of type jwt: ${jwt}`);
}
}
print(42);
print('failing');
print(null);
print(undefined);
print({});
print({ id: 'id01', auth_token: 'token01', expires_in: new Date(2018, 11, 25) });
Playground
Try running that code on the TS Playground to see how it inspects the object at run-time.
If the jwt(JSON) object is retrieved with Http from HttpModule #angular/http you have to parse it to JSON
e.g.:
import { Http } from '#angular/http';
....
constructor(
private http: Http
...
) {}
this.http.get(url)
.map((res: any) => {
return res.json();
}).subscribe( (jwt: any) => {
//you got jwt in JSON format
});
If you use HttpClient from HttpClientModule #angular/common/http (Angular > 4.3.x) you do not need to parse the received data because it is already done.
import { HttpClient } from '#angular/common/http';
....
constructor(
private http: HttpClient
...
) {}
this.http.get<any>(url)
.subscribe((jwt: any) => {
//you got jwt in JSON format
})
More info in this answer
So I'm getting the following JSON structure from my asp.net core api:
{
"contentType": null,
"serializerSettings": null,
"statusCode": null,
"value": {
"productName": "Test",
"shortDescription": "Test 123",
"imageUri": "https://bla.com/bla",
"productCode": null,
"continuationToken": null
}
}
I have the following typescript function that invokes the API to get the above response:
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; //not working...
console.log("DATA: " + data);
console.log("DATA: " + data['value']);
return data;
});
}
ProductVM:
export interface ProductVM {
productName: string;
shortDescription: string;
imageUri: string;
productCode: string;
continuationToken: string;
}
My problem is that I can't deserialize it to ProductVM. The console logs just produce [object Object]
How can I actually map the contents of the value in my json response to a ProductVM object?
Is it wrong to say that data is a ProductVM in the map function? I have tried lots of different combinations but I cannot get it to work!
I'm unsure whether I can somehow automatically tell angular to map the value array in the json response to a ProductVM object or if I should provide a constructor to the ProductVM class (it's an interface right now), and extract the specific values in the json manually?
The data object in the map method chained to http is considered a Object typed object. This type does not have the value member that you need to access and therefore, the type checker is not happy with it.
Objects that are typed (that are not any) can only be assigned to untyped objects or objects of the exact same type. Here, your data is of type Object and cannot be assigned to another object of type ProductVM.
One solution to bypass type checking is to cast your data object to a any untyped object. This will allow access to any method or member just like plain old Javascript.
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: any) => this.externalProduct = data.value);
}
Another solution is to change your API so that data can deliver its content with data.json(). That way, you won't have to bypass type checking since the json() method returns an untyped value.
Be carefull though as your any object wil not have methods of the ProductVM if you ever add them in the future. You will need to manually create an instance with new ProductVM() and Object.assign on it to gain access to the methods.
From angular documentation: Typechecking http response
You have to set the type of returned data when using new httpClient ( since angular 4.3 ) => this.http.get<ProductVM>(...
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get<ProductVM>("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; // should be allowed by typescript now
return data;
});
}
thus typescript should leave you in peace
Have you tried to replace
this.externalProduct = data;
with
this.externalProduct = data.json();
Hope it helps
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map(data => {
this.externalProduct = <ProductVM>data;
console.log("DATA: " + this.externalProduct);
return data;
});
}
So, first we convert the response into a JSON.
I store it into response just to make it cleaner. Then, we have to navigate to value, because in your data value is the object that corresponds to ProductVM.
I would do it like this though:
Service
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get(`api/product?productCode=${code}`)
.map(data => <ProductVM>data)
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
Component
this.subscription = this.myService.getProductExternal(code).subscribe(
product => this.externalProduct = product,
error => console.warn(error)
);
I used this approach in a client which uses the method
HttpClient.get<GENERIC>(...).
Now it is working. Anyway, I do not understand, why I do not receive a type of T back from the http client, if I don't use the solution provided in the answer above.
Here is the client:
// get
get<T>(url: string, params?: [{key: string, value: string}]): Observable<T> {
var requestParams = new HttpParams()
if (params != undefined) {
for (var kvp of params) {
params.push(kvp);
}
}
return this.httpClient.get<T>(url, {
observe: 'body',
headers: this.authHeaders,
params: requestParams
}).pipe(
map(
res => <T>res
)
);
}
Consider this simple snippet of an AngularJS 2 application:
TestObject
export class TestObject {
id: number;
name: string;
}
TestService
[...]
export class TestService {
constructor(private http: Http) {}
test(): Observable<TestObject> {
return this.http
.get("http://www.example.com")
.map(this.save)
.catch(this.fail);
}
private save(response: Response) {
let testObject: TestObject = <TestObject> response.json();
return testObject || {};
}
private fail(error: any) {
return Observable.throw("error!");
}
}
AppComponent
[...]
export class AppComponent implements OnInit {
testObject: TestObject;
constructor(private testService: testService) {}
ngOnInit() {
this.testService.test().subscribe(
data => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
this.testObject = data;
console.log(this.testObject); // prints object, not TestObject?
},
error => { }
);
}
}
Here my questions:
1) Why does my application print out (using Chrome Inspector) object and not TestObject as type?
2) The property testObject of class AppComponent should be of type TestObject. Why does my application not fail?
3) How can I achieve that I really get TestObject? What would be the best way to do it? Of course I could just manually fill up my TestObject, but I hoped there is some way of automatically mapping the json to my object.
Here is an answer that I wrote to a question which explained the handling of observables in angular2.
Angular 2 http post is returning 200 but no response is returned
Here you can see how I am handling the Response object as returned by the service. It is very important that you return your response object from the map function in service.
Similarly you can convert your response object to typescript type by casting your response object. The example can be:
this._loginService.login(this.username, this.password)
.subscribe(
(response) => {
//Here you can map the response to a type.
this.apiResult = <IUser>response.json();
//You cannot log your object here. Here you can only map.
},
(err) => {
//Here you can catch the error
},
() => {
//this is fired after the api requeest is completed.
//here you can log your object.
console.log(this.apiResult);
//result will only be shown here.
}
);
Here, it can be clearly seen that I am casting the response object to IUser type.
Another thing is while handling apiresponse in your component it is to be noted that the subscribe function has three arguments and if you will like to log your object, you must do it in the last function of subscribe.
Hope this helps!
your call must be like
ngOnInit() {
this.testService.test().subscribe(
(data) => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
//only mapping
this.testObject = data;
},
error => { },
() => {
console.log(this.testObject);
}
);
}