TypeError: Cannot set property 'name' of undefined - json

I am trying to map some values of JSON response to another variable but getting some error "Cannot set property name of undefined"
export interface Data
{
description: any;
name : any;
}
Inside main class defined the following data
actionData : any;
action:Data[]=[];
getData()
{
this.spref.getNewData().subscribe(
response => {
this.actionData = response;
for(let i=0;i<this.actionData.length;i++)
{
this.action[i].name = this.actionData[i].name;
this.action[i].description = this.actionData[i].description;
}
})
},
error => {
console.log('Failure: ', error);
}
);
}
Response of actionData in this format
[{
description: "pqrs"
jsonType: "com.xyz.common.object.NewData"
name: "abc"
value: "xyz"
}]
I want action data will be stored in this format
[{
description: "pqrs"
name: "abc"
}]
Thanks in advance!

action[i] is undefined if not initialized. So, before setting any properties to it you need to initialize it, like so:
actionData : any;
action:Data[]=[];
getData()
{
this.spref.getNewData().subscribe(
response => {
this.actionData = response;
for(let i=0;i<this.actionData.length;i++)
{
this.action[i] = {
name: this.actionData[i].name;
description: this.actionData[i].description;
}
}
})
},
error => {
console.log('Failure: ', error);
}
);
}

Related

How to typecheck a string against an object type after json parse

I have some types:
export type Ping = {
kind: 'ping',
lag?: number
}
export type Message = {
kind: 'message',
value: string
}
I have an incoming message string encoded json:
let msg = "{kind:'message', value: 3 }",
ping = "{kind:'ping'}";`;
After I convert this into an object:
let obj = JSON.parse(msg);
I want to validate these messages to have the expected properties and dispatch them like so:
export function isMessage(_: any): _ is Message {
if (typeof _ === 'object') {
let res = (_ as Message);
if (res.kind === 'message' && res.value && typeof res.value === 'string') {
return true;
}
}
return false;
}
export function use(_: any) {
if (isMessage(_)) {
console.log('Message: ', _.value);
}
}
Do I have to typecheck every field of every kind of message like above, or is there an simpler way of doing this?
The simpler, or at least cleaner, way is to write a JSON Schema and run your data through a validator like ajv. Here's how your Message type could be validated:
import Ajv, { JSONSchemaType } from 'ajv';
const ajv = new Ajv();
export interface Message {
kind: 'message',
value: string
}
const messageSchema: JSONSchemaType<Message> = {
type: 'object',
properties: {
kind: { type: 'string', const: 'message' },
value: { type: 'string' }
},
required: ['kind', 'value'],
additionalProperties: false
};
const isMessage = ajv.compile(messageSchema);
export { isMessage };

How to iterate over JSON returned by HttpClient

I have a simple Angular HttpClient, which is correctly returning JSON. I am attempting to cast the results to enforce type safety (not sure if this is correct).
But how do I actually access the returned JSON to copy it into an array?
The httpClient get() request is (and seems to be working fine):
public sendGetRequest(): Observable<Symbols[]> {
return this.httpClient.get<Symbols[]>(this.REST_API_SERVER);
}
The Symbols interface is
export interface Symbols {
code: string
desc: string
}
I have a component which calls the data service and is getting a response. However the code below returns an error when attempting to map the JSON into a string array
ERROR TypeError: syms.map is not a function
listOfOption: Array<{ value: string; label: string }> = []
this.dataService.sendGetRequest().subscribe((syms: Symbols[]) => {
console.log('return value ' + JSON.stringify(syms))
// console output shows the returned JSON and it looks correct
//this does not work, how do I copy the results to a string array??
this.listOfOption = syms.map(results => {
return {
value: results.code,
label: results.code,
}
})
})
The JSON data structure is:
{
"results": [
{
"code": "code1",
"desc": "Long description of code 1"
},
{
"code": "code2",
"desc": "Long description of code 2"
},
{
"code": "code3",
"desc": "Long description of code 3"
},
{
"code": "code4",
"desc": "Long description of code 4"
}
]
}
This is driving me crazy
Model a new interface called responseData to support response type.
export interface responseData{
results: Symbols[]
}
export interface Symbols {
code: string
desc: string
}
Update the same in service
public sendGetRequest(): Observable<responseData> {
return this.httpClient.get<responseData>(this.REST_API_SERVER);
}
You can now retrieve the results using array.map()
listOfOption: Array<{ value: string; label: string }> = []
this.dataService.sendGetRequest().subscribe((syms: responseData) => {
console.log('return value ' + syms)
this.listOfOption = syms.results.map(result => {
return {
value: result.code,
label: result.code,
}
})
})
The response data has an object root, but you're trying to parse it as an array root. I think the simplest solution would be something like this:
public sendGetRequest(): Observable<Symbols[]> {
return this.httpClient.get<{results: Symbols[]}>(this.REST_API_SERVER)
.pipe(pluck('results'));
}
Which specifies that the response data is an object with a field named results which holds an array of Symbols.
Alternatively you could also extract the response type to a separate definition:
interface ApiResponse {
results: Symbols[]
}
public sendGetRequest(): Observable<Symbols[]> {
return this.httpClient.get<ApiResponse>(this.REST_API_SERVER)
.pipe(pluck('results'));
}

best way to pass an array of object with activeroute - Angular

I'm trying to pass an array of objects through activeroute. When I pass it to the next page I get [object Object]. I saw a question on Stackoverflow where they use JSON.stringify but that didn't work for me. Or is it better to use application providers instead of queryparams.
TS of page sending the data
criteriaList: ShipmentLookupCriteria[] = [];
navigateTo() {
const navigationExtras: NavigationExtras = {
queryParams: {
criteriaList: this.criteriaList
}
};
this.router.navigate(['/lookup/results'], navigationExtras);
}
TS of page receiving the data
this.sub = this.route.queryParams.subscribe(params => {
console.log(params.criteriaList);
});
ShipmentLookUpCriteria model
import { EquipReferenceTuple } from './equip-reference-tuple.model';
export class ShipmentLookupCriteria {
terminal: string;
equipReferenceList: EquipReferenceTuple[];
constructor(terminal: string, equipReferenceList: EquipReferenceTuple[]) {
this.terminal = terminal;
this.equipReferenceList = equipReferenceList;
}
}
UPDATE
I decided to start with something simple. So I create an array of objects with dummy data.
navigateTo() {
const navigationExtras: NavigationExtras = {
queryParams: {
criteriaList: [{ name: 1, age: 1 }, { name: 2, age: 2 }]
}
};
this.router.navigate(['lookup/results'], navigationExtras);
}
PAGE RECEIVING THE PARAMS
this.route.queryParams.subscribe(params => {
console.log(params.criteriaList[0]);
});
RETURNS = [object Object] If I do again JSON.stringify it shows it as string "[object Object]". if I do params.criteriaList[0].name returns undefined
You can simply pass,
this.router.navigate(['/lookup/results'], {queryParams: {criteriaList: this.criteriaList }});
and access it using
this.sub = this.route.snapshot.queryParamMap.get('criteriaList');

Delete row data from Firebase

I want to delete one clicked row from Firebase in my smart table. I am using Angular 4.
The smart table code:
<ng2-smart-table [settings]="settings" [source]="bicycleData"
(deleteConfirm)="onDeleteConfirm($event)">
</ng2-smart-table>
My constructor component code:
constructor(
db: AngularFireDatabase, ) {
this.bicyclesList = db.list('bicycle-list')
.snapshotChanges()
.map(changes => {
return changes.map(c => ({
key: c.payload.key,
...c.payload.val()
}))
});
this.bicyclesList.subscribe((data) => {
this.bicycleData = data;
});
}
and component.ts code:
settings = {
delete : {
deleteButtonContent: '<i class="nb-trash"></i>',
confirmDelete: true,
},
}
onDeleteConfirm() function and deleteEnquiry function in service:
onDeleteConfirm(event) {
console.log(event.data);
if (window.confirm('Are you sure you want to delete?')) {
this.deleteEnquiry(event.data);
event.confirm.resolve();
} else {
event.confirm.reject();
}
}
deleteEnquiry(data) {
console.log(data.$key);
this.db.list(`bicycle-list${data.$key}`).remove(data);
}
But it keeps showing me the following error in console:
ERROR TypeError: Cannot read property 'list' of undefined
How can I fix this error ?
Looks like an error in deleteEnquiry.
According to the docs is should be:
deleteEnquiry(data) {
console.log(data.$key);
this.db.list('bicycle-list').remove(data.$key);
}

Query data relationship on Firebase Realtime Database with angularfire2

I need to query comments and request only user that listed in the comment by userId.
My database structure in Firebase realtime db:
{
"comments" : {
"c_id1" : {
"commentId" : "c_id1",
"commentText" : "text",
"userId" : "u_id1"
},
"c_id2" : {
"commentId" : "c_id2",
"commentText" : "text",
"userId" : "u_id3"
},
},
"users" : {
"u_id1" : {
"userId" : "u_id1",
"userName" : "name1",
},
"u_id1" : {
"userId" : "u_id2",
"userName" : "name2",
},
"u_id1" : {
"userId" : "u_id3",
"userName" : "name3",
}
}
}
What I need in the end is Comment[], where Comment is:
{
"commentId" : "c_id",
"commentText" :"text",
"userId" : "u_id",
"user" : {
"userId":"u_id",
"userName":"name"
}
}
so, the class for Comment is
export class Comment {
commentId: string;
commentText: string;
userId: string;
user?: User;
}
So far I managed to get ALL users and then map them to comments on the client side. But wouldn't it be to much in case when db will have N number of users and only 2 comments, where N>>2?
OnGetUsersForComments(){
return this.angularFireDatabase.list("/comments").valueChanges()
.subscribe((data) => {
this.commentsUsers = data;
this.OnGetCommentsForTask()
});
}
OnGetCommentsForTask(){
this.angularFireDatabase.list("/comments").valueChanges()
.map((comments) => {
return comments.map( (comment: TaskComment) => {
this.commentsUsers.forEach((user: User) => {
if (comment.userId === user.userId) {
comment.commentUser = user;
}
});
return comment;
});
})
.subscribe((data)=> {
this.comments = data;
});
}
Is there a way get only users from comments?
I also tried to add this to the User, but did not manage it to work:
"userComments" : {
"uc_id1" : {
"commentId" : c_id2
},
}
Update 0
I have edited the question, I hope now is more clear.
I have been able to make it work like this:
solution from - https://www.firebase.com/docs/web/guide/structuring-data.html
and
https://firebase.google.com/docs/database/web/read-and-write
comments: TaskComment[] = [];
onGetComments(){
var ref = firebase.database().ref('/');
ref.child('comments/').on('child_added', (snapshot)=>{
let userId = snapshot.val().userId;
ref.child('users/' + userId).on('value', (user)=>{
this.comments.push( new TaskComment( snapshot.val(), user.val() ));
});
});
}
but I want to convert this to Observable, because with this I can not see if the comment have been deleted without refreshing the page.
Update 1
With the help from comment bellow I came out with this implementation.
onGetComments(){
this.angularFireDatabase.list("/comments").valueChanges()
.mergeMap((comments) => {
return comments.map((comment)=>{
this.firebaseService
.onListData('/users', ref => ref.orderByChild('userId').equalTo(comment.userId))
.valueChanges()
.subscribe((user: User[])=> {
comment.user = user[0];
})
return comment;
})
})
.subscribe((comment)=> {
console.log(comment);
});
}
This returns separate comments, where I would rather receive Comment[], I'll try to use child events: "child_added", "child_changed", "child_removed", and "child_moved" with snapshotChanges() instead .valueChanges().
Ok so according to your updates, I would personally first create a couple helper interfaces:
interface User {
userId: string;
userName: string;
}
interface FullComment {
commentId: string;
userId: string;
user: User;
}
interface CommentObject {
commentId: string;
commentText: string;
userId: string;
}
And then super handy helper methods:
getUser(uid: string): Observable<User> {
return this.db.object<User>(`/users/${uid}`)
.valueChanges()
}
getFullComment(commentObject: CommentObject): Observable<FullComment> {
return this.getUser(commentObject.userId)
.map((user: User) => {
return {
commentId: commentObject.commentId,
commentText: commentObject.commentText,
user: user,
};
});
}
So finally look how easy it becomes to get the FullComment objects observable:
getComments(): Observable<FullComment[]> {
return this.db
.list(`/comments`)
.valueChanges()
.switchMap((commentObjects: CommentObject[]) => {
// The combineLatest will convert it into one Observable
// that emits an array like: [ [fullComment1], [fullComment2] ]
return Observable.combineLatest(commentObjects.map(this.getFullComment));
});
}
I think this is what you need. Please let me know if this is helpful.
Happy coding with observables ;)
Latest update: Previously forgot to make a last transformation to fix the TypeError, so now it must be ok.