I want the $beforeValidate to wait for the async operation to complete as it updates the object to make it pass validation. But currently the $beforeValidate completes and rejects the record as it is not waiting for the async operation to complete.
class Label extends Model {
async $beforeValidate() {
if(this.name === undefined){
const res = await axios.get('/getSomeName')
console.log(res.body)
this.name = res.body
}
}
static get jsonSchema () {
return {
type: 'object',
required: ['name'],
properties: {
id: { type: 'integer' },
name: { type: 'string' }
}
}
}
}
Now when i insert a label with name undefined I can see that the validation error is raised before the async API call has even finished
await Label.query().insert({name: undefined})
Unfortunately $beforeValidate is a synchronous operation.
You can check it on the Official Documentation. Even there is an issue about it.
Related
I have local function to check some validation which returns true/false. I also have runtime callback function which is an async function ie. http call.
Note: This checkPermission function is happening inside a for loop.
I want to check if any othese two function call is true. Can anyone help me how to achieve this?
private checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): boolean {
if(callback) {
console.log('callback function defined');
}
//following is the local function. how to make callback() here?
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
My complete code is:
Component:
options: NavOption[] = [];
this.options = this.sideNavService.loadMenus();
Sidenav service:
loadMenus(): NavOption[] {
return this.getMenus();
}
private getMenus(): NavOption[] {
const filteredMenuItems: NavOption[] = [];
let menus = [{
id: 'recorded-events',
label: 'Recorded events',
icon: 'far fa-calendar-check fa-2x',
url: `/incident/${this.organisationId}/list`,
permissions: [
EventReportingPermissions.View,
EventReportingPermissions.ViewOwnEvents,
EventReportingPermissions.ViewEmployeesEvents
],
additionalPermissionCheck: () =>
this.eventAccessGroupService.hasEventAccessGroupException()//this is the service to make http call
},
{
id: 'new-events',
label: 'Report new event',
icon: 'far fa-calendar-plus fa-2x',
url: `/incident/${this.organisationId}/create`,
permissions: [EventReportingPermissions.Report]
}]
for(let item of menus) {
let canAccess = this.checkPermissions(
topLevelItem.module,
subItem.permissions
);
filteredMenuItems.push(item);
}
return filteredMenuItems;
}
//local function
private checkPermissions(moduleId: number, permissions: number[]): boolean {
//following returns value from local function and no http call
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
//additionalPermissionCheck?: () => Observable<boolean>;
I am not sure I am understanding correctly but is your callback the function that performs the permission checking?
If so you can use a map pipe:
// Beware this returns Observable<boolean> and not boolean
const safeCallbackResult = callback ? callback() : of(true) // default to returning true as we'd like to check for the second condition
return callback().pipe(
map(canDoAction => canDoAction ? this.userSecurityService.userHasLicenseAndPermission(...) : false)
)
If you'd like to return a boolean, you can't. Because the moment you need to await for the callback's observable emission that is an operation that can take some time. Even though you could make the function async
private async checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): Promise<boolean> {
// callback().toPromise() if using RxJS 6
// firstValueFrom(callback()) if using RxJS 7
if(callback && ! (await callback().toPromise())) return false
return this.userSecurityService.userHasLicenseAndPermission(...)
}
Something like this:
sub = myHttpGetCall$().subscribe(value => {
if (value && localValue) {
// do whatever when both are true
}
}
Where localValue is the return value from your local function, which I assume is not an async operation.
Use an RxJs iif https://www.learnrxjs.io/learn-rxjs/operators/conditional/iif
booleanObservable$ = iif(() => yourLocalCondition, yourHttpRequest$, of(false));
If your localCondition is true it will make the http request otherwise there is no point so it just retuns an observable that emits false.
I am looking to write what I am calling structural expectations with Jest and I am not sure how this could be accomplished.
To start I have a graphql server and a database with a number of todo items. I currently have the following test that just returns true if the content within the database is the same as the response that I have written. I want to check instead that the response looks like an object with data that could be anything.
Here is the code that I have:
describe('To Do:', () => {
it('add todo items', async () => {
const response = await axios.post('http://localhost:5000/graphql', {
query: `
query {
getTodoItems {
message
id
dateCreated
dateDue
}
}
`
});
const { data } = response;
expect(data).toMatchObject({
data: {
getTodoItems: [
{
message: "message",
id: "5bd9aec8406e0a2170e04494",
dateCreated: "1540992712052",
dateDue: "1111111111"
},
{
message: "message",
id: "5bd9aeec60a9b2579882a308",
dateCreated: "1540992748028",
dateDue: "1111111111"
},
{
message: "new message",
id: "5bd9af15922b27236c91837c",
dateCreated: "1540992789836",
dateDue: "1111111111"
}
]
}
})
});
});
Now I want to write something like this, where there can be any number of returned items and they follow similar structuring:
describe('To Do:', () => {
it('add todo items', async () => {
const response = await axios.post('http://localhost:5000/graphql', {
query: `
query {
getTodoItems {
message
id
dateCreated
dateDue
}
}
`
});
const { data } = response;
expect(data).toMatchObject({
data: {
getTodoItems: [
{
message: expect.any(String),
id: expect.any(String),
dateCreated: expect.any(String),
dateDue: expect.any(String)
} // There needs to be unlimited additional items here
]
}
})
});
});
I have been looking throught the docs and I even tried nesting the expectations but I can't seem to get the desired response. Let me know what yo think or if I can clarify in any way.
I figured out the best way for me to do it. I would love to hear better answers. I wrote a function within the scope of the test as a jest.fn and then I called it. In that function, I made custom checks to parse the data that was received in the response. From there I added an expect function with the 'toHaveReturnedWith' method to see what the response of my custom function was and finishing out the test.
const addTodoResponse = jest.fn(() => {
// Custom parsing and check here
// Returns true or false
});
addTodoResponse();
expect(addTodoResponse).toHaveReturnedWith(true);
Are there better ways to do this out there?
I often find myself using the following code:
export class Component implements OnDestroy {
private subscription: Subscription;
user: string;
constructor(private store: UserStore) {
this.subscription = store.select(fromUsers.getUser)
.subscribe(user => this.user = user);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
logout(): void {
this.store.dispatch({
type: LOGOUT,
payload: {
user: this.user
}
})
}
}
As you can see I need to store the user string as a member within the component to send it with my payload.
I would rather use the user string as an observable and make use of the async pipe.
How do I need to change my code to leverage the observable of the user when dispatching the action without storing it in a member variable?
You can use ngrx effects and enhance the LOGOUT command with current user.
#Effect() logoutEffect$ = this.actions$
.ofType(LOGOUT)
.withLatestFrom(this.store$)
.map(([action: Action, storeState: AppState]) => {
return storeState.getUser;
})
.map(payload => ({type: 'LOGOUT_USER', payload}))
I've got a fairly simple react container component that attempts to call set state in an ajax callback called from componentDidMount. The full error is:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the UserListContainer component.
the order of operations from my console.log are:
render
child-render
componentDidMount
ajax-data
[Big ol Error Message]
I started out using async/await but when I received the error I went back to callbacks with the same result. This is the relevant code:
export class UserListContainer extends React.Component<any, any>
{
constructor() {
super();
this.state = {
users: [], request: {}
};
}
//componentDidMount = async () => {
componentWillMount = () => {
console.log('componentWillMount');
//var response: Models.IUserQueryResponse = await Api.UserList.get(this.state.request);
Api.UserList.get(this.state.request).then((response) => {
console.log('ajax-data');
if (response.isOk) {
this.setState({ users: response.data, request: response.state });
}
});
}
render() {
console.log('render');
return <UserList
request={this.state.request}
users={this.state.users}
onEditClick={this.edit}
onRefresh={this.refresh}
/>;
}
Any help would be appreciated.
you cannot set state in componentWillMount because your component could be in a transitioning state.. also it will not trigger a re-rendering. Either use componentWillReceiveProps or componentDidUpdate.
Now that aside your issue is that you are calling setState in the callback from an API request. and the issue with that is you probably have unmounted that component and dont want to setState anymore.
you can fix this with a simple flag
constructor() {
super();
this.state = {
users: [], request: {}
};
this.isMounted = false;
}
componentDidMount(){
this.isMounted = true
}
componentWillUnmount(){
this.isMounted = false;
}
then in your api request you would do this.
Api.UserList.get(this.state.request).then((response) => {
console.log('ajax-data');
if (response.isOk && this.isMounted) {
this.setState({ users: response.data, request: response.state });
}
});
I think is better to use componentWillMount() instead of componentDidMount() cause you want to load the list and then set the state, not after the component was mounted.
I have a "post"-model in strongloop loopback with some properties:
title
text
tags
category
published (true or false)
Is it possible to use the model validations in strongloop loopback, but only when I want to publish the post, not when I save it?
Set up a custom post.saveOrPublish() remote method that only calls post.isValid() when post.publish === true. Or use the built-in persistedModel.save() for everything without validation and use a custom post.publish() remote method for when you actually click the publish button, which would trigger your validation code before calling save().
saveOrPublish example: (not tested, just a rough idea):
module.exports = function(Post) {
Post.saveOrPublish = function(post, cb) {
if(post.publish) {
post.isValid(function(valid){
if(valid) {
Post.upsert(post, function(err, post) {
if(err) {cb(err, null);}
cb(null, post);
});
} else {
cb(new Error('Publishing requires a valid post.'), post)
}
});
} else {
Post.upsert(post, function(err, post) {
if(err) {cb(err, null);}
cb(null, post);
});
}
};
// don't forget the remote method def
Post.remoteMethod('saveOrPublish',
{
accepts: [{
arg: 'post',
type: 'object'
}],
returns: {
arg: 'result',
type: 'object'
},
http: {verb: 'post'}
}
);
};