I'm having an issue with calling my data from a json file. When I click a button to have it appear in a textarea, it does nothing for the first click, but works like expected after that. What the program does is gets an id from dashboard and based on that id grabs different json file to pull in.
The program shows an error of:
ERROR TypeError: Cannot read property 'data' of undefined
at StepService.webpackJsonp.102.StepService.populateList (step.service.ts:69)
at CalibrationDetailComponent.webpackJsonp.101.CalibrationDetailComponent.next
step.service.ts
private jsonData: any; //Json data
public list: String[] = []; //Holds the list of steps
public listLength: number; //Length of the list
public listCurrent: number = 0; //Current step the list is on
//Gets the json file
public getJson(): Observable<any> {
return this.http.get(this.jsonUrl)
.map(response => response.json());
}
//Subscribe
public subScribe2Json() {
this.getJson().subscribe(data => (this.jsonData = data));
}
//Populates the list from the json so I can pull out specific steps
public populateList() {
this.jsonData.data.forEach(element => { //The line that throws the error
this.list.push(element.name);
});
this.listLength = this.list.length;
}
//Returns the mainStepText with the current step
public getJsonData(): String {
this.mainStepText = this.list[this.listCurrent];
return this.mainStepText;
}
calibration-detail.component.ts
next button method
next() { //Advances step
this.stepService.subScribe2Json();
if (this.stepService.listCurrent < 1) { //Makes sure only runs once to populate the list
this.stepService.populateList(); //Populates list from the json array
}
if (this.stepService.listCurrent < this.stepService.listLength) { //make sure dont go past number of steps
this.stepService.subScribe2Json(); //Sub to list
this.mainStepText = this.stepService.getJsonData(); //Grab the data from the list and output to main textarea
this.stepService.listCurrent ++; //Increments the step
This is not a complete solution but an answer to what the problem is. And direct your thought into the right direction depending on what you want to achieve.
You call
this.stepService.subScribe2Json();
if (this.stepService.listCurrent < 1) {
...
this calls the first method and immediately the second without waiting for the data. And then of course it fails because it is not there yet.
Depending on your use case you could either return the Observable (maybe change it to a Promise,... not 100% sure) and then:
return this.getJson().subscribe(data => (this.jsonData = data));
and something like
this.stepService.subScribe2Json().then(/* do all stuff here */);
or initialize
private jsonData: any = [];
but here of course you don't have anything on the first run.
Related
Hope the title isn't too specific.
The back-end I am working with returns Dates as a string. I have a function to convert that string to a javascript Date object. I use a Rxjs map to convert the json response to my Typescript objects like so.
getAllRecordsByEmployeeId(employeeId: number): Observable<Record[]> {
return this.http.get<Record[]>(
this.basePath
+ this.recordPath
+ this.recordEmployeeIdParam
+ employeeId,
this.httpOptions)
.pipe(
map((res: any) => res.records as Record[]),
);
}
I want to mutate res.records.startDate with a function before it gets turned into a Record object. How can I accomplish this?
getAllRecordsByEmployeeId(employeeId: number): Observable<Record[]> {
return this.http.get<Record[]>(
I understand, that your http request does not actually return a Record array. It returns an object with a Record Array field, which is basically another Record model. It is very similar, but it's a different model.
Please consider changing it to:
interface RecordFromApi extends Record {
startDate: string; // overwrite attribute
}
interface RecordResponse {
records: RecordFromApi[];
}
getAllRecordsByEmployeeId(employeeId: number): Observable<Record[]> {
return this.http.get<RecordResponse>(
this.basePath
+ this.recordPath
+ this.recordEmployeeIdParam
+ employeeId,
this.httpOptions)
.pipe(
map((res: RecordResponse) => res.records.map(record => mapRecord(record))), // mapRecord is a custom function which maps RecordFromApi model to Record model
);
}
We do something similar in my application. But instead of returning
res.records as Record[]
we do something like this:
.pipe(
map((records: Record[]) => records.map(records => new Record(record)))
);
and then on the record.ts
export class Record {
/*
properties
*/
date: Date;
constructor(params: Partial<Record> = {}) {
this.date = new Date(params.date);
}
}
This way you actually get instances of your class and you can use any functions you may have in your class (that's the issue we had when we came up with this solution).
I'm trying out things with Flutter right now. But my variables keep getting reinitialised when accessed from another class.
I'm using json parsing and i need two parts of my request. The "Relatorio" part and the "Mensagem" part.
to parse this json i'm doing this:
List<RelatorioProdutos> parseRelatorioPorProduto(String responseBody) {
final parsed = json.decode(responseBody);
var relatorio = parsed['Relatorio'];
var mensagem = parsed['Mensagem'];
print (mensagem); // Here the variable returns well,
//but when i need it in other class i receive null.
return relatorio.map<RelatorioProdutos>((json) => new RelatorioProdutos.fromJson(json)).toList();
}
class RelatorioProdutos {
String CodigoProduto;
var QtdVendida;
var TotalVendas;
String Descricao;
RelatorioProdutos({this.CodigoProduto, this.QtdVendida, this.TotalVendas, this.Descricao,});
factory RelatorioProdutos.fromJson(Map json) {
//returns a List of Maps
return new RelatorioProdutos(
CodigoProduto: json['CodigoProduto'] as String,
QtdVendida: json['QtdVendida'],
TotalVendas: json['TotalVendas'],
Descricao: json['Descricao'] as String,
);
}
}
I want to use this 'mensagem' variable in another class to show the error for user, but i always receive 'null'.
i already tried setState but it reloads my json and i dont want to request the RestServer again.
Thanks from now!
If I understand correctly, you want to access a local variable of a function from another class. I don't think it's possible.
One way to do it, would be to wrap your response in another object containing the response, and this variable:
List<Response<RelatorioProdutos>> parseRelatorioPorProduto(
String responseBody) {
final parsed = json.decode(responseBody);
var relatorio = parsed['Relatorio'];
var mensagem = parsed['Mensagem'];
print(mensagem); // Here the variable returns well,
//but when i need it in other class i receive null.
return relatorio
.map((json) => new Response<RelatorioProdutos>(
new RelatorioProdutos.fromJson(json), mensagem))
.toList();
}
class RelatorioProdutos {
String CodigoProduto;
var QtdVendida;
var TotalVendas;
String Descricao;
RelatorioProdutos({
this.CodigoProduto,
this.QtdVendida,
this.TotalVendas,
this.Descricao,
});
factory RelatorioProdutos.fromJson(Map json) {
//returns a List of Maps
return new RelatorioProdutos(
CodigoProduto: json['CodigoProduto'] as String,
QtdVendida: json['QtdVendida'],
TotalVendas: json['TotalVendas'],
Descricao: json['Descricao'] as String,
);
}
}
class Response<T> {
const Response(
this.value,
this.errorMessage,
);
final T value;
final String errorMessage;
bool get hasError => errorMessage != null;
}
In this example I created a Response object that can contains both the response value and an error message.
In the parseRelatorioPorProduto, instead of returning the relatorio, I changed the return type to Response<RelatorioProdutos> in order to have access to the value and the error message from any class which call this function.
Thanks Letsar, i tried yout ideia but i get a lot of others erros.
To solve this problem i used this:
List<RelatorioProdutos> parseRelatorioPorProduto(String responseBody) {
final parsed = json.decode(responseBody);
var relatorio = parsed['Relatorio'];
var mensagem = parsed['Mensagem'];
if(mensagem[0].toString().substring(16,17) == "0"){
List<RelatorioProdutos> asd = new List();
RelatorioProdutos aimeudeus = new RelatorioProdutos(Descricao: mensagem[0].toString(), CodigoProduto: "a", TotalVendas: 0, QtdVendida: 0);
asd.add(aimeudeus);
return asd;
}else{
return relatorio.map<RelatorioProdutos>((json) => new RelatorioProdutos.fromJson(json)).toList();
}
}
I am trying to use the data received from my API call in IONIC home.ts in google map method in home.ts .But it ended up giving a null or undefined error.
How can I use the data from one method in another?
Here is my home.ts code
getResturentDetails(id){
const data = localStorage.getItem('userToken');
this.userPostData.api_token= data;
this.userPostData.resturentId= id;
this.authService.postData(this.userPostData,'resturentDetail').then((result)=>{
this.responseData = result;
console.log(this.responseData); //I can see data as expected
})
}
Output of the console log is something like this
{"id":"1","lat":"10.90" ,"lon":"89.00"}
Something like this in JSON object notation
My constructor
constructor(public nav: NavController,
public navParams:NavParams,
public tripService: TripService,
public authService:AuthServiceProvider
) {
// set sample data
this.getResturentDetails(this.navParams.get('id'));
console.log(this.responseData.lat); //saying undefined index lat
console.log(this.responseData.lon); //saying undefined index lon
this.DisplayMap(this.responseData.lat,this.responseData.lon);
}
ionViewDidLoad() {
}
So how do I handle this issue ??
And in case if my API return an array of data, in that case how to handle the issue
For example, if the API response is something like this
[{"id":"1","lat":"10.90" ,"lon":"89.00"},{"id":"2","lat":"10.90" ,"lon":"89.00"}]
Something like this in JSON format in that case how to handle the issue??
You need to change your code like this :
constructor(...) {
...
this.getResturentDetails(this.navParams.get('id')).then(result => {
this.responseData = result;
console.log(this.responseData.lat); // will not say undefined index lat
console.log(this.responseData.lon); //will not say undefined index lon
this.DisplayMap(this.responseData.lat,this.responseData.lon);
})
}
getResturentDetails(id){
const data = localStorage.getItem('userToken');
this.userPostData.api_token= data;
this.userPostData.resturentId= id;
return this.authService.postData(this.userPostData,'resturentDetail');
}
Reason why its not working is , async behaviour :
Just follow the Execution sequence No in below code , you will get the idea how the
flow will go in real time execution
// this will call getResturentDetails , and consider it may take few sec
this.getResturentDetails(this.navParams.get('id')); // Execution sequence : 1
// next line will be executed before result returns coz of async behaviour
console.log(this.responseData.lat); // Execution sequence : 6
console.log(this.responseData.lon); // Execution sequence : 7
this.DisplayMap(this.responseData.lat,this.responseData.lon); // Execution sequence : 8
getResturentDetails(id){
const data = localStorage.getItem('userToken'); // Execution sequence : 2
this.userPostData.api_token= data; // Execution sequence : 3
this.userPostData.resturentId= id; // Execution sequence : 4
// Execution sequence : 5
this.authService.postData(this.userPostData,'resturentDetail').then((result)=>{
this.responseData = result; // Execution sequence : 9
console.log(this.responseData); // Execution sequence : 10
})
}
I want to go through every key and get the name value from each key.
This is how my LocalStorage looks like.
key: 3 Value:
{"name":"Kevin","country":"Canada","about":"Test","image":""}
key: 4 Value:
{"name":"Homer","country":"Canada","about":"Test","image":""}
I want to getboth of these names and add them to my array. I tried it with this method:
for(var key in localStorage){
let user = JSON.parse(localStorage.getItem(key));
this.users.push(user);
}
Error I get is:
SyntaxError: Unexpected token e in JSON at position 1
var keys = Object.keys(localStorage);
keys.forEach(key=>{
var json_str =localStorage.getItem(key)
try {
var abc = JSON.parse(json_str);
this.user = abc;
} catch (e) {
console.log(e)
}
})
when you say I want to getboth of these names, i don't get it but either way you can try something like:
var keys = Object.keys(localStorage);
for(var i=0;i<keys.length;i++){
var key = keys[i];
console.log(key, localStorage[key]);
//store here "both names" where you want them
//you can also access each element with localStorage[key].name, localStorage[key].country, etc.
}
This is a refinement of Robert's answer.
Just enumerate all of the values (The keys themselves do not matter) in localStorage that have a name property that is a string. Then return that array.
Based on your own answer, you likely have inconsistent mutable state as moving your temporary variable to instance scope should not impact your situation.
function getUsers() {
return Object.values(localStorage)
.map(json => {
try {
return JSON.parse(json);
}
catch (e) {
return undefined;
}
})
.filter((user?: any): user is {name: string} => user && typeof user.name === 'string');
}
const users = getUsers();
You can consider using my library ngx-store to deal with localStorage, sessionStorage, cookies and a bit more in Angular. To achieve what you want you will be able to store whole array in storage or just use code like the below with your current data structure:
import { LocalStorageService } from 'ngx-store';
export class Example {
public users: Array<any> = [];
constructor(public localStorageService: LocalStorageService) {
this.localStorageService.utility.forEach((value, key) => this.users.push(value));
}
}
Really, it can be just that simple ;)
You can use method hasOwnProperty('propertyName') to check name available or not in object. Then perform the operation that you want.
let localStorage = {
"key1": {"name":"Kevin","country":"Canada","about":"Test","image":""},
"key2": {"name":"Homer","country":"Canada","about":"Test","image":""}
}
for(let key of Object.keys(localStorage)){
if(localStorage[key].hasOwnProperty('name')){
console.log(localStorage[key]['name']);
}
}
const allItems = []
const keys = Object.keys(window.localStorage); // all keys
keys.forEach(key=> {
const item = JSON.parse(localStorage.getItem(key) + ''); //item with type Object
allItems.push(item);
});
console.log(allItems) // arry of object
I manage to solve it, was a simple error by always initializing a new let user inside the loop.
I moved out the user and the rest of the code works.
user: any;
getUsers():void{
for(var key in localStorage){
this.user = JSON.parse(localStorage.getItem(key));
this.users.push(this.user);
}
}
I am attempting to take the Name and ID fields from each object, but the fields are appearing undefined.
function OnHistoricalListBoxLoad(historicalListBox) {
$.getJSON('GetHistoricalReports', function (data) {
historicalListBox.trackChanges();
$.each(data, function () {
var listBoxItem = new Telerik.Web.UI.RadListBoxItem();
listBoxItem.set_text(this.Name);
listBoxItem.set_value(this.ID);
historicalListBox.get_items().add(listBoxItem);
});
historicalListBox.commitChanges();
});
}
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetHistoricalReports()
{
List<HistoricalReport> historicalReports = DashboardSessionRepository.Instance.HistoricalReports;
var viewModel = historicalReports.Select(report => new
{
ID = report.ID,
Name = report.Name
}).ToArray();
return Json(viewModel, JsonRequestBehavior.AllowGet);
}
I know that I am returning the data successfully, and I know that there is valid data. I am new to MVC/JavaScript, though.. I checked case sensitivity to ensure that I wasn't making just an easy mistake, but it does not seem to be the issue. Am I missing something more complex?
Inspecting the HTTP Response JSON tab in Chrome I see:
0: {ID:1, Name:PUE}
1: {ID:2, Name:Weight}
2: {ID:3, Name:Power Actual vs Max}
3: {ID:4, Name:Power Actual}
No idea, but passing such behemoth domain models to views is very bad practice. This is so kinda domain polluted that it has nothing to do in a view. In a view you work with views models. View models contain only what a view needs. In this case your view needs an ID and a Name. So pass a view model with only those single simple properties to this view:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetHistoricalReports()
{
var reports = DashboardSessionRepository.Instance.HistoricalReports;
var reportsViewModel = reports.Select(x => new
{
ID = x.ID,
Name = x.Name
}).ToArray();
return Json(reportsViewModel, JsonRequestBehavior.AllowGet);
}
Now, not only that you will save bandwidth, but you will get some clean JSON:
[ { ID: 1, Name: 'Foo' }, { ID: 2, Name: 'Bar' }, ... ]
through which you will be able to loop using $.each.
UPDATE:
Now that you have shown your JSON data it seems that there is a Content property which represents the collection. So you need to loop through it:
$.each(data.Content, ...);
and if you follow my advice about the view models your controller action would become like this:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetHistoricalReports()
{
var report = DashboardSessionRepository.Instance.HistoricalReports;
var reportsViewModel = report.Content.Select(x => new
{
ID = x.ID,
Name = x.Name
}).ToArray();
return Json(reportsViewModel, JsonRequestBehavior.AllowGet);
}
and now loop directly through the returned collection:
$.each(data, ...);