NGXS updateItem state operator - ngxs

I'm trying to use the NGXS state operators inside of my application, but I'm having trouble finding good examples of how to use them for slightly more complex updates.
For example, NGXS's documentation shows an example of updating this state:
#State<AnimalsStateModel>({
name: 'animals',
defaults: {
zebras: ['Jimmy', 'Jake', 'Alan'],
pandas: ['Michael', 'John']
}
})
In order to change the names of one of the pandas, it uses NGXS's "updateItem" state operator like this:
#Action(ChangePandaName)
changePandaName(ctx: StateContext<AnimalsStateModel>, { payload }: ChangePandaName) {
ctx.setState(
patch({
pandas: updateItem(name => name === payload.name, payload.newName)
})
);
}
In this example, the updateItem function uses a lambda expression in its first parameter to find the correct object in the array and replaces it with with the object in the second parameter.
How would you do this with an array containing complex objects of which you only wanted to change the value of one property? For instance, what if my state was this:
#State<AnimalsStateModel>({
name: 'animals',
defaults: {
zebras: [{1, 'Jimmy'} , {2, 'Jake'}, {3, 'Alan'}],
pandas: [{1, 'Michael'}, {2, 'John'}]
}
})
How would I use the updateItem function to locate the correct animal using the ID and then update the name?

The default state example you provided is invalid syntax, but I think I get what you intended to provide. Something like this:
#State<AnimalsStateModel>({
name: 'animals',
defaults: {
zebras: [{id: 1, name: 'Jimmy'} , {id: 2, name: 'Jake'}, {id: 3, name: 'Alan'}],
pandas: [{id: 1, name: 'Michael'}, {id: 2, name: 'John'}]
}
})
updateItem also accepts a state operator as a second parameter so you could use the patch operator again to modify the item. Your action would then look like this:
#Action(ChangePandaName)
changePandaName(ctx: StateContext<AnimalsStateModel>, { payload }: ChangePandaName) {
ctx.setState(
patch({
pandas: updateItem(item=> item.id === payload.id, patch({ name: payload.newName }))
})
);
}

Related

How can you implement "temporary placeholders" in data structures?

I am trying to boil down a pretty complicated problem into its essence so I can get some help on how to model or architect it. Here it goes.
Say we are compiling functions in this order:
function test() {
sum(mul(2, 3), mul(3, 4))
}
function sum(a, b) {
return a + b
}
function mul(a, b) {
return a * b
}
We end up with an AST something like this:
{
type: 'Program',
blocks: [
{
type: 'Function',
name: 'test',
args: [],
body: [
{
type: 'Call',
function: 'sum',
args: [
{
type: 'Call',
function: 'mul',
...
},
...
]
}
]
},
{
type: 'Function',
name: 'mul',
args: ...,
body: ...
},
{
type: 'Function',
name: 'sum',
args: ...,
body: ...
}
]
}
Now we start compiling this AST into more easily manipulated objects, with direct pointers to functions and such. The final result might look like this:
{
type: 'Program',
blocks: [
{
type: 'Function',
name: 'test',
args: [],
body: [
{
type: 'Call',
pointer: 2,
args: [
{
type: 'Call',
pointer: 1,
...
},
...
]
}
]
},
{
type: 'Function',
name: 'mul',
args: ...,
body: ...
},
{
type: 'Function',
name: 'sum',
args: ...,
body: ...
}
]
}
The main difference is that the "final" version has a pointer to the index where the function is defined. This is a very rough sketch. The reality would be there could be multiple passes required to resolve some context sensitivity, and so you end up with multiple partial/intermediate data structures in the transition from the AST to the final compiled object.
How do you make types to deal with this situation? The ideal is that there is an "initial" and a "final" type. The reality is that on our first pass, we have a "placeholder type" for the function calls, which we can't resolve until we have completed our first pass. So on the first pass, we have:
function: String
On the second pass we change it to:
pointer: Int
How do you reconcile this? How do you architect the algorithm so as to allow for these "placeholder" types for the final data structure?
I have tried searching the web for these sorts of topics but haven't found anything:
partial types
intermediate types
placeholder types
virtual types
temporary types
transitional types
how to have temporary placeholders in data structures
etc.
Create a hashmap.
In a first pass write name/index pairs to the hashmap without modifying the AST itself. For the example that would result in this hashmap (represented in JSON format):
{
"mul": 1,
"sum": 2
}
In a second pass you can use the hashmap to replace references to the keys of this hashmap with a pointer property that gets the corresponding value.
I would suggest not trying to understand how to store intermediate data types, but understanding how to store "references" or "holes". Go look up how a typical serialization/deserialization algorithm works (especially one that can deal with something like repeated substructure or circular references): http://www.dietmar-kuehl.de/mirror/c++-faq/serialization.html
It may give you helpful ideas.

GraphQL, extract all values of a specific type into an array

Let's assume I have schema like
type StaticData {
id: Int!
language: String!
name: String!
description: String!
someA: SpecialType!
someB: SpecialType!
}
SpecialType is a scalar. SpecialType can be used in further nested structures as well. Let's now assume I query for a list of StaticData. Is it somehow possible to receive a list of all SpecialType values, without me extracting it manually from the returned object?
Example:
My return object looks like this:
[
{
id: 1,
language: 'en',
name: 'Something',
description: 'SomeDescription',
someA: 1234,
someB: 2345
},
{
id: 2,
language: 'en',
name: 'SomethingElse',
description: 'SomeDescription',
someA: 4564,
someB: 1234
},
]
Since I want all SpecialType values, I do want to extract [1234, 2345, 4564, 1234]. Maybe it is possible to do the extraction on the server using resolvers and receive it with the full result object?
If you want the GraphQL response to be just a list of integers, then you'll need to provide a type of that shape to your schema, i.e. someA: [SpecialType!].
You're on the right track - it's on you to then build and return a list in the resolver that satisfies this structure. Hope this helps!

GraphQL - Relationship returning null

I started to learn GraphQL and I'm trying to create the following relationship:
type User {
id: ID!,
name: String!,
favoriteFoods: [Food]
}
type Food {
id: ID!
name: String!
recipe: String
}
So basically, a user can have many favorite foods, and a food can be the favorite of many users. I'm using graphql.js, here's my code:
const Person = new GraphQLObjectType({
name: 'Person',
description: 'Represents a Person type',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
favoriteFoods: {type: GraphQLList(Food)},
})
})
const Food = new GraphQLObjectType({
name: 'Food',
description: 'Favorite food(s) of a person',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
recipe: {type: GraphQLString}
})
})
And here's the food data:
let foodData = [
{id: 1, name: 'Lasagna', recipe: 'Do this then that then put it in the oven'},
{id: 2, name: 'Pancakes', recipe: 'If you stop to think about, it\'s just a thin, tasteless cake.'},
{id: 3, name: 'Cereal', recipe: 'The universal "I\'m not in the mood to cook." recipe.'},
{id: 4, name: 'Hashbrowns', recipe: 'Just a potato and an oil and you\'re all set.'}
]
Since I'm just trying things out yet, my resolver basically just returns a user that is created inside the resolver itself. My thought process was: put the food IDs in a GraphQLList, then get the data from foodData usind lodash function find(), and replace the values in person.favoriteFoods with the data found.
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'Root Query',
fields: {
person: {
type: Person,
resolve(parent) {
let person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3]
}
foodIds = person.favoriteFoods
for (var i = 0; i < foodIds.length; i++) {
person.favoriteFoods.push(_.find(foodData, {id: foodIds[i]}))
person.favoriteFoods.shift()
}
return person
}
}
}
})
But the last food is returning null. Here's the result of a query:
query {
person {
name
favoriteFoods {
name
recipe
}
}
}
# Returns
{
"data": {
"person": {
"name": "Daniel",
"favoriteFoods": [
{
"name": "Lasagna",
"recipe": "Do this then that then put it in the oven"
},
{
"name": "Pancakes",
"recipe": "If you stop to think about, it's just a thin, tasteless cake."
},
null
]
}
}
}
Is it even possible to return the data from the Food type by using only its ID? Or should I make another query just for that? In my head the relationship makes sense, I don't think I need to store the IDs of all the users that like a certain food in the foodData since it has an ID that I can use to fetch the data, so I can't see the problem with the code or its structure.
Calling shift and push on an array while iterating through that same array will invariably lead to some unexpected results. You could make a copy of the array, but it'd be much easier to just use map:
const person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3],
}
person.favoriteFoods = person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
return person
The other issue here is that if your schema returns a Person in another resolver, you'll have to duplicate this logic in that resolver too. What you really should do is just return the person with favoriteFoods: [1, 2, 3]. Then write a separate resolver for the favoriteFoods field:
resolve(person) {
return person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
}

Creating a GraphQLObjectType with an indexable field signature?

I'm currently in the process of transforming a REST API into GraphQL, but I've hit a bit of a snag in one of the endpoints.
Currently, this endpoint returns an object who's keys can be an unlimited set of strings, and whos values all match a certain shape.
So, as a rudimentary example, I have this situation...
// response
{
foo: { id: 'foo', count: 3 },
bar: { id: 'bar', count: 6 },
baz: { id: 'baz', count: 1 },
}
Again, the keys are not known at runtime and can be an unlimited set of strings.
In TypeScript, for example, this sort of situation is handled by creating an interface using an indexable field signature, like so...
interface Data {
id: string;
count: number;
}
interface Response {
[key: string]: Data;
}
So, my question is: Is this sort of thing possible with graphql? How would I go about creating a type/schema for this?
Thanks in advance!
I think that one solution can be usage of JSON.stringify() method
exampleQuery: {
type: GraphQLString,
resolve: (root, args, context) => {
let obj = {
foo: { id: 'foo', count: 3 },
bar: { id: 'bar', count: 6 },
baz: { id: 'baz', count: 1 }
};
return JSON.stringify(obj);
}
}
Then, after retrieving the result of GraphQL query you could use JSON.parse(result) (in case the part performing the query is also written in JavaScript - otherwise you would have to use equivalent method of other language to parse the incoming JSON response).
Disadvantage of such a solution is that you do not have the possibility to choose what fields of obj you want to retrieve from the query, but, as you said, the returning object can have unlimited set of strings that probably are not known on the front end of the application, so there is no need to choose it's keys, am I right?

How to use the names in a GraphQLEnumType as the defaultValue of a GraphQL query argument?

When defining a query in a schema, how do I refer to a value of an GraphQLEnumType declared previously, to use it as the default value of an argument?
Let's say I've defined following ObservationPeriod GraphQLEnumType:
observationPeriodEnum = new GraphQLEnumType {
name: "ObservationPeriod"
description: "One of the performance metrics observation periods"
values:
Daily:
value: '1D'
description: "Daily"
[…]
}
and use it as the type of query argument period:
queryRootType = new GraphQLObjectType {
name: "QueryRoot"
description: "Query entry points to the DWH."
fields:
performance:
type: performanceType
description: "Given a portfolio EID, an observation period (defaults to YTD)
and as-of date, as well as the source performance engine,
return the matching performance metrics."
args:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily ← how to achieve this?
[…]
}
Currently I'm using the actual '1D' string value as the default value; this works:
period:
type: observationPeriodEnum
defaultValue: '1D'
But is there a way I could use the Daily symbolic name instead? I couldn't find a way to use the names within the schema itself. Is there something I overlooked?
I'm asking, because I was expecting an enum type to behave as a set of constants also, and to be able to use them like this in the schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Naïve workaround:
##
# Given a GraphQLEnumType instance, this macro function injects the names
# of its enum values as keys the instance itself and returns the modified
# GraphQLEnumType instance.
#
modifiedWithNameKeys = (enumType) ->
for ev in enumType.getValues()
unless enumType[ ev.name]?
enumType[ ev.name] = ev.value
else
console.warn "SCHEMA> Enum name #{ev.name} conflicts with key of same
name on GraphQLEnumType object; it won't be injected for value lookup"
enumType
observationPeriodEnum = modifiedWithNameKeys new GraphQLEnumType {
name: "description: "Daily""
values:
[…]
which allows to use it as desired in schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Of course, this modifier fullfils its promise, only as long as the enum names do not interfere with GraphQLEnumType existing method and variable names (which are currently: name, description, _values, _enumConfig, _valueLookup, _nameLookup, getValues, serialize, parseValue, _getValueLookup, _getNameLookup and toString — see definition of GraphQLEnumType class around line 687 in https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L687)
I just ran into this. My enum:
const contributorArgs = Object.assign(
{},
connectionArgs, {
sort: {
type: new GraphQLEnumType({
name: 'ContributorSort',
values: {
top: { value: 0 },
},
})
},
}
);
In my queries, I was doing:
... on Topic {
id
contributors(first: 10, sort: 'top') {
...
}
}
Turns out you just don't quote the value (which after thinking about it makes sense; it's a value in the enum type, not an actual value:
... on Topic {
id
contributors(first: 10, sort: top) {
...
}
}
It's possible to declare enum values as default inputs via the schema definition language, but it looks like you are only using the JS library APIs. You might be able to get to a solution by taking a look at the ASTs for the working example and comparing that with the AST from what your JS code is producing.
Sorry not a solution, but hope that helps!
I found a pull request adding a method .getValue() to enum types, which returns name and value. In your case this call:
observationPeriodEnum.getValue('Daily');
would return:
{
name: 'Daily',
value: '1D'
}

Resources