I declare a model in ingredient.model.ts
export class Ingredient {
constructor(private name: string, public amount: number) {}
getName() { return this.name }
}
In ingredients.service.ts, if I get them in this way:
httpClient.get<Ingredient>(url).subscribe(
(igredient) => {
console.log(igredient.getName());
});
It gives errors in console, such as "no method getName in property igredient".
Also, whenever I try to declare a property type Category[] it fails, but Array seems working fine.
Edit:
I want to provide more info.
Given the Igredient model and the following JSON structure:
{
name: "Apple",
amount: "5",
created_at: "date",
}
The Igredient constructor isn't even invoked, therefore the GET payload won't be parsed.
You'll need to use a property, not a method. The returned object is really a json object, and there is no such thing as "getName()" method (despite your effort to add a type information). Try something like this:
export interface Ingredient {
strin: string,
amount: number,
created_at: string
}
httpClient.get<Ingredient>(url).subscribe(
(igredient) => {
console.log(igredient.amount);
});
EDIT: You need to provide a type information based on the expected json object. If the returned json object has attributes, strin, amount, and created_at, then you need to define a type that is compatible with the expected json object.
In angular 5, You can do this:
export interface Deserializable<T> {
deserialize(input: any): T;
}
export class Ingredient implments Deserializable<Ingredient>{
constructor(private name: string, public amount: number) {}
deserialize(input: any): Project {
Object.assign(this, input);
// do nested thing here -pop arrays of nested objects and create them
}
return this;
}
now in your service:
httpClient.get<Ingredient>(url).pipe(map(elem=>this.foo(elem)))
.subscribe((igredient) => {console.log(igredient.getName());
});
foo(ingredient:Ingrdient){
var i = new Ingridiant().desrialize(ingredient)
}
after the map you will have the Ingradient class, not the object.
Related
I have a question about MongoDB ISODate type and GraphQL. I need to declare a mutation in my gql schema that allows to add a document in my Mongo database.
This document has an ISODate property, but in my gql schema, I'am using a String :
mutation addSomething(data: SomeInput)
type SomeInput {
field1: String
field2: Int
created: String
}
My problem is that, in the new document, the created field is in String format (not ISODate), and I was expecting that. But I wonder how to do to make it insert an ISODate instead. Is there a "custom type" somewhere I could use instead a String ?
Thank you
PS: I'am using nodeJS and apollo libraries.
Edit 1 : Trying with the graphql-iso-date package
I have found this package https://www.npmjs.com/package/graphql-iso-date that adds 3 date custom types.
Here is my gql schema :
const { gql } = require('apollo-server');
const { GraphQLDateTime } = require('graphql-iso-date')
const typeDefs = gql`
scalar GraphQLDateTime
type Resto {
restaurant_id: ID!
borough: String
cuisine: String
name: String
address: Address
grades: [Grade]
}
type Address {
building: String
street: String
zipcode: String
coord: [Float]
}
type Grade {
date: GraphQLDateTime
grade: String
score: Int
}
input GradeInput {
date: GraphQLDateTime
grade: String
score: Int
}
extend type Query {
GetRestos: [Resto]
GetRestoById(id: ID!): Resto
}
extend type Mutation {
UpdateGradeById(grade: GradeInput!, id: ID!): Resto
RemoveGradeByIdAndDate(date: String, id: ID!): Resto
}
`
module.exports = typeDefs;
This is a test based on the sample restaurants dataset.
So, if I try to call the UpdateGradeById() function like this :
UpdateGradeById(grade:{date:"2020-08-25T08:00:00.000Z",grade:"D",score:15},id:"30075445"){...}
The document is updated but the date is always in String format (as you can see on the screenshot bellow) :
The date of the last grade in the list is recognized as a string (not as a date).
I can see an improvement though because before I was using the graphql-iso-date date fields were returned in timestamp format. Now they are returned as ISO string. But the insertion does not work as expected.
Ok, I missed to do something important in my previous example : resolvers.
So, if like me you want to manipulate MongoDB date type through GraphQL, you can use the graphql-iso-date package like this :
First, modify your schema by adding a new scalar :
const { gql } = require('apollo-server');
const typeDefs = gql`
scalar ISODate
type Resto {
restaurant_id: ID!
borough: String
cuisine: String
name: String
address: Address
grades: [Grade]
}
type Address {
building: String
street: String
zipcode: String
coord: [Float]
}
type Grade {
date: ISODate
grade: String
score: Int
}
input GradeInput {
date: ISODate
grade: String
score: Int
}
extend type Query {
GetRestos: [Resto]
GetRestoById(id: ID!): Resto
}
extend type Mutation {
UpdateGradeById(grade: GradeInput!, id: ID!): Resto
RemoveGradeByIdAndDate(date: ISODate!, id: ID!): Resto
}
`
module.exports = typeDefs;
(Here I choose to call my custom date scalar ISODate)
Then, you have to tell how to "resolve" this new ISODate scalar by modifying you resolvers file :
const { GraphQLDateTime } = require('graphql-iso-date')
module.exports = {
Query: {
GetRestos: (_, __, { dataSources }) =>
dataSources.RestoAPI.getRestos(),
GetRestoById: (_, {id}, { dataSources }) =>
dataSources.RestoAPI.getRestoById(id),
},
Mutation: {
UpdateGradeById: (_, {grade,id}, { dataSources }) =>
dataSources.RestoAPI.updateGradeById(grade,id),
RemoveGradeByIdAndDate: (_, {date,id}, { dataSources }) =>
dataSources.RestoAPI.removeGradeByIdAndDate(date,id),
},
ISODate: GraphQLDateTime
};
And that's it. Now, date properties in my mongodb documents are well recognized as Date type values.
I want to create an Object from an object type defined in Type or from an object name defined in String. The following example uses a String to hold the object type. But I think this is not that elegant - even with Type, this if block would increase dramatically for a lot of object types...
I didn't find a better solution for this yet. How can I create that object dynamically from the specified object type?
if (model == 'Event') {
data = Event.fromMap(result);
}
else if (model == 'Content') {
data = Content.fromMap(result);
}
else if (...) {
// ...
}
This is other approach.
class Event{
Event.fromMap(_map){
print('This is an event');
print(_map);
}
}
class Content{
Content.fromMap(_map){
print('This is a content');
print(_map);
}
}
Map<String, Function> types = {
'Event' : (_map)=>Event.fromMap(_map),
'Content' : (_map)=>Content.fromMap(_map),
};
void main() {
var a = types['Event']({'test':'success_event'});
print(a.runtimeType);
var b = types['Content']({'test':'success_content'});
print(b.runtimeType);
}
Its a bit more scalable (since only depends on add the class constructor into the map).
The explanation:
class Event{
Event.fromMap(_map){
print('This is an event');
print(_map);
}
}
class Content{
Content.fromMap(_map){
print('This is a content');
print(_map);
}
}
Here we are creating the test classes. nothing important.
Map<String, Function> types = {
'Event' : (_map)=>Event.fromMap(_map),
'Content' : (_map)=>Content.fromMap(_map),
};
Here we are defining a Map. Why? Because it allows us to access some value through some key in constant time. In this case, the keys are the Strings 'Event', 'Content', but also can be types as you wanted. For simplicity, let them be Strings. The values are Function's, in this example only getting as parameter a _map (because the Class constructors in the example require one parameter _map). So, if you need more types, only add the type and the function encapsulating the constructor for that type.
void main() {
var a = types['Event']({'test':'success_event'});
print(a.runtimeType);
var b = types['Content']({'test':'success_content'});
print(b.runtimeType);
}
Finally you can instantiate the classes easily. Only with the type string and passing to the function the values you want (In this example a map with a key 'test').
In your example would be something like:
data = types[model](result);
I am trying to consume an API where every object names its ID field differently. Example: Group.groupid, Team.teamid, etc.
I have a BaseAPIObject that has a required initializer that accepts a parsed JSON dictionary and a convenience initializer that takes just the ID field (the only required property of my class).
I've dealt with the changing id field names by adding a static aka "class" method that returns the ID field name and subclasses override that function to return their own field name.
The problem I have is that in my base class' convenience initializer I can't call self.dynamicType before I've called self.init() but I need the results of that static class function before I can properly construct my object.
public class BaseAPIObject {
var apiID: String!
var name: String?
var createdBy: String?
var updatedBy: String?
//Construct from JSONSerialization Dictionary
required public init(_ data: [String: AnyObject]) {
name = data["name"] as String?
createdBy = data["created_by"] as String?
updatedBy = data["updated_by"] as String?
super.init()
let idName = self.dynamicType.idFieldName()
apiID = data[idName] as String?
}
/// Creates an empty shell object with only the apiID set.
/// Useful for when you have to chase down a nested object structure
public convenience init(id: String) {
// THIS is what breaks! I can't call self.dynamicType here
// How else can I call the STATIC CLASS method?
// in ObjC this would be as simple as self.class.getIdFieldName()
let idFieldName = self.dynamicType.getIdFieldName()
let data = [idFieldName: id]
self.init(data)
}
//This gets overridden by subclasses to return "groupid" or whatever
class func idFieldName() -> String {
return "id"
}
}
Question: How can I solve the problem of calling a subclass' class function before I run init on the instance itself?
Instead of creating a class function for figuring out the id, create init functions instead. Since you already have to create one of these functions per subclass, you're not really losing anything. The subclasses init function then calls the super's init with the id name.
Here's an example, I changed some of the properties of your group just for the sake of making the example simple to illustrate the concept.
public class BaseAPIObject {
var objData: [String:String]
required public init(_ data: [String: String]) {
println("Data: \(data)")
self.objData = data
}
public convenience init(id: String, idFieldName: String) {
let data = [idFieldName: id]
self.init(data)
}
}
And then in your subclass, just conceptually something like this:
public class GroupObject: BaseAPIObject {
public convenience init (id: String) {
self.init(id: id, idFieldName: "group")
}
}
let go = GroupObject(id: "foo")
println(go.objData["group"]!) //prints "foo"
Well, I want to replace my String param from the following Play scala Route into my own object, say "MyObject"
From GET /api/:id controllers.MyController.get(id: String)
To GET /api/:id controllers.MyController.get(id: MyOwnObject)
Any idea on how to do this would be appreciated.
Well, I have written up my own "MyOwnObject" binder now. Another way of implementing PathBindable to bind an object.
object Binders {
implicit def pathBinder(implicit intBinder: PathBindable[String]) = new PathBindable[MyOwnObject] {
override def bind(key: String, value: String): Either[String, MyOwnObject] = {
for {
id <- intBinder.bind(key, value).right
} yield UniqueId(id)
}
override def unbind(key: String, id: UniqueId): String = {
intBinder.unbind(key, id.value)
}
}
}
Use PathBindable to bind parameters from path rather than from query. Sample implementation for binding ids from path separated by comma (no error handling):
public class CommaSeparatedIds implements PathBindable<CommaSeparatedIds> {
private List<Long> id;
#Override
public IdBinder bind(String key, String txt) {
if ("id".equals(key)) {
String[] split = txt.split(",");
id = new ArrayList<>(split.length + 1);
for (String s : split) {
long parseLong = Long.parseLong(s);
id.add(Long.valueOf(parseLong));
}
return this;
}
return null;
}
...
}
Sample path:
/data/entity/1,2,3,4
Sample routes entry:
GET /data/entity/:id controllers.EntityController.process(id: CommaSeparatedIds)
I'm not sure if it works for binding data in the path part of a URL, but you may want to read the docs on QueryStringBindable if you're able to accept your data as query params.
Is it currently possible to implement an indexer on a class in TypeScript?
class MyCollection {
[name: string]: MyType;
}
This doesn't compile. I can specify an indexer on an interface, of course, but I need methods on this type as well as the indexer, so an interface won't suffice.
Thanks.
You cannot implement a class with an indexer. You can create an interface, but that interface cannot be implemented by a class. It can be implemented in plain JavaScript, and you can specify functions as well as the indexer on the interface:
class MyType {
constructor(public someVal: string) {
}
}
interface MyCollection {
[name: string]: MyType;
}
var collection: MyCollection = {};
collection['First'] = new MyType('Val');
collection['Second'] = new MyType('Another');
var a = collection['First'];
alert(a.someVal);
This is an old question, for those looking for the answer: now it's possible to define a indexed property like:
let lookup : {[key:string]:AnyType};
the signature of the key must be either string or integer see:
Interfaces on www.typescriptlang.org
Is not possible to define an indexed property getter/setter in a class but you can "simulate" that in a way like this using Proxy:
class IndexedPropSample {
[name: string | symbol]: any;
private static indexedHandler: ProxyHandler<IndexedPropSample> = {
get(target, property) {
return target[property];
},
set(target, property, value): boolean {
target[property] = value;
return true;
}
};
constructor() {
return new Proxy(this, IndexedPropSample.indexedHandler);
}
readIndexedProp = (prop: string | symbol): any => {
return this[prop];
}
}
var test = new IndexedPropSample();
test["propCustom"] = "valueCustom";
console.log(test["propCustom"]); // "valueCustom"
console.log(test.readIndexedProp("propCustom")); // "valueCustom"
console.log(test instanceof IndexedPropSample); // true
console.log(Object.keys(test)); // ["propCustom", "readIndexedProp"]
you can try it in Typescript Playground