How to map two Oberservable arrays based on a value? - arrays

I have two observable arrays.
habits$: Observable<Habit[]>;
status$: Observable<Status[]>;
The habit has the following structure:
export interface Habit {
habitId: number;
name: string;
repeatVal: number;
notification: string;
color: string;
question: string;
id: number;
}
The status array has the habitId, date and status as keys.
I want to merge both habits$ and status$ into one Observable array based on the habitId attribute present in both of them.
I have done some research and a combination of mergeMap() and forkJoin() seems to be the way to go, like shown here
Combine multiple observable arrays into new object array
But I can't figure out a syntax that will work for me.
This is how my code looks at the moment:
ngOnInit() {
this.habits$ = this.habitService.fetchAllById(this.authService.userId);
console.log(this.habits$);
this.status$ = this.statusService.fetchAll();
this.habits$.pipe(
mergeMap(habits => {
this.status$ = this.statusService.fetchAll().filter(status => status.habitId == habits.habitId);
}));
console.log(this.joined$);}

I would use forkJoin in the following fashion:
ngOnInit() {
forkJoin([
this.habitService.fetchAllById(this.authService.userId),
this.status$ = this.statusService.fetchAll()
]).subscribe(([habits, statuses]) => {
this.joined = habits.map(habit => ({
...statuses.find(t => t.habitId === habit.habitId),
...habit
}));
});
}
see simple demo

Related

Generic Typescript function to check for duplicates in an array

I'm trying make a generic Typescript function to check if an array contains duplicates. For example:
interface Student {
name: string;
class: string;
};
const students: Student[] = [
{ name: 'John Smith', class: 'Science' },
{ name: 'Edward Ryan', class: 'Math' },
{ name: 'Jessica Li', class: 'Social Studies'},
{ name: 'John Smith', class: 'English'}
];
That is the data.
This is what I want to do with the data:
const registerStudents = async (students: Student[]): Promise<void> {
checkDuplicate(students, existingState); //This is the function I want to build
const response = await axios.post('/students/new', students)
existingState.push(response); //pushes newly registers students to the existing state
};
Regarding the checkDuplicate(), I want to make it a generic function, but I'm struggling with the logic.
export const checkDuplicate = <T>(items: T[], existingState: T[]): void {
//checks if the items have any duplicate names, in this case, it would be 'John Smith', and if so, throw an error
//Also checks if items have any duplicate names with the existingState of the application, and if so, throw an error
if (duplicate) {
throw new Error('contains identical information')
};
};
It's a little bit complex and I haven't been able to figure out the logic to work with typescript. Any advice on how I can implement this would be appreciated!
One reasonable way to approach this is to have checkDuplicate() take a single array items of generic type T[], and another array keysToCheck of type K[], where K is a keylike type (or union of keylike types) and where T is a type with keys in K and whose values at those keys are strings. That is, the call signature of checkDuplicate() should be
declare const checkDuplicate: <T extends Record<K, string>, K extends PropertyKey>(
items: T[],
keysToCheck: K[]
) => void;
This function should iterate over both items and keysToCheck, and if it finds an item where a property is the same string as the same property in a previous item, it should throw an error.
If you had such a function, you could write the version which accepts students and existingState, two arrays of Student objects, like this:
function checkDuplicateStudents(students: Student[], existingState: Student[]) {
checkDuplicate([...students, ...existingState], ["name", "class"]);
}
where we are spreading the students and existingState arrays into a single array to pass as items to checkDuplicate(), and since we are checking Student we are passing ["name", "class"] as keysToCheck.
Here's a possible implementation of checkDuplicate():
const checkDuplicate = <T extends Record<K, string>, K extends PropertyKey>(
items: T[],
keysToCheck: K[]
): void => {
const vals = {} as Record<K, Set<string>>;
keysToCheck.forEach(key => vals[key] = new Set());
for (let item of items) {
for (let key of keysToCheck) {
const val: string = item[key];
const valSet: Set<string> = vals[key]
if (valSet.has(val)) {
throw new Error(
'contains identical information at key "' +
key + '" with value "' + val + '"');
};
valSet.add(val);
}
}
}
The way it works is that we create an object named vals with one key for each element key of keysToCheck. Each element vals[key] is the Set of strings we have already seen for that key. Every time we see a string-valued property val with key key in any item in the items array, we check whether the set in vals[key] has val. If so, we've seen this value for this key before, so we throw an error. If not, we add it to the set.
(Note that it's possible to replace Set<string> with a plain object of the form Record<string, true | undefined>, as shown in Mimicking sets in JavaScript? , but I'm using Set here for clarity.)
Okay, let's test it against your example:
checkDuplicateStudents(students, []);
// contains identical information at key "name" with value "John Smith"
Looks good. It throws an error at runtime and properly identifies the duplicate data.
Playground link to code

Angular/TypeScript update element of array of objects

please forgive if my question is stupid but I have no idea what am I doing wrong.
I have array of objects
export interface IOptionsData {
option: string;
optionPrice: number;
benefit: string;
benefitPrice: number;
oneshotPrice: number;
commitmentPeriod: number;
}
formData: IOptionsData[] = [];
And saving data from form in it
onOptionChange(i) {
console.log(i);
console.log(this.productForm.controls['user_options']);
this.formData[i].option = this.productForm.controls['user_options'].value[i].option;
this.formData[i].commitmentPeriod = this.productForm.controls['user_options'].value[i].commitmentPeriod;
console.log(this.formData);
...
}
On first element everything seems normal
My form data:
And my array data:
But when I update another item in form, instead of updating that element in array, it updates complete array to that values
onChange function is called with correct parameter, there is no multiple functions calls
Have no idea why is this happening
Please if you know any reason why it would
Thank you
EDIT: forgot to mention
Before I went with array of objects, I used individual arrays for each value I need to store.
selectedOptions: string[] = [];
selectedBenefits: string[] = [];
selectedPrices: number[] = [];
benefitPrices: number[] = [];
oneshotBenefitPrices: number[] = [];
And same update worked as intended, only updated value is getting updated
this.selectedOptions[i] = this.productForm.controls['user_options'].value[i].option;
EDIT No2:
To reproduce this error you don't need html template.
Here is part of .ts file
export interface IOptionsData {
option: string;
optionPrice: number;
benefit: string;
benefitPrice: number;
oneshotPrice: number;
commitmentPeriod: number;
}
demoOptionsData: IOptionsData = { option: null, optionPrice: 0, benefit: null, benefitPrice: 0, oneshotPrice: 0, commitmentPeriod: 24 };
ngOnInit() {
/* Initiate the form structure */
this.formData.push(this.demoOptionsData);
this.formData.push(this.demoOptionsData);
this.formData.push(this.demoOptionsData);
this.formData[1].option = 'TEST';
console.log(this.formData);
}
Solved by comment from #htn
formData is an array of the SAME REFERENCE demoOptionsData, so, the result is expected

Search inside an array to see how many times each object has been used

We have 2 arrays one is the AnswersList[] this contains all possible answers for questions.
we have split AnswersList[] into 6 separate Arrays {gender, age, disability, ethnic origin, religion, sexual orientation}. For this issue if we use genderList[].
The main array is EqualityAnswers[] this is of type IApplicantAnswers.
import {IAnswers} from "../Enum/answer.model";
export interface IApplicantAnswers {
ApplicantAnswersKey: number;
CompetitionKey: number;
Gender: IAnswers;
Age: IAnswers;
SexualOrientation: IAnswers;
Religion: IAnswers;
EthnicOrigin: IAnswers;
Disability:IAnswers;
}
what we want to be able to do is check in genderList[] which is also of type IAnswer
export interface IAnswers {
AnswerKey: number;
QuestionKey: number;
Name: string;
Description: string;
}
so what i want to do is for each type of genderList[i].AnswerKey i want to see how many people have selected that in the EqualityAnswers[].
initially i was looking at looping through genderList[] and then filter EqualityAnswers[].Gender.AnswerKey and return the length.
The issue is assigning this to an array of ResourceList
export class ResourceList {
Name: string;
Count: number;
}
called genderCountList: ResourceList[];
the idea is that genderCountList will be {name:"female", count:2}, {name:"male", count:5}, {name:"prefer not to say", count: 3}
then I will be able to use this to construct my tables dynamically. This will allow for us to add answers at database level.
The only issue is i have mind lock and cant think how to structure this.
So with the help from Ricardo which I am very grateful for I managed to get this working from his advice.
getListOfGendersWithCount(): void {
var self = this;
this.genderList.map(function (item, index) {
var gender = new ResourceList();
gender.Name = item.Description;
gender.Count = self.equalityData.filter(x => x.Gender.AnswerKey === item.AnswerKey).length;
self.genderCountList.push(gender);
return item;
});
}

How to extract array from json on Angular

I'm working with angular2 ( version 5).
I make an http request an get back json.
I know how to access and use value but not the array.
and I don't find how to extract the two array inside element.
here my json:
{ "ImpiantiProva": [
{
"nomeImpianto":"MFL1",
"descrImpianto":"Multifilo 1",
"posizione":"Place1",
"dati_status": "true",
"unita_misura": "m/s",
"vel_attuale": 11.5,
"vel": [24.5,13.6,34.6,12.1],
"orario": ["17.05","17.06","17.07","17.08"]
},
{
"nomeImpianto":"MFL2",
"descrImpianto":"Multifilo 2",
"posizione":"Place2",
"dati_status": "true",
"unita_misura": "m/s",
"vel_attuale": 12.5,
"vel": [24.5,13.6,34.6,12.1],
"orario": ["17.05","17.06","17.07","17.08"]
}
]
}
In the data.service.ts I have the http request and it store values on :
stream$: Observable<ImpiantoModel[]>;
here my definition of the model:
#impianto.model
export class ImpiantoModel {
nomeImpianto: string;
descrImpianto: string;
posizione: string;
dati_status: string;
unita_misura: string;
vel_attuale: number;
vel: VelocitaModel[];
orario: OrariModel[];
}
#orari.model.ts
export class OrariModel {
orario: string;
}
#velocita.model.ts
export class VelocitaModel{
vel : number;
}
is it the right why to define my object?
How can I use the array "vel" and "orario"?
How can I print (access) the array "vel" of machine with "nomeImpianto" = "MFL1" ?
and how can I copy the array "vel" on new array?
thank you very much!
Here is what I understood of what you want to do : get the item in your json resp and put it in your object , so the best way is to create a static method directly when you get the json response, before returning the value create this adapter adaptImpiant(jsonObj) which will do something like :
adaptImpiant(jsonObj) {
let impiantTab = [];
jsonObj.ImpiantiProva.forEach((item) => {
let impiantoModel = {};
// impiantoModel = item if the model (below) match the item;
// if not manually set all your var like your velocita
let velocita = [] // is an array or an object with an array
// if class velocita = {}
velocita = item.vel.slice(0);
// if class velocita.valuesTab = item.vel.slice(0);
impiantoModel.velocita = velocita;
impiantTab.push(impiantoModel);
}
}
Your model seems wrong in this case, because you already use a ImpiantoModel array, so just create a class with whatever you want in :
#impianto.model
export class ImpiantoModel {
nomeImpianto: string;
descrImpianto: string;
posizione: string;
dati_status: string;
unita_misura: string;
vel_attuale: number;
vel: VelocitaModel // or simply [];
orario: OrariModel // or simply [];
}
I'm not sure I understand you, but I'll try.
is it the right why to define my object?
It should be:
export class ImpiantoModel {
nomeImpianto: string;
descrImpianto: string;
posizione: string;
dati_status: string;
unita_misura: string;
vel_attuale: number;
vel: Array<string>;
orario: Array<string>;
}
(But I have to confess, I don't know why model and not an interface)
How can I use the array "vel" and "orario"?
What do you mean?
How can I print (access) the array "vel" of machine with
"nomeImpianto" = "MFL1"
const thisContainsTheDataFromVel = whereYourDataIsStored['ImpiantiProva'].find((item) => { item['nomeImpianto'] === 'MFL1'})['vel'];
and how can I copy the array "vel" on new array?
UPDATE after reading your comment under this answer:
I took code from your example and added what you are missing. I made it so it can be more reusable (it can be enhanced even more, but I hope you understand the code and do what you need).
copyArray(data, targetValue) {
const mfl1Data = data.find((item) => item['nomeImpianto'] === targetValue);
if (mfl1Data) {
return mfl1Data.vel;
}
return [];
}
getdata2() {
this.http.get<ImpiantoModel[]>(this.myUrl)
.subscribe(
data => {
this.variableToStoreIn = this.copyArray(data, 'MFL1');
data.forEach(item => {
this.sub$.next(item);
});
});
return this.sub$;
}
CopyArray finds the data and returns it. If you don't want it like this, but just set a value of some property to the value of vel array then you can change it to:
copyArray(data) {
const mfl1Data = data.find((item) => item['nomeImpianto'] === targetValue);
if (mfl1Data) {
this.yourVariable = mfl1Data.vel;
}
}
If this answer is sufficient, please consider to mark it as the best answer, thank you.
According to your model classes, your JSON is wrong. You should have something like this:
{ "ImpiantiProva": [
{
"nomeImpianto":"MFL1",
"descrImpianto":"Multifilo 1",
"posizione":"Place1",
"dati_status": "true",
"unita_misura": "m/s",
"vel_attuale": 11.5,
"vel": [
{
"vel": 24.5
},
{
"vel": 13.6
}
...
],
"orario": [
{
"orario": "17.05"
},
{
"orario": "17.06"
}
...
]
}
]
}
Your model expects ImpiantoModel.vel and ImpiantoModel.orario to be arrays of objects. In your JSON response one is an array of numbers and the other of strings.
An if you want to use it in an HTML template, considering that you have a class attribute in your .ts file like this:
private impiantoModels: ImpiantoModel[];
You could do something like this inside your .html template:
<div *ngFor="let impModel of impiantoModels">
...
<div *ngFor="let v of impModel.vel">
<p>{{v.vel}}</p>
</div>
<div *ngFor="let o of impModel.orario">
<p>{{o.orario}}</p>
</div>
</div>

One type definition to cover properties of primitive and generic types without unioning

This is the pattern that requires me to define two types that I would like to refactor/consolidate:
...
.then((result1: any) => {
let promises = {
one: $q.when(val1),
two: $q.when(val2)
};
return $q.all(promises);
})
.then((resolvedPromises: any) => {
// use
resolvedPromises.one...
resolvedPromises.two
});
I would like to define type of promises and resolvedPromises but as they are type related I would like to define a single type (likely generic) instead of defining two types with similar definition.
So instead of:
public interface ResultingType {
[id: string]: any,
one: Something1,
two: Something1
}
public interface PromisingType {
[id: string]: ng.IPromise<any>,
one: ng.IPromise<Something1>,
two: ng.IPromise<Something2>
}
The problem is that from the usual generic type perspective we provide the inner-most type to be injected into, but in my case I need to somehow define the outer type either being ng.IPromise<> or nothing actually.
I also don't want to end up with a single type that would allow both types:
public interface ConsolidatedType {
[id: string]: any | ng.IPromise<any>,
one: Something1 | ng.IPromise<Something1>,
two: Something2 | ng.IPromise<Something2>
}
This would allow for mixing and matching in invalid places. So that's a no-go. Is there any other way to actually have a single type without code repetition that only wraps types into promises?
How about:
interface BaseType<T, S> {
[id: string]: T;
one: S;
two: S;
}
And then:
...
.then((result1: any) => {
let promises = {
one: $q.when(val1),
two: $q.when(val2)
} as BaseType<ng.IPromise<any>, ng.IPromise<Something1>>;
return $q.all(promises);
})
.then((resolvedPromises: BaseType<any, Something1>) => {
// use
resolvedPromises.one...
resolvedPromises.two
});
You can also create "shortcuts":
interface PromiseType extends BaseType<ng.IPromise<any>, ng.IPromise<Something1>> {}
interface ValueType extends BaseType<any, Something1> {}
And then:
...
.then((result1: any) => {
let promises = {
one: $q.when(val1),
two: $q.when(val2)
} as PromiseType;
return $q.all(promises);
})
.then((resolvedPromises: ValueType) => {
// use
resolvedPromises.one...
resolvedPromises.two
});

Resources