I am using fullcalendar (free version (6)) in my angular (15) project. Once a user clicks on a date in the calendar, I need to to show some information about this day. Thus, I call a function which retrieves this info from the database, and shows it in a modal to the user (for now I just alert it).
But calling my function I get the error:
Property 'retrieveDataBase' does not exist on type 'CalendarOptions'
So I want to know if there is any way to integrate my function into fullcalendar?
P.S. my data is huge and I can't show it as an event in different days!
Here is my code.
export class someComponent {
calendarOptions: CalendarOptions = {
plugins: [interactionPlugin],
dateClick: function(info) {
this.retrieveDataBase(info.dateStr); // Gives error!
},
}
retrieveDataBase(date: string):void{
this.dummyService.getdb(date).subscribe(
(response: any) => {
const { results } = response;
alert(results);
console.log(response);
},
(error: any) => {
console.log(error);
}
);
}
}
Try replacing that callback with an arrow function (this could cause it to resolve this based on lexical scoping, which would then refer to the class):
export class someComponent {
calendarOptions: CalendarOptions = {
plugins: [interactionPlugin],
dateClick: (info) => {
this.retrieveDataBase(info.dateStr);
},
}
retrieveDataBase(date: string):void{
this.dummyService.getdb(date).subscribe(
(response: any) => {
const { results } = response;
alert(results);
console.log(response);
},
(error: any) => {
console.log(error);
}
);
}
}
Related
I update multiple records using the following update method and receive the updated and failed record count from result. Then I want to display related toastr message sequentially using Angular Material toastr. However, the following approach skip the success in #1 (or it is displayed back of the error) and display the error in #2. So, how can I display them sequentially for this method? Maybe I need to use RxJs for this purpose.
update() {
this.demoService.update(...).toPromise()
.then(result => {
if(result.success.count > 0){
// #1 display success toastr
}
if(result.failed.count > 0) {
// #2 display error toastr
}
}).catch(err => {
// #3 display error toastr related to other errors
});
}
definitelly you need to use Rxjs Observables instead of Promises if you work with Angular :d
So, your code will become:
constructor(private alertService: AlertService) {}
update() {
this.demoService.update(...).subscribe(result => {
if(result.success.count > 0) {
result.success.forEach(item => {
this.alertService.info(item);
await this.delay(2000);
}
}
if(result.failed.count > 0) {
result.failed.forEach(item => {
this.alertService.error(item);
await this.delay(2000);
}
}
}, err => {
this.alertService.error(err.message);
});
}
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
For each message, to not be override by the next, you need to await a time as the popup to be hidden. For this delay() function is used.
and the service class as an alert provider (and inject into your component):
import { Injectable } from '#angular/core';
import { ToastrService } from 'ngx-toastr';
#Injectable({
providedIn: 'root',
})
export class AlertService {
constructor(private toastr: ToastrService) {}
public success(message: string): void {
this.toastr.success(message, '', {
timeOut: 2000
});
}
public error(message: string): void {
this.toastr.error(message, '', {
timeOut: 2000
});
}
}
I am new to javascript/node and I am building a class that calls the Telegram api every second to get updates and store so I can have other function use that data. I have pieced together code form examples but I am getting an error when I call bot.start(); because no function is being passed to getUpdates. I am not sure why the (fn) is needed.
class Bot {
constructor(token) {
let _baseApiURL = `https://api.telegram.org`;
this.baseApiURL = _baseApiURL;
this.token = token;
}
start(){
this.getUpdates();
}
getBaseApiUrl(){
return this.baseApiURL;
}
getToken(){
return this.token;
}
getAPI(apiName) {
return axios.get(`${this.getApiURL()}/${apiName}`);
}
getApiURL() {
return `https://api.telegram.org/bot${this.getToken()}`;
}
getUpdates(fn) {
this.getAPI('getUpdates')
.then(res => {
this.storeUpdates(res.data);
fn(res.data);
setTimeout(() => {
this.getUpdates(fn);
}, 1000);
})
.catch(err => {
console.log('::: ERROR :::', err);
});
}
storeUpdates(data){
console.log(data);
}
}
const bot = new Bot(TOKEN);
bot.start();
Its not clear what exactly you are trying to achieve with that fn method. You are not passing any method, therefore it fails. This would work
getUpdates() {
this.getAPI('getUpdates')
.then(res => {
this.storeUpdates(res.data);
setTimeout(() => {
this.getUpdates();
}, 1000);
})
.catch(err => {
console.log('::: ERROR :::', err);
});
}
If you want to implement some kind of Observer pattern, you should not couple it with getUpdates method, just create methods for registering observers and notify them when store is changed.
Also the way how you trigger the repetition is not too good, because once you get error (and with HTTP methods you usually get some ERROR sooner or later) it will break the whole flow.
Use something like https://www.npmjs.com/package/cron to trigger periodic actions.
Struggling a bit to set up error handling with vuex. There seems to be quite a few ways to do so and little documentation on proper error handling. I've been experimenting with four alternatives, though I haven't found a satisfying solution yet.
Alternative 1 - Catching and processing errors on component
in pages/login.vue:
export default {
methods: {
onLogin() {
this.$store.dispatch('auth/login', {
email: this.email,
password: this.password,
}).then(() => {
this.$router.push('/home');
}).catch((error) {
// handle error in component
});
},
},
}
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
});
},
}
PROS
Hmm.
CONS
Errors not handled and stored in vuex.
Introduces lots of boilerplate code in component methods.
Alternative 2 - Catching and processing errors in vuex
in pages/login.vue:
export default {
methods: {
onLogin() {
this.$store.dispatch('auth/login', {
email: this.email,
password: this.password,
}).then(() => {
this.$router.push('/home');
});
},
},
}
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
}).catch((error) => {
// store error in application state
commit('SET_ERROR', error);
});
},
}
PROS
Error object is accessible with vuex from any component
Could use a reactive error component in layout, which is revealed when the error state changes.
CONS
I'm not sure if there is a way to track the source of the error, from which component it was thrown.
Alternative 3 - Catching errors using axios interceptors
in plugins/axios.js:
export default function({ $axios, store }) {
$axios.onError(error => {
store.dispatch('setError', error);
});
}
in pages/login.vue:
export default {
methods: {
onLogin() {
this.$store.dispatch('auth/login', {
email: this.email,
password: this.password,
}).then(() => {
this.$router.push('/home');
});
},
},
}
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
});
},
}
PROS
Global error handling
No need to catch errors in either vuex or component
No boiler-plate code
CONS
All exceptions are unhandled, meaning non-axios errors are uncaught.
Alternative 4 - Custom error plugin
I've been experimenting in implementing a custom plugin that catches all exceptions, but I'm not succeeding in making it work.
in plugins/catch.js:
export default (ctx, inject) => {
const catchPlugin = function (func) {
return async function (args) {
try {
await func(args)
} catch (e) {
return console.error(e)
}
}
};
ctx.$catch = catchPlugin;
inject('catch', catchPlugin);
}
in pages/login.vue:
export default {
methods: {
onLogin: this.$catch(async function () {
await this.$store.dispatch('auth/login', { email: this.email, password: this.password });
this.$router.push('/home');
}),
},
}
PROS
No boilerplate.
All errors caught in plugin.
CONS
I cannot make it work. :(
My impression is that there is a lack of documentation on error handling in vue/nuxt. Could anyone get the fourth alternative to work? Would this be ideal? Any other alternatives? What is conventional?
Thank you for your time!
The reason why option #4 is not working is because you're returning a function that never gets executed:
function catchPlugin(outerFunction) {
return function async innerFunction(args) {
try {
const data = await outerFunction(args);
return { data }
} catch (error) {
return { error }
}
}
}
Usage:
const execute = catchPlugin((args) => {
// do something
})
execute('myArgument');
As you can see you need to execute the inner function as well, to make your example work:
onLogin: this.$catch(async function () {
await this.$store.dispatch('auth/login', { email: this.email, password: this.password });
this.$router.push('/home');
})(), // mind the () :-)
But... I believe handling errors in components is not a bad thing, since this is tightly coupled to your view component. For instance, think about a login component, what we see these days is a global error handler (toastr) which will display a toast message if the username/password is incorrect. From my experience this is not the best behavior, it's a good starting point but better would be to add error messages close to the component displaying what exactly went wrong. Meaning you will always have to add error handling (UI related) in the component itself.
We're also struggling with this in our company with colleagues working on the same product. One is adding error handling, the other one is not.. The only solution, in my opinion, is to educate developers to always add proper error handling. The syntax with async/await is not that bad:
methods: {
async login (email, password) {
try {
await this.$store.dispatch('auth/login', { email, password })
// do something after login
} catch (error) {
// handle error
}
}
}
One last thing about your con: Errors not handled and stored in vuex.. Why is this a con? Do you need to have the error globally available? What I see a lot is people putting so much useless state in vuex that's only used in the component itself. It's not bad to have local component state. Since it's about login, this error should only be known in the login component.
Use Promise in action
Example in vuex:
NEW_AUTH({ commit }) {
return new Promise((resolve, reject) => {
this.$axios.$get('/token').then((res) => {
...
resolve();
}).catch((error) => {
reject(error);
})
})
}
In page:
this.$store.dispatch('NEW_AUTH')
.then(() => ... )
.catch((error) => ... )
To address the Con from Alternative 2 you can either
(a) pass in the name of the component or even a reference to the component or
(b) you can persist the error in the state for the component that made the call. Then in your component you could check if there is an error and display it. For that you could use a mixin to forgo the need for boiler plate.,
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
commit('save_to_state', { response: res });
}).catch((error) => {
commit('save_to_state', { error });
});
},
}
Create an error key in the state of each Vuex module. Then dispatch the error for a given component to its relative Vuex module. Then create a global handler to watch for errors in the separate Vuex modules and, if one is triggered, display the error.
// store/auth.js
export const state = () => {
return {
success: null,
error: null
}
}
export const actions = {
async login({ commit }, { email, password }) {
try {
const response = await axios.post('/api/login', {
email,
password
})
commit('SET_SUCCESS', response)
} catch(err) {
commit('SET_ERROR', error)
}
}
}
export const mutations = {
SET_SUCCESS(state, payload) {
state.success = payload
},
SET_ERROR(state, payload) {
state.error = payload
}
}
// auth.vue
export default {
methods: {
onLogin() {
try {
await this.$store.dispatch('auth/login', {
email: this.email,
password: this.password
})
if (this.$store.state.auth.success) this.$router.push('/home')
} catch (err) {
console.log(err)
}
}
}
}
// app.vue
export default {
created() {
this.$store.subscribe((mutation, state) => {
if (mutation.type.includes('ERROR')) {
// display error in global error output
console.log(mutation.payload)
}
})
}
}
Trying to mock one of the function with callback from api and getting error as TypeError: specificMockImpl.apply is not a function
import { IEnvironmentMap, load } from 'dotenv-extended';
import { getTokensWithAuthCode, sdk } from '../src/connection-manager';
describe('getTokensWithAuthCode function Tests', () => {
jest.useFakeTimers();
let boxConfig: IEnvironmentMap;
beforeAll(() => {
boxConfig = load({
errorOnMissing: true,
});
});
it('should reject a promise if there is wrong auth code provided', async () => {
sdk.getTokensAuthorizationCodeGrant = jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
try {
const tokens = await getTokensWithAuthCode();
} catch (error) {
expect(error).toBe('Error');
}
});
});
And my function which is trying to test is as follow:
import * as BoxSDK from 'box-node-sdk';
import { IEnvironmentMap, load } from 'dotenv-extended';
import {ITokenInfo} from '../typings/box-node-sdk';
const boxConfig: IEnvironmentMap = load({
errorOnMissing: true,
});
export const sdk: BoxSDK = new BoxSDK({
clientID: boxConfig.BOX_CLIENT_ID,
clientSecret: boxConfig.BOX_CLIENT_SECRET,
});
/**
* - Use the provided AUTH_CODE to get the tokens (access + refresh)
* - Handle saving to local file if no external storage is provided.
*/
export async function getTokensWithAuthCode() {
return new Promise((resolve: (tokenInfo: ITokenInfo) => void, reject: (err: Error) => void) => {
if (boxConfig.BOX_AUTH_CODE === '') {
reject(new Error('No Auth Code provided. Please provide auth code as env variable.'));
}
sdk.getTokensAuthorizationCodeGrant(boxConfig.BOX_AUTH_CODE, null, (err: Error, tokenInfo: ITokenInfo) => {
if (err !== null) {
reject(err);
}
resolve(tokenInfo);
});
});
}
Is there any other way to mock function in jest? I have read an article https://www.zhubert.com/blog/2017/04/12/testing-with-jest/
On this line, rather than pass a function to mockImplementation, you're passing three arguments:
jest.fn().mockImplementation(boxConfig.BOX_AUTH_CODE, null, cb => {
cb('Error', null);
});
It looks like you might have just missed some braces. Try switching it to:
jest.fn().mockImplementation((boxConfig.BOX_AUTH_CODE, null, cb) => {
cb('Error', null);
});
It's better not trying to mutate a const used elsewhere.
You could change getTokensWithAuthCode to make it receive sdk as parameter, thus in your test you would pass the mock function as argument, therefore having a more predictable behavior than mutating directly sdk.
In your code, you could make a second getTokensWithAuthCode implementation, with the signature getTokensWithAuthCodeUnbound(sdk) for example, and export it. This implementation will be used in your tests.
Exporting using the same getTokensWithAuthCode name, you would call:
export const getTokensWithAuthCode = getTokensWithAuthCodeUnbound.bind(null, sdk)
That way, your app will use getTokensWithAuthCodeUnbound bound with the default sdk, and you can test more easily its implementation.
Mozilla Developer Network (MDN) bind documentation.
I am trying to make my Vue app have server-side rendering. I am using vue-server-renderer (https://www.npmjs.com/package/vue-server-renderer). Client-side rendering is working fine.
My app use vue-router and axios
Here is my server.js:
server.get('*', (request, response) => {
bundleRenderer.renderToString({ url: request.url }, (error, htmlPromise) => {
if (error) {
// Log the error in the console
console.error(error)
// Tell the client something went wrong
return response
.status(500)
.send(error)
}
response.send(layout.replace('<div id=app></div>', htmlPromise))
})
})
getInfo() is the method to fetch server data.
Here is getInfo():
export default {
methods: {
getInfo(api) {
return axios
.get(api || this.$route.params.path)
.then((data) => {
this.data = data
this.$set(this, 'isLoading', false)
})
},
},
}
My server entry is:
import { app, router, store } from './index'
export default context => {
let componentPromises = router.getMatchedComponents().filter((component) => {
return component.methods && component.methods.getInfo
}).map((component) => {
return component.methods.getInfo()
})
return Promise.all(componentPromises).then(() => {
return app
})
}
However, I soon realize that all the components from router.getMatchedComponents() does not have $route or $set. Therefore, the method getInfo() stops working.
The document from https://router.vuejs.org/en/api/router-instance.html is very short and does not provide much information:
router.getMatchedComponents()
Returns an Array of the components (definition/constructor, not
instances) matched by the current route. This is mostly used during
server-side rendering to perform data prefetching.
How can I fix the problem?
I have previously incurred into a similar problem and managed to successfully prefetch data by doing the following:
app.$router.onReady(() => {
const matchedComponents = app.$router.getMatchedComponents()
if (!matchedComponents.length) { /* ... */}
Promise.all(matchedComponents.map((Component: any) => {
if (Component.options.methods.asyncData) {
return Component.options.methods.asyncData({
store: app.$store,
route: app.$router.currentRoute
});
}
})).then(() => { /* your callback here ... */ });
}
According to vue ssr documentation (https://ssr.vuejs.org/en/data.html) the suggested way is to use a custom asyncData method in your component to perform data fetching rather than calling component methods directly:
export default {
asyncData ({ store, route }) {
// return the Promise from the action
return store.dispatch('fetchItem', route.params.id)
}
},