TypeScript Cannot find namespace despite Variable being in same Class - node.js

I'm trying to define Callbackdefinitions to make it easier to work with many callbacks in my Node.js project.
My database.ts file looks like that:
export default class Database {
//Export the enums
public static LoadObjectResponse = LoadObjectResponse;
//Export Callback definitions
public static loadObjectCallback: (resultCode: Database.LoadObjectResponse) => void;//ERROR
...
}
enum LoadObjectResponse {
ERROR_ON_LOADING, //"Error on Loading Object.",
OBJECT_NOT_FOUND //"Object not found."
}
So I want a loadObjectCallback defined, that says that the parameter has to be of the enum Type LoadObjectResponse. But when I try to do it like that, the compiler always gives the error
Cannot find namespace "Database"
I don't understand why it gives me the error, the variable itself is in the definition of Database, why doesn't it work?
It gives me the same error when I try to use it in Classfunction definitions:
public static loadObject(MongoModel, searchObject, callback: Database.LoadObjectResponse) {//ERROR namespace Database not found
Again Error:
Cannot find namespace "Database"
Inside of functions in the Database class calling
Database.LoadObjectResponse
works flawlessly, why doesn't it work in variable definitions?

Cannot find namespace "Database"
This is a common learning curve issue. You need to understand and be comfortable with the intutive concept of declaration spaces : https://basarat.gitbook.io/typescript/project/declarationspaces
Things are distinct in the type declaration space or in the variable declaration space.
In your case public static LoadObjectResponse is a variable hence cannot be used as a type (error on annotation usage : Database.LoadObjectResponse).
Fix
Please don't treat a class as a namespace. The file is a module.
export class Database {
//Export Callback definitions
public static loadObjectCallback: (resultCode: LoadObjectResponse) => void;//ERROR
}
export enum LoadObjectResponse {
ERROR_ON_LOADING, //"Error on Loading Object.",
OBJECT_NOT_FOUND //"Object not found."
}
Also beware of export default : https://basarat.gitbook.io/typescript/main-1/defaultisbad

It's because Database.LoadObjectResponse is a property and not a type. You can't use properties as types.
To make this work, change it to use the type of the property:
static loadObjectCallback: (resultCode: typeof Database.LoadObjectResponse) => void;
Or refer directly to the enum type of LoadObjectResponse:
static loadObjectCallback: (resultCode: LoadObjectResponse) => void

Related

Typescript - Dynamic class Type

I'm trying to build a sort of model Factory in Typescript.
I'm receiving a string parameter from an API call and I would like to istantiate a new object depending on the received value.
Here you can find a simple example of what I would like to accomplish:
/classes/ClassA.ts
export class ClassA {
doSomething() {
console.log("ClassA");
}
}
/classes/ClassB.ts
export class ClassB {
doSomething() {
console.log("ClassB");
}
}
/classes/index.ts
import { ClassA } from './ClassA';
import { ClassB } from './ClassB';
export { ClassA, ClassB }
Now, I would like to import all classes exported from index.ts (this file will be automatically updated when a new Class is being created) and run doSomething() on a class depending on a variable value:
/index.ts
import * as Classes from './classes';
const className: string = "ClassA";
new Classes[className]().doSomething()
In visualStudioCode I don't get any error, but at compile time I get:
error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'typeof import("/testApp/src/tmp/classes/index")'.
Even changing className to "any" gives the same result.
If I remove className type
const className = "ClassA";
it works without any issue but I cannot proceed in this direction because received value is "typed" as string.
I know that prepending istantiation code with
// #ts-ignore
It works but I would like to avoid this kind of "tricks"
So, what would it be the correct way to type className getting it's possible values from the imported ts?
Thanks
Micko

Typescript class can only be found if there isn't a reference to its properties?

I am using TypeScript 2.0 in VSCode, however, the errors highlighted are all confirmed by the TypeScript compiler. So I am importing a module:
import * as els from 'elasticsearch';
where elasticsearch has definitions installed, e.g. npm i #types/elasticsearch -S
Now if in my class I have a property with an els type like this:
private _client: els.Client;
There isn't an issue, however, if I have a property with a type like this:
search(term: string): Promise<els.Client.search> {}
then I get the error:
Module 'Elasticsearch' has no exported member 'Client'
How can the class not be found if I'm looking for one of its properties, but not if I just look for it?
You are right, the error message is confusing. It originates from your attempt to use els.Client.search as a type. You get similar messages if you try this:
import * as els from 'elasticsearch';
class Foo {
private _client: els.Client;
y: els.Client.search;
bar() {}
x: Foo.bar;
}
error TS2305: Module 'Elasticsearch' has no exported member 'Client'.
error TS2503: Cannot find namespace 'Foo'.
Note how in the second message it complains that it can't find Foo right within the Foo class. You might consider posting an issue for typescript about this.
How can the class not be found if I'm looking for one of its
properties, but not if I just look for it?
The real problem is that you probably want the return type of your search to be the same as the return type of els.Client.search. I don't think there is a better way to do that other than essentially copy els.Client.search declaration:
search<T>(term: string): Promise<els.SearchResponse<T>> {}

Optional arguments on interface and class can conflict

I have just come across an interesting gotcha where optional arguments on an interface and the implementing class can conflict.
I found this out the hard way (school boy error) whilst experimenting. You cannot spot it in the debugger and I assumed it was me messing up the dependency injection.
I'm guessing this is so an alternative interface can give a differing view on what default behaviour should be?
Is there a compiler warning or style cop rule to help point this out?
public interface MyInterface
{
MyStuff Get(bool eagerLoad = true); //this overrules the implementation.
}
public class MyClass : MyInterface
{
public MyStuff Get(bool eagerLoad = false) //will still be true
{
//stuff
}
}
Remember default arguments are a compile-time feature. The compiler picks up the default argument based on the static type of the reference in question and inserts the appropriate default argument. I.e. if you reference is of the interface type you get one behavior but if the reference is of the class type you get the other in your case.

class properties are not available in generic method c#

I am calling a generic method with two different classes as below:
FillDataPointsInOrder<Metrics>(dataPoints.Where(O => O.SortOrder != null).OrderBy(O => O.SortOrder));
FillDataPointsInOrder<Metric>(angieStatsCmp.GetDataColumns());
private void FillDataPointsInOrder<T>(IEnumerable<T> dataPoints)
{
foreach (T dpoint in dataPoints)
{
if (!dpoint.IsPhone)
FillDrp(this.EmailDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
if (dpoint.IsPhone && this.IsPhoneShop)
FillDrp(this.PhoneDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
}
}
in "FillDataPointsInOrder" method I am getting compile errors :
'T' does not contain a definition for 'IsPhone' and no extension method 'IsPhone' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
Same errors for Name , MetricId and VName properties.
Not sure why T is not able to access properties of Metrics and Metric.
If I remove the code from generic method and write it directly in foreach over dataPoints it is working fine.
Can somebody advise what is wrong here?
FillDataPointsInOrder only knows it will be called with a T. T could actually be string, int or anything.
If you want to call properties on T, you will have to use a where constraint.
But in this case it looks like your method does not even need to be generic.
If both Metric and Metrics share a base class or an interface that has the properties you need:
interface IMetric {
bool IsPhone {get; }
}
you could just have:
private void FillDataPointsInOrder(IEnumerable<IMetric> dataPoints)
Note that IEnumerable is covariant, so if Metric is a IMetric, IENumerable<Metric> is a IEnumerable<IMetric>
You need to at least tell the compiler something about T if you want to do that. Do you have an interface that has members like IsPhone, Name, MetricId, etc. that your classes implement?
If so you can add a 'where' constraint to your class definition:
public class Something<T> where T : ISomethingElse
...where ISomethingElse is the interface that implements IsPhone.

Implicit Conversion Not Working for Dynamic Type

I am running into a problem when trying to implicitly convert one of my dynamic types. There are two assemblies with definitions similar to the following:
Configuration.dll:
public class ConfigurationValue : DynamicObject
{
public ConfigurationValue(string val)
{
//...
}
//...
public static implicit operator string(ConfigurationValue val)
{
return val.ToString();
}
}
There is another class in this dll called Configuration with a member variable called Instance (to make the class singleton). This variable holds the ConfigurationValue instances in a dictionary and is of type dynamic. This allows me to do this following:
Server.dll:
//...
if (Configuration.Instance.SecurityLevel != "Insecure")
{
//...
}
Assuming that SecurityLevel is in the dictionary.
This if statement appears verbatim in my code and always fails with the following error:
{"Operator '!=' cannot be applied to operands of type 'System.Dynamic.DynamicObject' and 'string'"}
Previously, when these two classes were in the same assembly, this code worked fine. Can anyone tell me what I'm doing wrong here?
Thanks,
Max
Solved the problem, a little embarrassing actually, I forgot to change the container class for ConfigurationValue (e.g. the type of Configuration.Instance) from internal to public when I moved it to the new assembly, so of course the type couldn't be resolved and the implicit conversion was not found
Try
var SecurityLevel = new ConfigurationValue("Insecure");

Resources