Update rxjs from angular material table responsive directive - rxjs

In this stackblitz demo there's an Angular 12 project that lets you create responsive
material tables with expandable rows. you use the "matTableResponsive" directive in the table html tag.
The rxjs version of that project is 6.5.3.
I'm trying to use this code in my project which uses rxjs version 7.5.5 and Angular 14.
I imported everything, tried to use the directive but its simply not working.
I checked the code in "mat-table-responsive.directive.ts" which contains this function:
ngAfterViewInit() {
combineLatest([this.theadChanged$, this.tbodyChanged$])
.pipe(
mapTo({ headRow: this.thead.rows.item(0)!, bodyRows: this.tbody.rows }),
map(({ headRow, bodyRows }) => ({
columnNames: [...headRow.children].map(
headerCell => headerCell.textContent!
),
rows: [...bodyRows].map(row => [...row.children])
})),
takeUntil(this.onDestroy$)
)
.subscribe(({ columnNames, rows }) =>
rows.forEach(rowCells =>
rowCells.forEach(cell =>
this.renderer.setAttribute(
cell,
'data-column-name',
columnNames[(cell as HTMLTableCellElement).cellIndex]
)
)
)
);
}
This function uses mapTo, which works on rxjs 6.5.3 but its deprecated in version 7.5.5:
I may be wrong but i believe that's the reason why the directive is simply not working on my project.
Can someone help me update this function to work in the latest rxjs version? I know i have to replace mapTo with map as the image recommends, but i dont exactly know how to.

mapTo(true) becomes map(() => true)
mapTo({ headRow: this.thead.rows.item(0)!, bodyRows: this.tbody.rows }), becomes
map(() => ({ headRow: this.thead.rows.item(0)!, bodyRows: this.tbody.rows })),

Related

Cypress multiple arrow function parameters?

So I'm just starting to learn Typescript with Cypress and I noticed this in a page I was reading about here: https://medium.com/#NicholasBoll/cypress-io-scaling-e2e-testing-with-custom-commands-6b72b902aab
export const updateTodo = (name: string) => ($todo: JQuery) => {
cy.wrap($todo).within(() => {
cy.get('label').dblclick()
cy.get('.edit').clear().type(`${name}{enter}`)
})
So I'm still new to typescript and I understand I think that the Jquery element is the subject parameter although I'm still a little bit unsure at what would represent a "Jquery" element/parameter in Cypress.
However what confused me is the fact that there are two fat arrow parameter sections... I've not seen that before? What exactly does that mean? Especially when further down it's called as such (With just a string):
it('should update a todo', () => {
createTodo('Learn Cypress Command API')
.then(updateTodo('Learn Cypress composition'))
.then(getTodoName)
.should('equal', 'Learn Cypress composition')
})
Can anyone explain what's going on here?
Two fat arrows just means the function is returning another function.
So
export const updateTodo = (name: string) => ($todo: JQuery) => {
cy.wrap($todo).within(() => {
cy.get('label').dblclick()
cy.get('.edit').clear().type(`${name}{enter}`)
})
is equivalent to
export const updateTodo = (name: string) => { // outer function
return ($todo: JQuery) => { // inner function
cy.wrap($todo).within(() => {
cy.get('label').dblclick()
cy.get('.edit').clear().type(`${name}{enter}`)
})
Where you use the double-fat-arrow function is another shorthand,
This
.then(updateTodo('Learn Cypress composition'))
is shorthand for this
.then((name: string) => updateTodo(name)('Learn Cypress composition'))
where the double brackets updateTodo()() calls the outer function then the inner function.
Or even longer syntax:
.then((name: string) => {
const innerFn = updateTodo(name);
return innerFn('Learn Cypress composition');
})

Converting old RxJS code to v6 : merge, filter, timer, map, first, toPromise()

Im trying to update this code to version 6 but I cannot figure out how rework the flow, from reading and playing around I think I need to pipe results etc but I cant see how it can be done using the flow of merge, filter, timer, map, first, toPromise() that used to work. Any RxJS folks able to educate me or point me in the right direction ?
const chats: chats[] = <chats[]>await Observable.merge(
this.chatService.$chats.filter(chats => chats.length > 0),
Observable.timer(5000).map(x => {
throw 'Timeout'
})
).first().toPromise()
if(chats.find( chat => chat.id === chatId )) {
this.log.log(`Found active chat ${chatId}, navigating to it from push notification...`)
}
Try:
import { merge, timer } from 'rxjs';
import { filter, first, map, } from 'rxjs/operators';
const chats: chats[] = <chats[]> await merge(
this.chatService.$chat.pipe(
filter(chats => chats.length > 0),
),
timer(5000).pipe(
map(x => {
throw 'Timeout';
}),
),
).pipe(
first(),
).toPromise();
if(chats.find( chat => chat.id === chatId )) {
this.log.log(`Found active chat ${chatId}, navigating to it from push notification...`)
}
You have to pipe the operators instead of chaining them with ..

Possible to determine when epics finish in Redux Observable?

I'm new to RxJS so sorry if it doesn't make much sense.
Let's say I want to have a reusable epic for fetching a user that will be invoked by action from an app load epic.
Oversimplified example:
const getUserEpic = action$ =>
action$.pipe(
ofType(GET_USER_REQUEST),
switchMap(action => from(service.fetchUser(action.userId).pipe(
mapTo({ type: GET_USER_SUCCESS })))
),
);
const appLoadEpic = action$ =>
action$.pipe(
ofType(LOAD_APP_REQUEST),
map(() => of({ type: GET_USER_REQUEST }, { type: SOME_OTHER_REQUEST }))
);
What if I wanted to call LOAD_APP_SUCCESS after all of the invoked epics (getUser etc.) finish? It'd be great if it could be done in the appLoadEpic but I'm afraid it's not possible.
The way I would suggest doing it is combining the the individual epics into a "meta"-epic. That is, you can use the individual streams to listen to their individual events and the propagate them when all the merged streams are completed.
const getUserEpic = action$ => ...
const someOtherEpic = action$ => ...
// Creates an epic that merges all the results from each provided epic
const initializationEpic = combineEpics(getUserEpic, someOtherEpic)
const appLoadEpic = (action$, state$) => {
// Runs the new epic with the action and state values.
const onLoad$ = initializationEpic(action$, state$).pipe(
endWith({type: LOAD_APP_SUCCESS})
)
// Listen for the load app request before subscribing to the initialization
action$.pipe(
ofType(LOAD_APP_REQUEST),
mergeMapTo(onLoad$),
)
}
If you are feeling fancy and don't want to have to inject the epics via import, you can also dynamically inject epics The docs detail a way to inject the epic asynchronously, meaning that instead of file injection you could include it as part of the action body during start up, this might make testing a bit easier.
const appLoadEpic = (action$, state$) => {
// Listen for the load app request before subscribing to the initialization
action$.pipe(
ofType(LOAD_APP_REQUEST),
// Now the epic is injected during the app loading, and you run it inline
// here. This makes it easy to mock it during testing
mergeMap(({epic}) => epic(action$, state$).pipe(endWith({type: LOAD_APP_SUCCESS}))),
)
}

How can I call an array of actions in RxJS?

I have a number of reset actions I need to fire at once. Currently I'm using a mergeMap to call the action types as below. However, I think there is a way to replace the type objects with an array of strings but I can't seem to figure it out. Can anyone help?
const resetModuleEpic = action$ =>
action$.pipe(
ofType('RESET_MODULE'),
mergeMap(() =>
of(
{
type: 'RESET_IMAGE'
},
{
type: 'RESET_CATEGORY'
},
{
type: 'RESET_FILTERS'
}
)
)
);
I believe something like below is possible but can't get it right:
const resetModuleEpic = action$ =>
action$.pipe(
ofType('RESET_MODULE'),
merge(['RESET_IMAGE','RESET_CATEGORY','RESET_FILTERS'])
);
Your example should work, probably error is somewhere else in you code.
I have added example at stackblitz. I personally prefer from over of, but cannot reason it.
What you have with merge will just take each item in the array and re-emit it so it'll output just pure strings instead of actions.
There are obviously multiple ways you can achieve what you want. For example, you can use combination of of().map() and turn each string into action:
mergeMap(() => of('RESET_IMAGE','RESET_CATEGORY','RESET_FILTERS').pipe(
map(type => ({ type })),
))

Is there a better way to form this code example?

I'm new to rxjs and using redux-observable. The short of it is that I need to make a couple promise requests when i get a connection then output the results. I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read. Thanks ahead of time for your comments.
export const handleBleConnectionSuccess = (action$,store,{bleCommunicator}) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS)
.do((a)=>{
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.firmwareRevision.uuid))
.do((value)=>store.dispatch({type:c.DEVICE_FIRMWARE_VERSION,device:{...a.device,firmwareVersion:value}}))
.retry(3);
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.modelNumber.uuid))
.do(value=>store.dispatch({type:c.DEVICE_MODEL_NUMBER,device:{...a.device,modelNumber:value}}))
.retry(3);
})
.mapTo({type:'DEVICE_INFORMATION_REQUESTED'});
I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read
Yes, there is a better way, and it's possible to do what you want.
From the syntax, I'm guessing that you use ngrx (effects) (and not redux-observable).
So with ngrx/effects you could do it like that:
export const handleBleConnectionSuccess = (
action$,
store,
{ bleCommunicator }
) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS).switchMap(a => {
const readCharacteristic = deviceOrFirmwareUuid =>
bleCommunicator.readCharacteristic(a.device.id, gattInfo.uuid, deviceOrFirmwareUuid);
return Observable.merge(
readCharacteristic(gattInfo.firmwareRevision.uuid)
.map(value => ({
type: c.DEVICE_FIRMWARE_VERSION,
device: { ...a.device, firmwareVersion: value },
}))
.retry(3),
readCharacteristic(gattInfo.modelNumber.uuid)
.map(value => ({
type: c.DEVICE_MODEL_NUMBER,
device: { ...a.device, modelNumber: value },
}))
.retry(3),
{ type: 'DEVICE_INFORMATION_REQUESTED' }
);
});

Resources