UI not update (have to click somewhere) & router not work in Angular2-Meteor - meteor

I cannot update the UI immediately after subscribing the data from database, I have to click somewhere
Also if I use the router to go to another page, it does not work
#Component({
selector: 'foo-component',
template: `
{{foo}}
`
})
export class FooComponent extends MeteorComponent {
foo: string ;
constructor() {
super();
this.subscribe('messages', () => {
// I cannot change foo value immediately, I have to click somewhere
this.foo = 'foo';
// Also if I use the router to go to another page, it does not work
// this.router.navigate(['Home']);
});
}
}
How to solve this?

Note the new angular2-meteor version autoBind is set to true by default. So you probably won't meet this issue again.
But you still need use NgZone in Accounts.changePassword or other similar Accounts.foo() functions.
This problem is because that part of code run out of Angular 2 zone, you need run it inside of zone to update UI immediately or use router to go to another page.
Where do these problems usually happen?
Most time you don't do this. So when do you need this? Usually in callback of Meteor functions:
this.autorun(() => {
// here you need
});
this.subscribe('messages', () => {
// here you need
});
this.call('newMessage', (error, result) => {
// here you need
});
Accounts.changePassword(currentPassword, newPassword, error => {
// here you need
});
How to solve?
Take
this.call('newMessage', (error, result) => {
this.router.navigate(['Home']);
});
for example, you need change to:
import { NgZone } from '#angular/core';
constructor(private zone: NgZone) {}
this.call('newMessage', (error, result) => {
this.zone.run(() => {
this.router.navigate(['Home']);
});
});
Is there a clean way?
Luckily, Angular2-Meteor helps you do this dirty work.
Check Angular2-Meteor API document, there is an autoBind parameter for this.subscribe and this.autorun.
So now you don't need use this.zone.run, instead you can just add a true:
this.autorun(() => {
// your code
}, true);
this.subscribe('messages', () => {
// your code
}, true);

Related

Vue3 how to use a dynamic path with defineAsyncComponnet

I am trying to add retry functionality to defineAsyncComponent so I created a helper function
const MAX_TRY = 3;
const WAIT_TIME = 1000;
async function loadAsyncComponent(componentPath: string, tryCount = 1) {
if (tryCount > MAX_TRY) return Promise.reject();
return new Promise((resolve, reject) => {
const path = componentPath.replace('.vue', '').replace('#/modules/', '');
// https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
import(`../../../modules/${path}.vue`).then(resolve).catch((error) => {
console.error('loading async component failed : ', error);
captureException(error);
wait(WAIT_TIME).then(() => {
loadAsyncComponent(componentPath, ++tryCount)
.then(resolve)
.catch(reject);
});
});
});
}
export default function customAsyncComponent<T>(componentPath: string): T {
return defineAsyncComponent({
// the loader function
loader: () => loadAsyncComponent(componentPath) as any,
// A component to use while the async component is loading
loadingComponent: Loading,
// Delay before showing the loading component. Default: 200ms.
delay: 200,
// A component to use if the load fails
errorComponent: AsyncLoadFailed,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 10000,
});
}
This works in local DEV environment but when I build and deployed to prod it broke Error: Unknown variable dynamic import: ../../../modules/room/components/room/Room.vue
It seems that these dynamic imports are not included in the final build.
Found this answer https://stackoverflow.com/a/72479802/1055015
Tryied these
rollupOptions: {
external: [path.resolve(__dirname, 'src/modules/room/components/room/Room.vue')],
},
rollupOptions: {
external: ['#/modules/room/components/room/Room.vue')],
},
After vite build The dynamically imported files are not generated in the dist folder
Any ideas ?
UPDATE: I found a workaround using glob-import testing it now
Solution 2:
The first solution left me with hundred's of small files , every single vue,ts,css file loading separately (~175),
I needed to preload them for better UX but and it started making 429 errors (too many request) on my server
So I made some changes on my custom loader and manually listed the files that needed to be preloaded
const PRELOAD_LIST = [
() => import('#/modules/X.vue'),
];
async function loadAsyncComponent(componentLoader: any, tryCount = 1) {
return new Promise((resolve, reject) => {
componentLoader() // <--------------- this part mostly
.then(resolve)
.catch((error) => {
console.error('loading async component failed : ', error);
captureException(error);
if (tryCount >= MAX_TRY) reject(error);
else {
wait(WAIT_TIME).then(() => {
loadAsyncComponent(componentLoader, ++tryCount)
.then(resolve)
.catch(reject);
});
}
});
});
}
const X = customAsyncComponent(() => import('#/modules/X.vue'));
Solution 1:
For now I used this approach :
const MODULES = import.meta.glob('../../../modules/**/*.vue');
this generates a key/value object list, the keys are the file path's and the values are import statements with static path
// code produced by vite
const MODULES = {
'../../../modules/dir/foo.vue': () => import('./modules/dir/foo.vue'),
'../../../modules/dir/bar.vue': () => import('./modules/dir/bar.vue')
}
Then you can just do MODULES[myPath]() to load the file/module.
This works but it also generates extra code for the modules at I don't really need to, so I am still looking for a better solution.

How to test NgRx Effect that uses event from dependency injected service

I have following effect that uses the an observable source from a PlatformService provided by Angulars Dependency Injection.
public resume$ = createEffect(() => {
return this.platformService.resume().pipe(mapTo(PlatformActions.appResumed()));
});
constructor(private platformService: PlatformService) {
}
This works really well, but I do not really know how to test it elegantly. Usually we try to setup our TestingModule as unspecific as possible. e.G
describe('PlatformEffects', () => {
let effects: PlatformEffects;
let platformServiceSpy: SpyObj<PlatformService>;
beforeEach(() => {
platformServiceSpy = jasmine.createSpyObj({
resume: EMPTY
});
TestBed.configureTestingModule({
providers: [
PlatformEffects,
{
provide: PlatformService,
useValue: platformServiceSpy
}
]
});
effects = TestBed.inject(PlatformEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('resume$', () => {
it('dispatches appResumed action on resume event', (done) => {
// AAA: This test should setup the response from the platformService so the arrange setup is coupled in the test.
});
});
});
Sadly this does not seem to work as the effects are created in the constructor and therefore my event stream is always EMPTY and does not trigger the effect even if I overwrite the resume response in my spy.
Now I could set a default value in the beforeEach (e.G. of(undefined)) but then it is not really coupled in my tests and I cant use the AAA pattern.
On the other hand I could probably create a new Effects instance every time but that seems a bit overkill not?
Is there a better method? NgRx solved it really well with the actions stream and I wonder if there is a similar solution for effects using sources from DI.
For more info see, https://timdeschryver.dev/blog/testing-an-ngrx-project#effects
it('fetch$ dispatches a success action', () => {
// 🔦 The Effect Actions stream is created by instantiating a new `ActionsSubject`
const actions = new ActionsSubject();
const effects = new CustomersEffects(actions, newCustomerService());
// 🔦 Subscribe on the effect to catch emitted actions, which are used to assert the effect output
const result: Action[] = [];
effects.fetch$.subscribe((action) => {
result.push(action);
});
const action = customerPageActions.enter({ customerId: '3' });
actions.next(action);
expect(result).toEqual([
customersApiActions.fetchCustomerSuccess(
newCustomer({
id: action.customerId,
}),
),
]);
});
I tried a bit more and thought that the actions stream is probably just a subject and I could do the same for my own service method:
describe('PlatformEffects', () => {
let effects: PlatformEffects;
let platformServiceSpy: SpyObj<PlatformService>;
let resumeSubject: ReplaySubject<any>;
beforeEach(() => {
// create new subject that can be used as an event emitter
resumeSubject = new ReplaySubject<any>();
platformServiceSpy = jasmine.createSpyObj({
resume: resumeSubject.asObservable()
});
TestBed.configureTestingModule({
providers: [
PlatformEffects,
{
provide: PlatformService,
useValue: platformServiceSpy
}
]
});
effects = TestBed.inject(PlatformEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('resume$', () => {
it('dispatches appResumed action on resume event', (done) => {
// emit new test string in resume subject
resumeSubject.next('test');
effects.resume$.subscribe((res) => {
expect(res).toEqual(PlatformActions.appResumed());
done();
});
});
});
});
This works for this case but as soon as I try to do the same with a service method that returns a promise this solution does not work anymore.

Async side effect depends on another effect

I have two side effects. One of them presents a global loading spinner and the other one dismisses the spinner again. For presenting and dismissing the loader I have created a LoadingService. Note that the presentLoader() and dismissLoader() methods are async. Reason is that the service encapsulates the LoadingController of the Ionic framework.
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoadingService {
private loader?: HTMLIonLoadingElement;
constructor(private loadingController: LoadingController) {
}
public async presentLoader(translationKey: string): Promise<void> {
if (this.loader) {
this.loader.message = translationKey;
return;
}
this.loader = await this.loadingController.create({
message: translationKey
});
await this.loader.present();
}
public async dismissLoader(): Promise<void> {
await this.loader?.dismiss();
this.loader = undefined;
}
}
In my Application i work with the RequestStarted, RequestSucceeded and RequestFailed Action naming pattern.
public updateMyThingsItem$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemNameRequested,
MyThingsItemActions.updateMyThingsItemPurchasePriceRequested,
MyThingsItemActions.updateMyThingsItemPurchaseDateRequested
),
exhaustMap((action) =>
this.myThingsIntegrationService.patchMyThingsItem(action.myThingsItem.id, action.myThingsItem.changes).pipe(
map((myThingsItem) => MyThingsItemActions.updateMyThingsItemSucceeded({ myThingsItem })),
catchError((error: string) => of(MyThingsItemActions.updateMyThingsItemFailed({ error })))
)
)
);
});
Now I created a side effect to display a loader for a couple of RequestStarted actions and a side effect that hides the loader again for the related Succeeded and Failed actions. This looks something like this:
public presentLoadingSpinner$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemNameRequested,
MyThingsItemActions.updateMyThingsItemPurchasePriceRequested,
MyThingsItemActions.updateMyThingsItemPurchaseDateRequested
),
exhaustMap(() => this.loadingService.presentLoader('loading...'))
);
}, {
dispatch: false
});
public hideLoadingSpinner$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemSucceeded,
MyThingsItemActions.updateMyThingsItemFailed
),
exhaustMap(() => this.loadingService.dismissLoader())
);
}, {
dispatch: false
});
Now for my problem: Because the methods in my loadingService are async there is the possibility that the side effect to dismiss the loader is triggered before the presentLoader() method is completed and the loader does not yet exist in the DOM. This happens when the API is really quick or a client error (no network) happens and the Failure Action is dispatched before the loader is created.
I therefore realise that I now have depending effects. But how else could I solve the problem with a side effect that triggers async code?
I know that I could create a loading state with a isLoading flag. But I would have the same problem when creating the effect from a selector.
An other possibility would be to create an overlaying Loader Component in the AppComponent that is rendered on the isLoading flag. But then I would have to give up the LoadingController of the Ionic framework which I do not really like.
I would like to know how to solve these problems in general when calling a side effect method that is async.

Change layout according to wordpress page template NuxtJs

I want to get change layout according to wordpress page template (i get data with rest api)
API Data
{
"id": 47,
"template": "test.php", // need vue template according this
}
export default {
validate ({ params }) {
return !isNaN(+params.id)
},
async asyncData ({ params, error }) {
return fetch('http://wordpress.local/wp-json/wp/v2/'+params.postType+'/'+params.id)
.then(response => response.json())
.then(res => {
return { users: res }
})
},
layout () {
return 'blog' // Change here according to wordpress page template
},
}
I found a way to pass something from middleware to store which you can use inside the layout function. Here is the basic example I put together.
middleware/get-layout.js I simulate an async call here, could also be result of axios.post() for example
export default async (ctx) => {
return new Promise((resolve, reject) => {
// you can also access ctx.params here
ctx.store.commit('setLayout', 'new');
resolve();
});
}
store/index.js nothing crazy here
export const state = () => ({
layout: ''
})
export const mutations = {
setLayout(state, layout) {
state.layout = layout;
}
}
Middleware can either be registered globally for every route in nuxt.config.js or only for pages where you need this logic.
Finally using it in page component layout property:
layout(ctx) {
return ctx.store.state.layout;
}
I tested it with new.vue inside layout folder.

React redux separation of concerns

I'm trying to build a simple app to view photos posted from nasa's picture of the day service (https://api.nasa.gov/api.html#apod). Currently watching for keypresses, and then changing the date (and asynchronously the picture) based on the keypress being an arrow left, up, right, or down. These would correspondingly change the date represented by a week or a day (imagine moving across a calendar one square at a time).
What I'm having trouble with is this: I've created an async action creator to fetch the next potential date - however I need to know the current state of the application and the keypress to retrieve the new date. Is there a way to encapsulate this into the action creator? Or should I put the application state where the exported action creator is called in the application so I can keep my action creator unaware of the state of the application? I've tried to do this by binding the keydown function in componentDidMount for the top level Component, but the binding to the application store doesn't seem to reflect the changes that happen in the reducer.
The async logic relying on redux-thunk middleware and q:
// This function needs to know the current state of the application
// I don't seem to be able to pass in a valid representation of the current state
function goGetAPIUrl(date) {
...
}
function getAsync(date) {
return function (dispatch) {
return goGetAPIUrl(date).then(
val => dispatch(gotURL(val)),
error => dispatch(apologize(error))
);
};
}
export default function eventuallyGetAsync(event, date) {
if(event.which == 37...) {
return getAsync(date);
} else {
return {
type: "NOACTION"
}
}
}
Here's the top level binding to the gridAppState, and other stuff that happens at top level that may be relevant that I don't quite understand.
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
actions.eventuallyGetAsync(event, gridAppState.date);
});
}
render() {
const { gridAppState, actions } = this.props;
return (
<GridApp gridAppState={gridAppState} actions={actions} />
);
}
}
App.propTypes = {
actions: PropTypes.object.isRequired,
gridAppState: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
gridAppState: state.gridAppState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(GridActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
I've validated that the correctly modified date object is getting to the reducer - however the gridAppState seems stuck at my initial date that is loaded.
What is the right way to approach async logic in redux that relies on attaching event handlers and current application state? Is there a right way to do all three?
You should handle the event in your component and call the correct action depending on the key pressed.
So when you dispatch an async action you can do something like
export default function getNextPhoto(currentDate) {
return (dispatch) => {
const newDate = calculateNewDate(currentDate);
dispatch(requestNewPhoto(newDate));
return photosService.getPhotoOfDate(newDate)
.then((response) => {
dispatch(newPhotoReceived(response.photoURL);
});
};
}
You should handle the keypress event on the component and just dispatch your action when you know you need to fetch a new photo.
Your App would look like
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
if (event.which == 37) {
actions.getNextPhoto(gridAppState.date);
} else if (...) {
actions.getPrevPhoto(gridAppState.date);
}
// etc
});
}
}
By the way you re still missing your reducers that update your state in the Redux Store.

Resources