I'm trying to learn some CycleJS and I ended up not knowing what to do exactly with this. The goal is to create inputs via configuration, instead of declaring them manually. The problem is I'm only getting rendered the last of the inputs from the array, instead of both. I'm assume the error is in view$ and how I'm dealing with the stream.My naive implementation is this:
Main.js
const sliderGunProps$ = xs.of({
value: 30,
id: 'gun'
});
const sliderCannonProps$ = xs.of({
value: 70,
id: 'cannon'
});
const propsConfig = [sliderGunProps$, sliderCannonProps$];
function view$(state$) {
return xs.fromArray(state$)
.map(state => {
return xs.combine(state.sliderVDom$, state.values)
.map(([sliderVDom, value]) =>
div([sliderVDom, h1(value)])
);
})
.flatten();
}
function model(actions$) {
return actions$.map((action) => {
const sliderVDom$ = action.DOM;
const sliderValue$ = action.value;
const values$ = sliderValue$.map(val => val);
return {
sliderVDom$: sliderVDom$,
values: values$
};
});
}
function intent(sources) {
return propsConfig.map(prop$ => Slider({
DOM: sources.DOM,
props$: prop$
}));
}
function main(sources) {
const actions$ = intent(sources);
const state$ = model(actions$);
const vdom$ = view$(state$);
const sink = {
DOM: vdom$
};
return sink;
}
Thanks!
I ended up figuring out how to solve it. The point was that I was not understanding how view$ handle the streams. The proper code:
function total(values) {
return xs.combine(...values)
.map(val => val.reduce((acc, x) => acc + x));
}
function view$(state$) {
const DOMElements = state$.map(slider => slider.sliderVDom$);
const values = state$.map(slider => slider.values);
const totalValue$ = total(values);
return xs.combine(totalValue$, ...DOMElements)
.map(([totalValue, ...elements]) => (
div([
...elements,
h1(totalValue)
])
));
}
function model(actions$) {
return actions$.map((action) => ({
sliderVDom$: action.DOM,
values: action.value.map(val => val)
}));
}
function intent(sources) {
return propsConfig.map(prop$ => Slider({
DOM: sources.DOM,
props$: prop$
}));
}
Related
const getDate = (id) =>{
const userId = info.find(user => user.id === id)
return userId.date;
}
const getValueAtOne = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value[0]){
return userId?.value[0]
}
}
const getValueAtTwo = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value[1]){
return userId?.value[1]
}
}
const getAllValues = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value) {
const arrValue = userId?.roles.map((validData) => {
return { value: validData, label:validData };
});
return arrValue;
}
}
I have these 4 methods, which I am calling from different places in my code.But i want to optimize my code and want all these methods in a single method, but I cant figure out the best way to do it. first method returns me the date, second one returns the value of array at position 1, third method returns value of array at position 2, and 4th method returns the all the value and convert it in object.
You can return a single object like so:
const getAll = (id) => {
const userId = info.find((user) => user.id === id);
const [valueAtOne, valueAtTwo] = userId?.value ?? [];
const allValues =
userId?.value.roles.map((validData) => ({
value: validData,
label: validData,
})) ?? [];
return {
allValues,
valueAtOne,
valueAtTwo,
};
};
I have two properties that might depend on each other so I introduce a bus:
let esDealOne1 = Bacon.later(0, true);
let esDealOne2 = Bacon.later(1000, true);
let bHanging = new Bacon.Bus();
let esSetCards = bHanging.filter(_ => _.cards);
let pHanging = HangingStateProperty(esSetCards);
let pDrawer = DrawerProperty(bHanging, esDealOne1);
let pStacks = [
StackProperty(0, esDealOne2, pHanging),
];
function HangingStateProperty(esSetCards) {
let setCards = (_, cards) => {
return { ..._, ...cards };
};
return Bacon.update({},
[esSetCards, setCards]);
}
function DrawerProperty(bHanging, esDealOne1) {
let dealOne1 = (_) => {
let card = _.usefulProperty;
bHanging.push({ cards: [card] });
return _; };
return Bacon.update({},
[esDealOne1, dealOne1]);
}
function StackProperty(n, esAllDealOne2, pHanging) {
let esDealOne2 = esAllDealOne2.filter(_ => _.i === n);
let dealOne2 = (_, {i}, hangingState) => {
let cards = hangingState.cards;
_.property = cards;
return _;
};
return Bacon.update({},
[esDealOne2, pHanging, dealOne2]);
}
How can I get rid of this bus?
It's hard to remove the bus, instead this would simplify the usage of the Bus:
export function SyncBus() {
let bus = this.bus = new Bacon.Bus();
this.assign = pBase => {
pBase.onValue(_ => bus.push(_));
};
}
Usage:
let bDrawer = new SyncBus();
// don't forget `toProperty` otherwise gets old values
let pDragCardsDrawEarly = bDrawer
.bus
.map(_ => _.extra)
.filter(_ => _.dragcards)
.map(_ => ({ dragcards: _.dragcards }))
.toProperty();
let pDrawer = OwnerProperty({});
bDrawer.assign(pDrawer);
How do you combine two or more observable of array i.e. Observable<Object[]>, Observable<Object[]> using rxjs in order to return one Observable<Object[]>?
forkJoin and merge are emitting the two Observable<Object[]> arrays independently.
getEmployeesLeavesByEmployeeNumber2(employeeNumber,afromDate,atoDate) {
const scenario1 = this.afs.collection(`${environment.FB_LEAVES}`, ref => {
let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
query = query.where("employeeNumber", "==", employeeNumber);
query = query.where("fromDate",">=",afromDate);
query = query.where("fromDate","<=",atoDate);
return query;
}).snapshotChanges()
.pipe(take(1))
.pipe(
map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Leave;
data.docId = a.payload.doc.id;
return data;
})
})
).pipe(map(leaves => {
let leavesArr=leaves.filter(leave => leave.status!==environment.LEAVE_STATUS_DECLINED)
return leavesArr;
}));
const scenario2 = this.afs.collection(`${environment.FB_LEAVES}`, ref => {
let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
query = query.where("employeeNumber", "==", employeeNumber);
query = query.where("toDate","<=",afromDate);
query = query.where("toDate","<=",atoDate);
return query;
}).snapshotChanges()
.pipe(take(1))
.pipe(
map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Leave;
data.docId = a.payload.doc.id;
return data;
})
})
).pipe(map(leaves => {
let leavesArr=leaves.filter(leave => leave.status!==environment.LEAVE_STATUS_DECLINED)
return leavesArr;
}));
const scenario3 = this.afs.collection(`${environment.FB_LEAVES}`, ref => {
let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
query = query.where("employeeNumber", "==", employeeNumber);
query = query.where("fromDate","<=",afromDate);
return query;
}).snapshotChanges()
.pipe(take(1))
.pipe(
map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Leave;
data.docId = a.payload.doc.id;
return data;
})
})
).pipe(
filter(leave => {
return leave!==undefined;
})
);
return merge(scenario1,scenario2);
}
I am expecting a single observable of array but getting 2 i.e.
emp's leaves: [{…}]
assign.component.ts:198 leaves array length at assignment error 1
assign.component.ts:168 emp's leaves: (2) [{…}, {…}]
assign.component.ts:198 leaves array length at assignment error 2
I have got it to work using:
return forkJoin(scenario1,scenario2).pipe(map((arr) => [...arr[0],...arr[1]] ));
How can I refactor these nested arrays so that I can call something once all of the subscriptions have finished? I am sure it has to do with a combination of pipes, mergeMaps, concatMaps, etc.
this.teams = [
{
Assignments: [{Id: 0, Name: 'assignment', Notes: 'notes'}]
},
{
Assignments: [{Id: 0, Name: 'assignment', Notes: 'notes'}]
}]
this.teams.map((team:any) => {
team.Assignments.map((a: Assignment) => {
return this.videoService.getById(a.VideoId).subscribe(
res => {
let e = new Event();
e.Id = a.Id;
e.title = a.Name;
e.location = '';
e.message = a.Notes;
e.startDate = a.StartDate;
e.endDate = a.EndDate;
e.video = res;
e.team = team.Name;
this.eventList.push(e);
},
err => {
});
})
})
With lodash:
Observable.from(
lodash.flatten(
this.teams.map(team => team.Assignments)
)
)
.flatMap(a => this.videoService.getById(a.VideoId))
. subscribe(
res => {
//handle individual responses
},
err => {},
() => {
//handle after all complete
}
)
You can't listen on subscriptions, however, you could return an observable for each assignment an do a forkJoin of them, something like:
this.teams.map((team:any) => {
forkJoin(...team.Assignments.map((a: Assignment) => {
return this.videoService.getById(a.VideoId).map(
res => {
const e = new Event();
e.Id = a.Id;
e.title = a.Name;
e.location = '';
e.message = a.Notes;
e.startDate = a.StartDate;
e.endDate = a.EndDate;
e.video = res;
e.team = team.Name;
this.eventList.push(e);
});
})).subscribe(data => {
// Do something;
})
})
Now, I would refactor a little that code in order to make it more readable, something like:
function mapToEvent(team, assignment, response) {
const e = new Event();
e.Id = assignment.Id;
e.title = assignment.Name;
e.location = '';
e.message = assignment.Notes;
e.startDate = assignment.StartDate;
e.endDate = assignment.EndDate;
e.video = response;
e.team = team.Name;
return e;
}
this.teams.map(team => {
forkJoin(
...team.Assignments.map(a =>
this.videoService
.getById(a.VideoId)
.map(res => mapToEvent(team, a, res))
.do(event => this.events.push(event))
)
).subscribe(data => {
// Do something;
});
});
p.s. Some alternative syntax I was thinking on is:
function mapToEvent(team, assignment, response) {
const obj = {
Id: assignment.Id,
title: assignment.Name,
location: '',
message: assignment.Notes,
startDate: assignment.StartDate,
endDate: assignment.EndDate,
video: response,
team: team.Name
};
return Object.assign(new Event(), obj);
}
However, I'm not sure how it looks, although this may cause some underlying issues with V8, due to hidden classes.
Based on the other answer
I'm not much a fan of lodash, so I just wanted to present a vanilla js alternative:
Observable.from(
this.teams
.map(team => team.Assignments)
.reduce((acc, a) => [...acc, ...a], [])
)
.flatMap(a => this.videoService.getById(a.VideoId))
.catch(err => {
// Do Something
})
.finally(() => {
// Do something
})
.subscribe(res => {
// Handle Single
});
I'm trying to obtain a list of all the keys defined on a normalizr schema, & have written a function that does what I need for a simple schema:
export const collectAttributes = target => {
const schemaKeys = []
if (target.hasOwnProperty('_key')) {
schemaKeys.push(target._key)
}
const definitions = Object.keys(target).filter(key => key[0] !== '_')
definitions.forEach(key => {
collectAttributes(target[key]).forEach(attribute => schemaKeys.push(attribute))
})
return schemaKeys
}
However, this fails on a nested schema definition with a Maximum call stack size exceeded error, as illustrated with this test case:
describe('collectAttributes', () => {
it('should collect all unique collections defined on a recursive schema', () => {
const nodeSchema = new schema.Entity('nodes', {})
const nodeListSchema = new schema.Array(nodeSchema)
nodeSchema.define({ children: nodeListSchema })
expect(collectAttributes(nodeSchema)).toEqual(['nodes'])
})
})
If anyone has ideas on how to collect the already visited schemas such that the recursive function halts, they would be much appreciated.
I figured it out in the end - solution below:
export const isSchema = target => {
if (Array.isArray(target)) {
return target.length ? isSchema(target[0]) : false
} else {
return target.hasOwnProperty('schema') || target instanceof schema.Entity || target instanceof schema.Array
}
}
const recursiveCollect = (target, visited = []) => {
const entities = []
const visitedSchemas = [...visited]
if (isSchema(target)) {
entities.push(target.key)
visitedSchemas.push(target)
}
if (Array.isArray(target) || target instanceof schema.Array) {
/*
* If the current target is an ArraySchema, call `recursiveCollect`
* on the underlying entity schema
*/
return recursiveCollect(target.schema, visitedSchemas)
}
Object.keys(target.schema).filter(x => x[0] !== '_').forEach(definition => {
const childSchema= target.schema[definition]
const alreadyVisited = visitedSchemas.includes(childSchema)
if (isSchema(childSchema) && !alreadyVisited) {
/* Only call `recursiveCollect` on the child schema if it hasn't
* already been encountered
*/
const result = recursiveCollect(childSchema, visitedSchemas)
if (result.entities) {
result.entities.forEach(x => entities.push(x))
}
if (result.visitedSchemas) {
result.visitedSchemas.forEach(x => visitedSchemas.push(x))
}
}
})
return { entities, visitedSchemas }
}
export const collectAttributes = target => {
const { entities } = recursiveCollect(target)
return entities
}