Array of objects with unique properties - typescript-typings

Is there a way to declare an interface that one of the properties is an array of objects and inside those objects we have a property that acts similarly to a "primary key" in a database structure? To not allow repetition.
interface a {
myList: {
uniqueName: string;
}[]
}
const myAObject: a = {
myList: [
{
uniqueName: "bla-1"
},
{
uniqueName: "bla-2"
},
{
uniqueName: "bla-1" //shouldnt allow that
}
]
}

I've used a Record<> to achieve something similar:
interface myThings = {
a: string;
b: string;
c: string;
}
type item<T, K extends keyof T> = {
name: string;
value: T[K];
}
type items<T> = { [K in keyof T]-?: Record<K, item<T, K>> }[keyof T];
const myItems:items<myThings> = {
a: { //this uniqueness is enforced with Record<>
name: "some name"
},
b: {
name: "some name"
},
c: {
name: "some name"
}
}

Related

setting type of index in for of loop in Typescript

I am trying to set the type of an item when I loop through a response which is an array of objects, however, I am not sure how to declare the type.
I have the following type:
export type Movie = {
title: string;
director: string;
year: string;
};
I get the following response from an api
const movies = [{
"movie": {
"title": 'The Dark Knight',
"director": 'Christofer Nolan',
},
"details": {
"year": 2008,
"rating": 4.5
}
},
{
"movie": {
"title": 'The Joker',
"director": 'Todd Phillips',
},
"details": {
"year": 2019,
"rating": 4.7
}
}
}]
I want to map the response to xml which I have the following function for
function mapToMoviesXML(movies: Movie[]) {
let data = `<?xml version="1.0" encoding="UTF-8"?>`;
data += `<movies>`;
for (let item of movies) {
data += `<movies>
<title>${item.movie.title}</title>
<director>${item.movie.director}</director>
<year>${item.details.year}</year>
<rating>${item.details.rating}</rating>
</movies>`;
}
however, I get the following error for item.movie and item.details within the loop
Property 'movie' does not exist on type 'Movie'
Property 'details' does not exist on type 'Movie'
I thought because I am getting the final value i.e. item.movie.title which is defined in the Movie type I would not need to declare a type for item. Any ideas what I need to change or update my type to?
To satisfy the way movies: Movie[] is being used (and to mirror what the API is actually sending) the type definition would need to be something along the lines of this:
export type Movie = {
movie: {
title: string;
director: string;
}
details: {
year: number;
rating: number;
};
};
or potentially more useful would be to break the nested objects into their own types and reference them from the parent object, i.e.
export interface MovieOverview {
title: string;
director: string;
}
export interface MovieDetails {
year: number;
rating: number;
}
export interface Movie {
movie: MovieOverview;
details: MovieDetails;
}

Typescript undefined error produced for defined enum

In file defs.ts I define an enum and export it...
enum MyState {
state1 = 'state1'
state2 = 'state2'
... state5 = 'state5'
}
this gets exported to index.tsx of a Component and exported from there
export {
MyState,
...
}
It then gets imported into a file in another Component
import { MyState } from "../Component1"
const filterMap: { [key: string]: { title: string; filter: object } } = {
s1: { title: 's1', filter: { state: MyState.state1 } },
s2: { title: 's2', filter: { state: MyState.state2 } },
s2: { title: 's3', filter: { state_ne: [MyState.state3 ] } },
}
However on being run, an error gets generated
Uncaught TypeError: Cannot read properties of undefined (reading 'MyState')
WTF is going on?
This works for me
defs.ts
export enum MyState {
state1 = 'state1',
state2 = 'state2',
state5 = 'state5',
}
Component2.tsx
import { MyState } from './defs'
const filterMap: { [key: string]: { title: string; filter: object } } = {
s1: { title: 's1', filter: { state: MyState.state1 } },
s2: { title: 's2', filter: { state: MyState.state2 } },
s2: { title: 's3', filter: { state_ne: [MyState.state5 ] } },
}
If you need to export it from an intermediary you would do something like this.
Component1.tsx
export * from './defs'
I

Get local JSON data: The element has a type "any" implicitly

I have a project in Node JS with Typescript in which I am creating an API to get data from a local JSON file depending on the given variable.
This is my model.ts:
interface ListProductBatchModel {
resp: ResultMsg
}
interface ResultMsg {
name?: string,
price?: string,
error?: string
}
export { ListProductBatchModel, ResultMsg };
This is my properties JSON file:
{
"CT": {
"name": "box",
"price": "5,00"
},
"CC": {
"name": "car",
"price": "6,00"
}
}
This is my controller.ts:
import * as logger from 'winston';
import { Controller, Get, Response, Route, SuccessResponse, Tags } from 'tsoa';
import { ListProductBatchModel } from './models/listProduct.models';
import { ListProductBatchUtils } from './utils/listProductBatch.utils';
#Route('/list/product')
#Tags('list-product')
export class ListProductBatchController {
private listProductBatchUtils: ListProductBatchUtils;
constructor() {
this.listProductBatchUtils = new ListProductBatchUtils();
}
#Get('/{codProduct}')
#SuccessResponse(200, 'Success Response')
async listProductBatch(codProduct: string): Promise<ListProductBatchModel> {
try {
const listProductBatch = await this.listProductBatchUtils.getDataProduct(codProduct);
return Promise.resolve(listProductBatch as ListProductBatchModel);
} catch (error) {
logger.info(JSON.stringify(error));
return Promise.reject(error);
}
}
}
This is my utils.ts:
import * as logger from 'winston';
import * as getProperty from '../json/product.json';
import { ListProductBatchModel, ResultMsg } from '../models/listProduct.models';
export class ListProductBatchUtils {
public async getDataProduct(codProduct: string): Promise<ListProductBatchModel> {
try {
let result: ResultMsg;
if (getProperty[codProduct.toUpperCase()]) {
result = {
name: getProperty[codProduct.toUpperCase()].name,
price: getProperty[codProduct.toUpperCase()].price
}
}else {
result = {
error: "ERROR"
}
}
logger.info('start')
return Promise.resolve({ resp: result });
} catch (error) {
logger.info(JSON.stringify(error));
return Promise.reject(error);
}
}
}
This is the error I get in getProperty [codProduct.toUpperCase ()]:
The element has a type "any" implicitly because the expression of type "string" cannot be used to index the type "{CT: {name: string; price: string;}; CC: {name: string; price : string;};} ".
No index signature was found with a parameter of type "string" in type "{CT: {name: string; price: string;}; CC: {name: string; price: string;};}".
My problem: I don't understand how the error is generated, what I want is to take the name and price properties that match the codProduct variable. Why can this happen? What am I doing wrong and how can I solve it?
Right now, codProduct is a string. When you're accessing getProduct via its subscript [], TypeScript expects you to use an index of getProduct (which, in this case is either "CT" or "CC").
You can satisfy the TypeScript compiler by casting your string as a keyof getProperty's type. Note that this will work at compile time, but will not guarantee that it is in fact a key of getProperty at runtime. But, since you're doing boolean checks already, that seems like it will be okay in your case.
Here's a simplified example:
const getProperty = {
"CT": {
"name": "box",
"price": "5,00"
},
"CC": {
"name": "car",
"price": "6,00"
}
};
type GetPropertyType = typeof getProperty;
function myFunc(input: string) {
const result = getProperty[input.toUpperCase() as keyof GetPropertyType].name;
}

How to manage GraphQL child objectType that can be nullable in an output type?

I'm setting up a nodeJS GraphQL API and I'm experimenting a blocking point regarding one of my resource output type.
The feature is a form that contain three different level :
Level 1- formTemplate
Level 2- formItems (templateId, type (video, image, question) - 1-N relation with formTemplate)
Level 3- formQuestions (0-1 relation with formItem if and only if formItems.type is 'question')
My GraphQL resource is returning all the templates in the database so it's an array that for each template is returning all his items and each item of type "question" needs to return an array containing the associated question.
My problem is : I really don't know how to return an empty object type for the formItems where type is different from "question" or if there is a better approach for this kind of situation
I've tried to look at GraphQL directives and inline fragments but I think it really needs to be manage by the backend side because it's transparent for the API consumer.
const formTemplate = new GraphQLObjectType({
name: 'FormTemplate',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
authorId: {
type: new GraphQLNonNull(GraphQLInt)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
items: {
type: new GraphQLList(formItem),
resolve: parent => FormItem.findAllByTemplateId(parent.id)
}
}
}
})
const formItem = new GraphQLObjectType({
name: 'FormItem',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
templateId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
question: {
type: formQuestion,
resolve: async parent => FormQuestion.findByItemId(parent.id)
}
}
}
})
const formQuestion= new GraphQLObjectType({
name: 'FormQuestion',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
itemId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
label: {
type: new GraphQLNonNull(GraphQLString)
}
}
}
})
My GraphQL request :
query {
getFormTemplates {
name
items {
type
question {
label
type
}
}
}
}
What I'm expected is
{
"data": {
"getFormTemplates": [
{
"name": "Form 1",
"items": [
{
"type": "question",
"question": {
"label": "Question 1",
"type": "shortText"
},
{
"type": "rawContent"
"question": {}
}
]
}
]
}
}
I'd design your "level 2" items so that the "type" property corresponded to actual GraphQL types, implementing a common interface. Also, in general, I'd design the schema so that it had actual links to neighboring items and not their identifiers.
So if every form item possibly has an associated template, you can make that be a GraphQL interface:
interface FormItem {
id: ID!
template: FormTemplate
}
Then you can have three separate types for your three kinds of items
# Skipping VideoItem
type ImageItem implements FormItem {
id: ID!
template: FormTemplate
src: String!
}
type QuestionItem implements FormItem {
id: ID!
template: FormTemplate
questions: [FormQuestion!]!
}
The other types you describe would be:
type FormTemplate {
id: ID!
author: Author!
name: String!
items: [FormItem!]!
}
type FormQuestion {
id: ID!
question: Question
type: String!
label: String!
}
The other tricky thing is, since not all form items are questions, you have to specifically mention that you're interested in questions in your query to get the question-specific fields. Your query might look like
query {
getFormTemplates {
name
items {
__typename # a GraphQL builtin that gives the type of this object
... on Question {
label
type
}
}
}
}
The ... on Question syntax is an inline fragment, and you can similarly use it to pick out the fields specific to other kinds of form items.
Thank you David for your answer !
I've figured it out how to solve my problem using inline fragments and UnionTypes that seems to be the most adapted for this use case. Here is the code :
const formItemObjectType = new GraphQLUnionType({
name: 'FormItemObject',
types: [formItemContent, formItemQuestion],
resolveType(parent) {
switch (parent.type) {
case ('question'): return formItemQuestion
default: return formItemContent
}
}
})
and the GraphQL query using inline fragment:
query {
getFormTemplates {
name
items {
...on FormItemContent {
type,
meta
}
...on FormItemQuestion {
type,
meta,
question {
label
}
}
}
}
}

How can I create a nested mat-select in Angular?

I'm new to Angular so if my question is too noobie, please understand.
I have a data structure something like this:
export class Category {
constructor(
public id: string,
public name: string,
public hasChild: boolean,
public parent: string,
public uri: string
) { }
}
export class CategoryTreeItem {
constructor(
public category: Category,
public children: CategoryTreeItem[]
) {}
static createCategoryTree(categories: Category[], parent: string): CategoryTreeItem[] {
const tree: CategoryTreeItem[] = [];
const mainCats = categories.filter(cat => cat.parent === parent);
mainCats.forEach(cat => {
if (cat.hasChild) {
const subCats = this.getCategoryTree(categories, cat.id);
tree.push(new CategoryTreeItem(cat, subCats));
} else {
tree.push(new CategoryTreeItem(cat, null));
}
});
return tree;
}
}
And here is a sample output:
CategoryTreeItem.createCategoryTree(aCategoryArray, null);
// second parameter is null, since I want to get a full tree.
// If I want to start the tree from a specific category, I can give it's ID.
// this will return a data like this:
[
1: CategoryTreeItem {
category: Category {
name: A,
hasChild: true,
parent: null,
uri: a
},
children: [
1: CategoryTreeItem {
category: Category {
name: B,
hasChild: false,
parent: A,
uri: b
}
children: null // null since B.hasChild = false
}
]
},
2: CategoryTreeItem {
category: Category {
name: C,
hasChild: false,
parent: null,
uri: c
},
children: null // null since C.hasChild = false
}
3: CategoryTreeItem {
category: Category {
name: D,
hasChild: true,
parent: null,
uri: d
},
children: [
1: CategoryTreeItem {
category: Category {
name: E,
hasChild: true,
parent: D,
uri: e
}
children: [
// more children since E.hasChild = true
]
},
2: CategoryTreeItem {
category: Category {
name: F,
hasChild: false,
parent: D,
uri: f
}
children: null // null since F.hasChild = false
}
]
},
]
My problem is I have no idea to create a mat-select with this data.
Here is what I tried to do for now:
If a category does have a child category, it should be a <mat-optgroup> and not selectable. In my data structure, products cannot refer to a category which has child categories.
Categories with no child category are going to be <mat-option> placed in it's parent's opt-group.
This way I'm going to have a tree-like-looking mat-select, at least this is what I expect.
Thank you.

Resources