Processing an Immutable.js list and extracting unique objects - javascript

I have the below type script that uses immutables. I am looking to use immutables List to process the students and generate a list of students that are unique. My initial list can have duplicate students objects and I can filter them out by studentId.
Issue that I am facing is that , I am not able to access the studentId attribute from the Student Object when iterating the list, in line studentId is not accessible.
if (d && !studentIdSeen.has(d!.studentId)
export interface StudentProperties {
studentId: number;
student: string;
region: string;
active: boolean;
}
export interface Student extends TypedRecord<Student>, StudentProperties { }
export const studentFactory = makeTypedFactory<StudentProperties, Student>({ studentId: -1, student: '', region: '', active: true });
REDUX CALL
case types.FETCH_STUDENT_DATA_SUCCESS: {
let fetchedStudents = List(action.data.map(item => studentFactory(item)));
console.log("Fetched student data ==> " + fetchedStudents);
let uniqueStudents: Student[] = [];
let studentIdSeen: Set<number>;
fetchedStudents.forEach(d => {
if (d && !studentIdSeen.has(d!.studentId) { //error here , not able to access studentId attribute
uniqueStudents.push(d);
studentIdSeen.add(d!.studentId);////error here , not able to access studentId attribute
}
});
console.log("List of unique students = " + uniqueStudents);
return state.set('students', List(uniqueStudents));
}
If I return the fetchedStudents in state.set('students',fetchedStudents) it works fine so there is no issue with the data that is being fetched.

Fist:
d!.studentId
is wrong, you only want
d.studentId
both in: !studentIdSeen.has(d!.studentId)
and: studentIdSeen.add(d!.studentId);
Second, if possible you could cast to a set which removes duplicates. Then back to array:
let uniqueStudents: Student[] = [... new Set(fetchedStudents)]
Or
let uniqueStudents: Student[] = [];
fetchedStudents.forEach(d => {
if (d && !uniqueStudents.has(x => x.studentId === d.studentId)) {
uniqueStudents.push(d);
}
});

Related

Typescript filter returning an empty array in Angular

I have a filtering function in Angular that is returning an empty array. I know there similar questions have been asked before, and i have tried all of them, but have failed. The function itself seems correct. Any help would be appreciated.
gifts is an array of gift objects (see below)
Typescript:
export class DetailsPageComponent implements OnInit {
gifts: Array<Gift> = [ ... ]
id: number
currentGift: any
constructor(private currentRoute: ActivatedRoute) { }
ngOnInit(): void {
this.currentRoute.paramMap.subscribe((params: ParamMap) => {
this.id = +params.get('id')
this.currentGift = this.gifts.filter(gift => {
gift.id == this.id
})
})
}
}
A gift object:
{
name: 'giftName',
id: 22,
}
UPDATE Changing gift.id == this.id to gift.id == 22 did return a value. Which means that this.id is not being fetched properly. Not sure why... Any ideas?
The filter function
this.currentGift = this.gifts.filter(gift => {
gift.id == this.id
})
doesn't return anything. It has to return a boolean value:
this.currentGift = this.gifts.filter(gift => gift.id === this.id)
Here is an example:
const gifts = [{
name: 'giftName',
id: 22,
}];
const id = +'22';
const currentGift = gifts.filter(gift => gift.id === id);
console.log(currentGift);
You have to use a URL like localhost:4200/details-page/11 instead of localhost:4200/details-page/:11 otherwise params.get('id') will be set to ':11' and this string obviously can't be converted to a number.

Assign dynamically nested array of classes

I need to be able to receive data from an external API and map it dynamically to classes. When the data is plain object, a simple Object.assign do the job, but when there's nested objects you need to call Object.assign to all nested objects.
The approach which I used was to create a recursive function, but I stumble in this case where there's a nested array of objects.
Classes
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
}
Function to initialize a class
function create(instance: object, data: any) {
for (const [key, value] of Object.entries(instance)) {
if (Array.isArray(value)) {
for (const element of data[key]) {
// get the type of the element in array dynamically
const newElement = new User();
create(newElement, element)
value.push(newElement);
}
} else if (typeof value === 'object') {
create(value, data[key]);
}
Object.assign(value, data);
}
}
const orgWithError = Object.assign(new Organization(), { admin: { id: 'admin-external' }});
console.log(orgWithError.admin.getFullName()); // orgWithError.admin.getFullName is not a function
const org = new Organization();
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
create(org, data);
// this case works because I manually initialize the user in the create function
// but I need this function to be generic to any class
console.log(org.users[0].getFullName()); // "name surname"
Initially I was trying to first scan the classes and map it and then do the assign, but the problem with the array of object would happen anyway I think.
As far as I understand from your code, what you basically want to do is, given an object, determine, what class it is supposed to represent: Organization, Account or User.
So you need a way to distinguish between different kinds of objects in some way. One option may be to add a type field to the API response, but this will only work if you have access to the API code, which you apparently don't. Another option would be to check if an object has some fields that are unique to the class it represents, like admin for Organization or account for User. But it seems like your API response doesn't always contain all the fields that the class does, so this might also not work.
So why do you need this distinction in the first place? It seems like the only kind of array that your API may send is array of users, so you could just stick to what you have now, anyway there are no other arrays that may show up.
Also a solution that I find more logical is not to depend on Object.assign to just assign all properties somehow by itself, but to do it manually, maybe create a factory function, like I did in the code below. That approach gives you more control, also you can perform some validation in these factory methods, in case you will need it
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
static fromApiResponse(data: any) {
const org = new Organization()
if(data.id) org.id = data.id
if(data.admin) org.admin = User.fromApiResponse(data.admin)
if(data.users) {
this.users = org.users.map(user => User.fromApiResponse(user))
}
return org
}
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
static fromApiResponse(data: any) {
const user = new User()
if(data.id) user.id = data.id
if(data.name) user.name = data.name
if(data.account)
user.account = Account.fromApiResponse(data.account)
return user
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
static fromApiResponse(data: any) {
const acc = new Account()
if(data.id) acc.id = data.id
if(data.money) acc.money = data.money
return acc
}
}
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const organization = Organization.fromApiResponse(data)
I can't conceive of a way to do this generically without any configuration. But I can come up with a way to do this using a configuration object that looks like this:
{
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
}
and a pointer to the root node, 'org'.
The keys of this object are simple handles for your type/subtypes. Each one is mapped to an object that has a _ctor property pointing to a constructor function, and a collection of other properties that are the names of members of your object and matching properties of your input. Those then are references to other handles. For an array, the handle is [surrounded by square brackets].
Here's an implementation of this idea:
const create = (root, config) => (data, {_ctor, ...keys} = config [root]) =>
Object.assign (new _ctor (), Object .fromEntries (Object .entries (data) .map (
([k, v]) =>
k in keys
? [k, /^\[.*\]$/ .test (keys [k])
? v .map (o => create (keys [k] .slice (1, -1), config) (o))
: create (keys [k], config) (v)
]
: [k, v]
)))
class Organization {
constructor () { this.id = 'org1'; this.admin = new User(); this.users = [] }
}
class User {
constructor () { this.id = 'user1'; this.name = 'name'; this.account = new Account() }
getFullName () { return `${this.name} surname`}
}
class Account {
constructor () { this.id = 'account1'; this.money = 10 }
calculate () { return 10 * 2 }
}
const createOrganization = create ('org', {
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
})
const orgWithoutError = createOrganization ({ admin: { id: 'admin-external' }});
console .log (orgWithoutError .admin .getFullName ()) // has the right properties
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const org = createOrganization (data)
console .log (org .users [0] .getFullName ()) // has the right properties
console .log ([
org .constructor .name,
org .admin .constructor.name, // has the correct hierarchy
org .users [0]. account. constructor .name
] .join (', '))
console .log (org) // entire object is correct
.as-console-wrapper {min-height: 100% !important; top: 0}
The main function, create, receives the name of the root node and such a configuration object. It returns a function which takes a plain JS object and hydrates it into your Object structure. Note that it doesn't require you to pre-construct the objects as does your attempt. All the calling of constructors is done internally to the function.
I'm not much of a Typescript user, and I don't have a clue about how to type such a function, or whether TS is even capable of doing so. (I think there's a reasonable chance that it is not.)
There are many ways that this might be expanded, if needed. We might want to allow for property names that vary between your input structure and the object member name, or we might want to allow other collection types besides arrays. If so, we probably would need a somewhat more sophisticated configuration structure, perhaps something like this:
{
org: { _ctor: Organization, admin: {type: 'usr'}, users: {type: Array, itemType: 'usr'} },
usr: { _ctor: User, account: {type: 'acct', renameTo: 'clientAcct'} },
acct: { _ctor: Account }
}
But that's for another day.
It's not clear whether this approach even comes close to meeting your needs, but it was an interesting problem to consider.

Convert all MirageJS ids to integers

MirageJS provides all model ids as strings. Our backend uses integers, which are convenient for sorting and so on. After reading around MirageJS does not support integer IDs out of the box. From the conversations I've read the best solution would be to convert Ids in a serializer.
Output:
{
id: "1",
title: "Some title",
otherValue: "Some other value"
}
But what I want is:
Expected Output:
{
id: 1,
title: "Some title",
otherValue: "Some other value"
}
I really want to convert ALL ids. This would included nested objects, and serialized Ids.
I think you should be able to use a custom IdentityManager for this. Here's a REPL example. (Note: REPL is a work in progress + currently only works on Chrome).
Here's the code:
import { Server, Model } from "miragejs";
class IntegerIDManager {
constructor() {
this.ids = new Set();
this.nextId = 1;
}
// Returns a new unused unique identifier.
fetch() {
let id = this.nextId++;
this.ids.add(id);
return id;
}
// Registers an identifier as used. Must throw if identifier is already used.
set(id) {
if (this.ids.has(id)) {
throw new Error('ID ' + id + 'has already been used.');
}
this.ids.add(id);
}
// Resets all used identifiers to unused.
reset() {
this.ids.clear();
}
}
export default new Server({
identityManagers: {
application: IntegerIDManager,
},
models: {
user: Model,
},
seeds(server) {
server.createList("user", 3);
},
routes() {
this.resource("user");
},
});
When I make a GET request to /users with this server I get integer IDs back.
My solution is to traverse the data and recursively convert all Ids. It's working pretty well.
I have a number of other requirements, like removing the data key and embedding or serializing Ids.
const ApplicationSerializer = Serializer.extend({
root: true,
serialize(resource, request) {
// required to serializedIds
// handle removing root key
const json = Serializer.prototype.serialize.apply(this, arguments)
const root = resource.models
? this.keyForCollection(resource.modelName)
: this.keyForModel(resource.modelName)
const keyedItem = json[root]
// convert single string id to integer
const idToInt = id => Number(id)
// convert array of ids to integers
const idsToInt = ids => ids.map(id => idToInt(id))
// check if the data being passed is a collection or model
const isCollection = data => Array.isArray(data)
// check if data should be traversed
const shouldTraverse = entry =>
Array.isArray(entry) || entry instanceof Object
// check if the entry is an id
const isIdKey = key => key === 'id'
// check for serialized Ids
// don't be stupid and create an array of values with a key like `arachnIds`
const isIdArray = (key, value) =>
key.slice(key.length - 3, key.length) === 'Ids' && Array.isArray(value)
// traverse the passed model and update Ids where required, keeping other entries as is
const traverseModel = model =>
Object.entries(model).reduce(
(a, c) =>
isIdKey(c[0])
? // convert id to int
{ ...a, [c[0]]: idToInt(c[1]) }
: // convert id array to int
isIdArray(c[0], c[1])
? { ...a, [c[0]]: idsToInt(c[1]) }
: // traverse nested entries
shouldTraverse(c[1])
? { ...a, [c[0]]: applyFuncToModels(c[1]) }
: // keep regular entries
{ ...a, [c[0]]: c[1] },
{}
)
// start traversal of data
const applyFuncToModels = data =>
isCollection(data)
? data.map(model =>
// confirm we're working with a model, and not a value
model instance of Object ? traverseModel(model) : model)
: traverseModel(data)
return applyFuncToModels(keyedItem)
}
})
I had to solve this problem as well (fingers crossed that this gets included into the library) and my use case is simpler than the first answer.
function convertIdsToNumbers(o) {
Object.keys(o).forEach((k) => {
const v = o[k]
if (Array.isArray(v) || v instanceof Object) convertIdsToNumbers(v)
if (k === 'id' || /.*Id$/.test(k)) {
o[k] = Number(v)
}
})
}
const ApplicationSerializer = RestSerializer.extend({
root: false,
embed: true,
serialize(object, request) {
let json = Serializer.prototype.serialize.apply(this, arguments)
convertIdsToNumbers(json)
return {
status: request.status,
payload: json,
}
},
})

cloud firestore only add object if its name is unique

Object1 looks like this { category1: '', category2: '', description: '' }
Category1 has an array field for [category2] and a Name
My firebase contains 3 documents: 1 for object1 1 for Category1 and the last one is not relevant.
In my Ionic application I have made an input field where the user can paste an array of JSON objects. these JSON objects get converted to Object1 and added to the database.
My problem is when category1 is new it should be added to the firebase document for category1 but can't be a duplicate. Or when category2 is new it should update category1.
I think the solution is to make category1 unique Id its property name, so I can check with firebase security rules if it already exists when trying to add it.
Can someone explain to me how to set name as the unique Id of this document or does anyone know a better solution?
in my service:
private category1Collection: AngularFirestoreCollection<Category1>;
private category1s: Observable<Category1[]>;
constructor(db: AngularFirestore) {
this.category1Collection = db.collection<category1>('category1s');
var docRef = db.collection("category1s").doc("category1s");
this.category1s= this.category1Collection.snapshotChanges().pipe(
map(actions => {
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
return { id, ...data };
})
})
)
}
addItem(category1: Category1) {
return this.category1Collection.add(category1);
}
I fixed it:
changed code in the service to:
addItem(category1: Category1) {
return this.category1Collection.doc(category1.Naam).set(category1);
}
and this code in the page.ts:
this.category1Service.getItem(HF.Naam).subscribe(data => {
if (data != null) {
console.log(data)
HF.category2s = data.category2s;
if (!this.containsCategory2(element.category2, HF.category2s)) {
HF.category2s.push(element.category2);
this.category1Service.addItem(HF);
}
} else {
this.category1Service.addItem(HF);
}
});
containsCategory2(category2: string, list: string[]) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i] === category2) {
return true;
}
}
return false;
}

Ordering or sorting objects in state [React JS]

There is a function that adds objects product to products in state
addProductToCart(productId, productName)
{
var product = {
id: productId,
name: productName,
};
this.setState({
products: {
...this.state.products,
[product.id]: product
}
});
}
but these objects are sorted by [product.id]. How do I sort them in the order they are added to the cart?
maintain one more array ids in which, you can append the productIds as they are added :
addProductToCart(productId, productName)
{
var product = {
id: productId,
name: productName,
};
this.setState({
products: {
...this.state.products,
[product.id]: product
},
productIds : [...ids, product.id]
});
}
You can then iterate over the array to retrieve the product in the order of their insertion.
If you read this objects are by default key value pairs,
You have 2 options you can use #Easwar solution or you can use array instead to store.
As far as I see your strucutre there is nothing wrong in using the array structure for your requirement.
You should restructure your state like this
constructor() {
super();
this.addProductToCart = this.addProductToCart.bind(this);
this.state = {
products: []
};
}
addProductToCart() {
productsId--;
var product = {
id: productsId,
name: 'test',
};
this.setState({
products: [...this.state.products,product]
});
}
Demo
As I can see you have a problem getting removed object from array you can use this easily
removefromCart(value) {
var array = [...this.state.products]; // make a separate copy of the array
var index = array.findIndex(a => a.id === value);
if (index !== -1) {
array.splice(index, 1);
this.setState({ products: array });
}
}

Categories