With my angular2 application, i am getting the response and assigning to object as follows,
seatingConcession: {
parking: data.concession.extras.parking ? data.concession.extras.parking : null,
restrictedview: data.concession.extras.restrictedview ? data.concession.extras.restrictedview : null,
wheelchair: data.concession.extras.wheelchair ? data.concession.extras.wheelchair : null
}
sometimes extras does not have value. sometimes restrictedview inside extras does not have value. what is the best way to check and assign the default value .
Whole code:
this.eventService.getListingsByEventId(this.eventId).subscribe(listresults => {
this.bindListing(listresults);
}, error => this.errorMessage = error);
}
bindListing(listres: any[]) {
let price_table = {};
let section_table = {};
listres.forEach((data) => {
data.ticket.seating.forEach((seat: any) => {
// tslint:disable-next-line:max-line-length
this.listings.push({
section: seat.section, selling: data.price.selling, amount: data.ticket.amount, type: data.ticket.type, row: seat.row, category: seat.category,
seatingConcession: {
parking: data.concession.extras ? (data.concession.extras.restrictedview || null) : null,
restrictedview: data.concession.extras.restrictedview || null,
wheelchair: data.concession.extras.wheelchair || null
},
deliveryconcession: {
instantdownload: data.delivery.instantdownload || null,
readytoship: data.delivery.readytoship || null,
unespecifiedshipment: data.delivery.unspecifiedshipment || null
}
});
// this.listings.push({ section: seat.section, selling: data.price.selling, amount: data.ticket.amount, type: data.ticket.type, row: seat.row, category: seat.category});
// tslint:disable-next-line:curly
if (!price_table.hasOwnProperty(data.price.selling))
price_table[data.price.selling] = [];
price_table[data.price.selling].push(data);
// tslint:disable-next-line:curly
if (!section_table.hasOwnProperty(seat.section))
section_table[seat.section] = [];
section_table[seat.section].push(data);
});
});
Service js:
getListingsByEventId(EventID: string): Observable<ListingSeller[]> {
let apiurl = this.appConfig.getAPIUrl() + '/getListingsByEventId';
return this.http
.get(apiurl + queryString)
.map(this.extractData)
.catch(this.handleErrors);
}
You can use the following function to achieve what you want.
function getSafe(fn) {
try {
return fn();
} catch (e) {
return null;
}
}
Then use it like this
seatingConcession: {
parking: getSafe(() => data.concession.extras.parking),
restrictedview: getSafe(() => data.concession.extras.restrictedview),
wheelchair: getSafe(() => data.concession.extras.wheelchair),
}
See details.
Another approach would be to execute data.concession.extras = data.concession.extras || {} before actually creating your object.
You mentioned,
"sometimes extras does not have value. sometimes restrictedview inside extras does not have value"
so, this condition will help you.
data.concession.extras ? (data.concession.extras.restrictedview || data.concession.extras ) : null
Here is an example:
The first example has restrictedview and the second example doesn't.
data = {}
data.concession = { 'extras' : {} }
data.concession.extras = { 'restrictedview' : 'restrictedview value'}
data2 = {}
data2.concession = { 'extras' : 'extras value' }
var output = data.concession.extras ? (data.concession.extras.restrictedview || data.concession.extras ) : null
var output2 = data2.concession.extras ? (data2.concession.extras.restrictedview || data2.concession.extras ) : null
console.log(output)
console.log(output2)
PLEASE RUN THE ABOVE SNIPPET
Observables do try...catch, so for data structures it is possible to follow the pattern:
data$
.map(data => data.complex.path || null)
.catch(() => Observable.of(null))
But for nested structures this will result in complex observable hierarchy which is hard to comprehend.
So basically it is possible to treat complex paths to values with this recipe:
parking: ((data.concession || {}).extras || {}).parking || null
This case is conveniently treated by Lodash/Underscore get or a similar helper function:
parking: _.get(data, 'concession.extras.parking', null)
Related
Is there a way that I can simplify this code?
I was thinking if there is a way to set { ...filterItem, type: 'chip' } as the parameter in map function instead of creating a const that will be returned in each state.
Is this type of syntax possible to do? If so, is there a specific term for it?
filtersToUse = filtersToChip.map((filterItem) => {
const filterItem2 = { ...filterItem, type: 'chip' }
if (filterItem.id === '12345') {
return { ...filterItem2, labelOverride: 'new-label' }
} else if (filterItem.id === '67890') {
return { ...filterItem2, labelOverride: 'new-label' }
}
return filterItem2
})
Seems like you want to:
Add type: 'chip' too all the elements
Add labelOverride: 'new-label' if id is 12345 OR 67890
You could do something like:
filtersToUse = filtersToChip.map((filterItem) => ({
...filterItem,
type: 'chip',
...([ '12345', '67890'].includes(filterItem.id) ? { labelOverride: 'new-label' } : {})
});
Where we use object spreading to add the desired options, if needed
Couldn't you do this:
filtersToUse = filtersToChip.map((filterItem) => ({
...filterItem,
type: 'chip',
labelOverride: ['12345', '67890'].includes(filterItem.id)
? 'new-label'
: undefined,
}));
I don't know if that is what you're searching for but i would optimize like that.
const newLabelItemIds = ['12345', '67890'];
const filtersToUse = filtersToChip.map((filterItem) => {
const label = newLabelItemIds.include(filterItem.id) ? { label: 'new-label' } : {};
return {
...filterItem,
...label,
type: 'chip',
};
});
I want to create a dropdown (or mat-select) to use as a sorting mechanism instead of the Angular Material Sort Header. So, if I for example click on the 'username' inside the dropdown, I want the table to sort by the username (instead of clicking on the header).
How can I do it? Any documentation online on how to achieve this?
Thank you for any help.
As required, I attach some code:
ngOnInit(): void {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(""),
map((value) => this._filter(value))
);
}
ngAfterViewInit() {
this.providersAdmin.sort = this.sort;
}
getAllAdmins() {
this.isLoading = true;
this.homeService.getAllAdmins().subscribe(
(response) => {
this.admins = response;
this.providersAdmin = new MatTableDataSource(this.admins);
this.isLoading = false;
},
(error) => {}
);
}
sortTableBy(event: any) {
const sortState: Sort = {
active: "username",
direction: "desc",
};
this.sort.active = sortState.active;
this.sort.direction = sortState.direction;
this.sort.sortChange.emit(sortState);
console.log(event);
}
The sortTableBy method is the one I found on here but nothing happens.
I added matSort on the mat-table and I added mat-sort-header on the header cell.
EDIT:
Hi, I managed to fix the problem by writing the following:
sortTableBy(event: any) {
const sortState: Sort = {
active: "username",
direction: "desc",
};
this.sort.active = sortState.active;
this.sort.direction = sortState.direction;
this.sort.sortChange.emit(sortState);
this.providersAdmin.sort = this.sort;
}
There is an example for you:
Exmaple
Your sort function has a wrong implementation, this is work for me:
sortData(fieldName: string) {
if (!fieldName) {
return;
}
const sortState: MatSortable = {
id: fieldName,
start: 'desc',
disableClear: true
};
this.sort.sort(sortState);
}
I am going to set up an example which you can adapt easily:
compare(a: number | string, b: number | string, isAsc: boolean) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
sortData() {
let isAsc = this.sort.direction != "" ?
event.direction == SortDirection.asc :
true;
let data = this.dataSource.data.slice();
data.sort((a, b) => {
switch (this.myChosenSort) {
case 'healthCareCenterName':
return this.compare(a.healthCareCenterName, b.healthCareCenterName, isAsc);
case 'address':
return this.compare(a.address, b.address, isAsc);
case 'contact':
return this.compare(a.contact, b.contact, isAsc);
default:
return 0;
}
});
this.dataSource = new MatTableDataSource<ServiceProviderTable>(data);
}
To change the sort.direction you need to play around a little bit with the code, maybe directly from the dropdown and hardcoding the isAsc when calling the compare method, depending on the value of the this.myChosenSort.
I have a little problem with my function, one of the params that I want to set I'm getting from the http request. The problem is that the final data from method below is recalculated in one of the components, and when it happens the result of request is still null. When the response come it's not triggering onChanges so I can't recalculate this data again, and doCheck triggering "too often".
updateData(componentRecords: ComponentRecord[], importSourceGroup?: ImportSource[], isImportSource = false, component: DiaryNode = null) {
const recordData = [];
const records = isImportSource ? importSourceGroup : componentRecords;
for (const record of records) {
const recordRow: any = record.ID === 'addingRow' ? record : {
ID: record.ID,
InputTypeID: record.InputTypeID,
SubRecords: record.SubRecords,
attachmentField: record.Fields ? record.Fields.find(({Type}) => Type === DiaryFieldType.ATTACHMENT) : null,
documentsFolder: null,
DateUpdated: null,
ComponentInstanceID: null,
linkedUnits: {},
recordRef: record
};
if (record.ID !== 'addingRow') {
if (isImportSource) {
recordRow.DateUpdated = (record as ImportSource).DateUpdated;
recordRow.ComponentInstanceID = (record as ImportSource).ComponentInstanceID;
}
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
this.subManager.add(this.documentsApiService
.getFileDetailsByFolderID(recordRow.attachmentField.Value)
.subscribe((documents: DocumentsFolder) =>
recordRow.documentsFolder = documents));
}
if (record.Fields) {
for (const field of record.Fields) {
const label = field.Label;
recordRow[label] = field.Type === DiaryFieldType.INTEGER ? parseInt(field.Value, 10) : field.Value;
const schema = component && component.Schema && component.Schema.find(diaryFormField => diaryFormField.Label === label);
if (schema && schema.LinkedUnit) {
recordRow.linkedUnits[label] = schema.LinkedUnit.Attributes.List.PickListItems[0].Label;
}
}
}
}
recordData.push(recordRow);
}
return recordData;
}
The part that is async is
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
this.subManager.add(this.documentsApiService
.getFileDetailsByFolderID(recordRow.attachmentField.Value)
.subscribe((documents: DocumentsFolder) =>
recordRow.documentsFolder = documents));
}
So I don't know what is the best solution for this but I was wondering if it's possible to wait here for the response, and go furthure when it comes.
What do you think?
In short the property cannot be assigned synchronously using the asynchronous HTTP request. Instead you need to make entire paradigm asynchronous. Then you could subscribe to the function updateData() to fetch the array.
Additionally you could use RxJS forkJoin function to combine multiple parallel observables. Try the following
updateData(
componentRecords: ComponentRecord[],
importSourceGroup?: ImportSource[],
isImportSource = false,
component: DiaryNode = null
): Observable<any> { // <-- return `Observable` here
const records = isImportSource ? importSourceGroup : componentRecords;
return forkJoin( // <-- use `forkJoin` to combine multiple parallel observables
records.map(record => {
const recordRow: any = record.ID === 'addingRow' ? record : {
ID: record.ID,
InputTypeID: record.InputTypeID,
SubRecords: record.SubRecords,
attachmentField: record.Fields ? record.Fields.find(({Type}) => Type === DiaryFieldType.ATTACHMENT) : null,
documentsFolder: null,
DateUpdated: null,
ComponentInstanceID: null,
linkedUnits: {},
recordRef: record
};
if (record.ID !== 'addingRow') {
if (isImportSource) {
recordRow.DateUpdated = (record as ImportSource).DateUpdated;
recordRow.ComponentInstanceID = (record as ImportSource).ComponentInstanceID;
}
if (record.Fields) {
for (const field of record.Fields) {
const label = field.Label;
recordRow[label] = field.Type === DiaryFieldType.INTEGER ? parseInt(field.Value, 10) : field.Value;
const schema = component && component.Schema && component.Schema.find(diaryFormField => diaryFormField.Label === label);
if (schema && schema.LinkedUnit) {
recordRow.linkedUnits[label] = schema.LinkedUnit.Attributes.List.PickListItems[0].Label;
}
}
}
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
return this.documentsApiService.getFileDetailsByFolderID(recordRow.attachmentField.Value).pipe( // <-- return the HTTP request
map((documents: DocumentsFolder) => ({ ...recordRow, recordRow.documentsFolder: documents })) // <-- spread operator to append new value to object
);
}
return of(recordRow); // <-- use `of()` to return as observable
}
return of(recordRow); // <-- use `of()` to return as observable
})
);
}
See here to learn more about fetching info from async request.
i get flat list from server and i must create a tree that list .
this is my model :
export interface ClaimManagerList {
id: number;
title: string;
parentId: number;
isChilde: boolean;
childs: Childes[];
}
export interface Childes {
id: number;
title: string;
parentId: number;
isChilde: boolean;
}
and in this code i convert flat list to tree list -> childs add to this property childs :
return this.claimsManagerService.getAll(this.searchParam).pipe(
map(data => {
data['records'].forEach(element => {
let model = {} as ClaimManagerList;
if (element.parentId == null) {
model.id = element.id;
model.isChilde = element.isChilde;
model.parentId = element.parentId;
model.title = element.title;
data['records'].forEach(child => {
if (child.parentId == element.id) {
let childe = {} as Childes;
childe.id = child.id;
childe.isChilde = child.isChilde;
childe.parentId = child.parentId;
childe.title = child.title;
model.childs.push(childe)
}
})
this.claims.push(model)
}
})
return this.claims;
})
but it show me error in this line :
model.childs.push(childe)
Cannot read property 'push'
whats the problem ? how can i solve this problem ?
This happening as model.childs is not set to an empty array at the beginning. We can resolve this like:
if(!model.childs) model.childs = [] as Childes[];
model.childs.push(childe) // This line should work fine now.
I'm going to propose some changes to your code to order to improve this. I hope these changes will be useful for you.
return this.claimsManagerService.getAll(this.searchParam).pipe(
map((data: any) => {
data.records.forEach((element: any) => {
let model: ClaimManagerList = {};
if (element.parentId == null) {
model.id = element.id;
model.isChilde = element.isChilde;
model.parentId = element.parentId;
model.title = element.title;
model.childs = [];
data.records.forEach((child: any) => {
if (child.parentId == element.id) {
let childe = {} as Childes;
childe.id = child.id;
childe.isChilde = child.isChilde;
childe.parentId = child.parentId;
childe.title = child.title;
model.childs.push(childe)
}
})
this.claims.push(model)
}
})
return this.claims;
})
Tell me, how correctly to check the existence of a key in associative arrays?
For example:
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: ''
value2" '',
},
},
subkey2: '';
},
}
if ((mydata.key2 != undefined) && (mydata.key2.subkey1 != undefined) && (mydata.key2.subkey1.subkey1_1 != undefined))
mydata.key2.subkey1.subkey1_1.value1 = 'test';
Too long and confusing
((mydata.key2 != undefined) && (mydata.key2.subkey1 != undefined) && (mydata.key2.subkey1.subkey1_1 != undefined))
I would like to use a simpler function, like
safeSet(mydata.key2.subkey1.subkey1_1.value1, 'test');
or
if (is_undefined(mydata.key2.subkey1.subkey1_1.value1) == true)
mydata.key2.subkey1.subkey1_1.value1 = 'test'; // now - error if 'mydata.key2.subkey1.subkey1_1' not exist
You can create custom function using reduce() to test if nested property exists. You can just pass key as string.
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: '',
value2: '',
},
},
subkey2: ''
},
}
function safeSet(key, data) {
return key.split('.').reduce(function(r, e) {
return r ? r[e] : undefined;
}, data) != undefined
}
console.log(safeSet('key2.subkey1.subkey1_1.value1', mydata))
You should use the in operator:
"key" in obj // true, regardless of the actual value
Or, if you want to particularly test for properties of the object instance (and not inherited properties), use hasOwnProperty:
obj.hasOwnProperty("key") // true
hope this would help you.
Source: http://www.advancesharp.com/questions/628/checking-if-an-associative-array-key-exists-in-javascript
Alternatively, you can make use of the .has() method of Lodash.
Then, you would only need to check:
if (_.has(mydata, 'key2.subkey1.subkey1_1.value1')
mydata.key2.subkey1.subkey1_1.value1 = 'test';
For trying to get something in a nested structure I'd do something like this:
function getPath(element, path) {
var handledSoFar = [];
for (var i = 0; i < path.length; i++) {
var property = path[i];
handledSoFar.push(property);
if (typeof element[property] === 'undefined') {
throw new Error('Path ' + handledSoFar.join('->') + ' is undefined');
}
element = object[property];
}
return element;
}
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: '',
value2: 'hi'
}
},
subkey2: ''
}
};
// Prints 'hi'
console.log(getPath(mydata, ['key2', 'subkey1', 'subkey1_1', 'value2']));
// Throws error 'Path key2->subkey2->subkey1_1 is undefined'
console.log(getPath(mydata, ['key2', 'subkey1', 'subkey1_1', 'value2']));
Of course keeping track of the search in handledSoFar is optional but might be useful for development / debugging.
You can also use the lodash deep field selector: lodash.get (documentation)
const get = require('lodash.get');
const set = require('lodash.set');
if (!get(mydata, 'key2.subkey1.subkey1_1.value1')) {
set(mydata, 'key2.subkey1.subkey1_1.value1', 'test');
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = { key1: '', key2: { subkey1: { subkey1_1: { value1: '', value2: '' } }, subkey2: '' } };
setValue(object, 'key2.subkey1.subkey1_1.value1', 'test');
console.log(object);
The problem with the example function that you proposed:
safeSet(mydata.key2.subkey1.subkey1_1.value1, 'test');
or
is_undefined(mydata.key2.subkey1.subkey1_1.value1)
Is that the mydata.key2.subkey1... part is run before the function is called. So if one of the subkeys does not exist, an exception will be thrown before your code is reached.
You could get something similar using a callback though...
safeSet(function(val) { mydata.key2.subkey1.subkey1_1.value1 = val; }, 'test')
the implementation of safeSet would then be:
var safeSet = function(setterFunc, val) {
try {
setterFunc(val);
} catch (e) {
if (e instanceof TypeError) {
return false;
} else {
throw e;
}
}
return true;
}
safeSet returns true if the value was set, and false otherwise.