TypeScript ignore expression `if (object instanceof SomeClass)` inside `for(){}`? Or not? - typescript-typings

I have code (see comments):
class ClassA
{
propA: any;
}
class ClassB
{
propB: any;
}
function fn( arr: (ClassA | ClassB)[] )
{
for( let element of arr )
{
if( element instanceof ClassA )
{
element.propA = true; // Work as expected
() =>
{
element.propA; // Unexpected error
}
}
}
}
Message of unexpectedly error:
Property 'propA' does not exist on type 'ClassA | ClassB'.
Property 'propA' does not exist on type 'ClassB'.
When I remove the loop for(){}. Works as expected:
class ClassA
{
propA: any;
}
class ClassB
{
propB: any;
}
function fn( element: ClassA | ClassB )
{
if( element instanceof ClassA )
{
element.propA = true; // Work as expected
() =>
{
element.propA; // Work as expected
}
}
}
It's a bug?

Resolved by aluanhaddad - contributor TypeScript:
Your inner function is closing over a mutable binding that is scoped outside the if block and is thus not always going to be an instance of ClassA.
To make this work, use a const binding:
function fn(arr: (ClassA | ClassB)[]) {
for(const element of arr) {

Related

Property 'id' does not exist on type 'string | JwtPayload' [duplicate]

This is a situation I have ran into a couple of times, it seems like it should be fairly straightforward, but I can't find a solution that doesn't set the type to any
A function takes one of two different objects as the argument, checks which object has been received, and returns the corresponding field.
This is a simplified version of the problem, but the issue is that the two objects are only distinguishable by their properties(which have no overlap), and I can't access any of the properties, because they're not present on the other type.
type Obj1 = {
message: string
}
type Obj2 = {
text: string
}
const getText = (obj: Obj1 |obj2): string => {
if (obj.message) {
return obj.message
}
return obj.text
}
You have to narrow down the type. You can do so by using the in operator.
const getText = (obj: Obj1 | Obj2): string => {
if ("message" in obj) {
return obj.message
}
return obj.text
}
You can cast the object to either Obj1 or Obj2:
type Obj1 = {
message: string
}
type Obj2 = {
text: string
}
const getText = (obj: Obj1 | Obj2): string => {
if ((obj as Obj1).message) {
return (obj as Obj1).message
}
return (obj as Obj2).text
}
The real answer to this problem according to what the question owner asked is this
But there might be a time you are using your defined type with primitive type in this way the above solution is not going to work as the problem I faced
here is the situation
type Obj1 = {
message: string
}
const getText = (obj: Obj1 |string): string => {
if (obj.message) {
return obj.message
}
return obj.text
}
so this scenario the solution stated above would not be perfect for you, so you might need to use typeof ✌️
const getText = (obj: Obj1 | string): string => {
if (typeof obj !== 'string') {
return obj.message
}
return obj.text
}
I recommend typescript-is.
import { is } from 'typescript-is';
...
const getText = (obj: Obj1 | Obj2): string => {
if (is<Obj1>(obj)) {
return obj1.message;
}
return obj2.text;
};

Accessing inline type in the constructor

I have this:
export class QueueEntity<T> implements HasInternalQueue<T> {
opts: { // <--- inline type here
foo: boolean
}
constructor(v: typeof this.opts) { // this doesn't quite work
this.opts = v
}
}
is there a way to reference the inline type or is this not possible?
Since there is no way to define a type inside the class directly (An open issue in the TS repo). I think you can use the class name to reference this:
export class QueueEntity<T> {
opts: { // <--- inline type here
foo: boolean
}
constructor(v: QueueEntity<T>['opts']) { // <-- should work now
this.opts = v
}
}
const obj = new QueueEntity({ foo : false });
const obj2 = new QueueEntity({ foo2 : false });
const obj3 = new QueueEntity();
Playground

Typescript: Generic type of method params to match type of callback function params

I'm trying to make a class that accepts a function in the constructor. The function can have arguments of any type. Then I want to put a method on the class that accepts that same arguments as function parameter, as it will be a wrapper around this callback. Here's a simplified example to show what I'm trying to do
interface Options<T> {
callbackFn(...x: any[]) => Promise<T>
}
class ExampleClass<T> {
private options: Options<T>;
result: T;
constructor(options: Options<T>) {
this.options = options;
}
async wrapperFn(...x: any[]) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(x)
// Do some stuff after
}
}
const example = new ExampleClass<string>({
callbackFn: (a: string, b:string) => new Promise((res) => {
res(a + b);
})
});
example.wrapperFn("foo", "bar")
This is basically the way I have it now, and it works but it obviously doesn't enforce the types of the params of wrapperFn which isn't ideal. Is there any way to do something like this?
If you want the compiler to keep track of both the callback return type and the callback argument list type, then you'll want Options to be generic in both the return type (you called it T but I'll call it R for "return") and the argument list type (I'll call it A for "arguments"):
interface Options<A extends any[], R> {
callbackFn(...x: A): Promise<R>
}
Now you can just use A anywhere you were using any[] before, and you'll get stronger typing. This also implies that ExampleClass needs to be generic in A and R too:
class ExampleClass<A extends any[], R> {
private options: Options<A, R>;
result?: R;
constructor(options: Options<A, R>) {
this.options = options;
}
async wrapperFn(...x: A) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(...x)
// Do some stuff after
}
}
Let's test it out:
const example = new ExampleClass({
callbackFn: (a: string, b: string) => new Promise<string>((res) => {
res(a + b);
})
});
// const example: ExampleClass<[a: string, b: string], string>
example.wrapperFn("foo", "bar") // okay
example.wrapperFn("foo", 123); // error!
// --------------------> ~~~
// Argument of type 'number' is not assignable to parameter of type 'string'.
Looks good.
Playground link to code

How to limit function input to dynamically assigned keys of an object in typescript?

lately I am trying to create a placeholder object that I will be able to use with typescript.
Idea is to have an empty object and two functions:
one to add new key to a placeholder object with another object as a value ( 'add' function )
and one to get this value by passing a key that already exists in a placeholder ( 'get' function )
I would like typescript to forbid to type keys that already exist in placeholder in 'add' function.
Also I would like to get suggestions while typing key in 'get' function.
Last thing that I would like to achieve is to have type of an object that is returned from 'get' function instead of 'any' or 'object'
Here is the sample code with some basic typing:
let placeholder = {}
function add(key: string, test: object) {
placeholder[ key ] = test
}
function get(key: string ) {
return placeholder[key]
}
add('test1', { val: 1 }) // here 'test1' is ok
add('test1', { val: 2 }) // here 'test1' should rise an error
let t1 = get('') // here 'test1' and should be suggested
t1.val // here t1 should have type { val: number }
So far I have tried using generic types with things like:
function add( key: Omit< string, keyof typeof placeholder >, test: object ) { ... } // it is casting key to properties of string
function get< Key extends keyof typeof placeholder > ( key: Key ) { ... } // it only works with static keys
That is not possible. This would require the type of the object to change, but types are static.
The only thing you could do would be to return an object from your add function with a modified type and then continue calls on that object.
Example of that approach (still has one typing issue i set to ignore):
class Placeholder<T>
{
constructor(private entries: T)
{
}
add<K extends string, V>(key: Exclude<K, keyof T>, value: V): Placeholder<T & { [X in K]: V }> {
const newEntries = { ...this.entries, [key]: value };
// #ts-ignore
return new Placeholder(newEntries);
}
get(key: keyof T) {
return this.entries[key];
}
}
const ph = new Placeholder({})
.add('test1', { val: 1 }) // here 'test1' is ok
.add('test1', { val: 2 }) // here 'test1' should rise an error
let t1 = ph.get('test1') // here 'test1' and should be suggested
t1.val // here t1 should have type { val: number }
[Playground Link]

How passing a protocol as parameter in Swift

In Objective-C, I know how passing a protocol as parameter:
- (void)MyMethod:(Protocol *)myparameter
But in Swift there is no more Protocol type.
How can I pass a protocol as parameter without knowing which is ?
In one of your comments you say:
"I want create a method which return an array of type of class which implements a desired protocol."
Have you tried something like the following:
//notice the use of #objc here
#objc protocol AlertProtocol
{
func getMyName()->String
}
class Class1 : AlertProtocol
{
let name = "Object 1"
func getMyName() -> String
{
return name
}
}
class Class2 : AlertProtocol
{
let name = "Object 2"
func getMyName() -> String
{
return name
}
}
//borrowing from and refactoring siLo's answer
func classesConformingToProtocol(proto:Protocol) -> [AnyClass]
{
let availableClasses : [AnyClass] = [ Class1.self, Class2.self ]
var conformingClasses = Array<AnyClass>()
for myClass : AnyClass in availableClasses
{
if myClass.conforms(to: proto)
{
conformingClasses.append(myClass)
}
}
return conformingClasses
}
Then use the above structure like this:
let classes = classesConformingToProtocol(AlertProtocol.self)
The tricky part that does the work is the "#objc" that exposes the protocol to the objective c runtime and allows us to pass any "Protocol Type" as a parameter.
Probably at some point in the future we will be able to do this in a "pure" Swift way.
Here is what I have tried:
#objc protocol Walker
{
func walk()
}
#objc protocol Runner
{
func run()
}
#objc class Zombie : Walker
{
func walk () { println("Brains...") }
}
#objc class Survivor : Runner
{
func run() { println("Aaaah, zombies!") }
}
func classesConformingToProtocol(proto:Protocol) -> AnyClass[]
{
let availableClasses : AnyClass[] = [ Zombie.self, Survivor.self ]
var conformingClasses = Array<AnyClass>()
for myClass : AnyClass in availableClasses
{
if myClass.conformsToProtocol(proto)
{
conformingClasses.append(myClass)
}
}
return conformingClasses
}
// This does not work
let walkers = classesConformingToProtocol(Walker.self)
let runners = classesConformingToProtocol(Runner.self)
I have been unable to convert Swift's Metatype information into a Protocol object.
In swift 2.0, I use it like this before:
classA.conformsToProtocol(XXXProtocol.self as! Protocol)
It doesn't works fine...
Look the definition of Protocol:
// All methods of class Protocol are unavailable.
// Use the functions in objc/runtime.h instead.
#available(iOS 2.0, *)
public class Protocol {
}
All are unavailable...and I don't know which to use instead in objc/runtime.h
So I have to use this method:
if ClassA is protocol<XXXProtocol> {
// do something
}
Currently, it works...
If you don't allow use #objc (because yours protocols have property, for example), the only solution that I found is with closure. Then, you need use a closure to use a protocol and return a value.
protocol Proto { }
protocol Proto2 { }
class Foo: Proto { }
class Bar: Proto, Proto2 { }
class Baz: Proto2 { }
class Qux { }
func printConforms(classList: [AnyClass], protoCond: (AnyClass) -> Any?) {
for i in classList {
print(i, terminator: " -> ")
if protoCond(i) != nil {
print("is subscriber")
} else {
print("NOT IS subscriber")
}
}
}
let myClasses: [AnyClass] = [Foo.self, Bar.self, Baz.self, Qux.self]
printConforms(classList: myClasses, protoCond: { $0 as? Proto.Type })
More complete example: https://gist.github.com/brunomacabeusbr/eea343bb9119b96eed3393e41dcda0c9
Edit
Another better solution is using generics, for example:
protocol Proto { }
class Foo: Proto { }
class Bar: Proto { }
class Baz { }
func filter<T>(classes: [AnyClass], byConformanceTo: T.Type) -> [AnyClass] {
return classes.filter { $0 is T }
}
filter(classes: [Foo.self, Bar.self, Baz.self], byConformanceTo: Proto.Type.self)
// return [Foo.self, Bar.self]
Worked out a way today (Xcode 6.1):
Firstly, the protocol must be marked as #objc for any checking to work.
Then use an "if let" cast to check for conformance.
#objc protocol MyProtocol {
var protocolValue: Int { get set }
}
if let conformingObject = someObject as? MyProtocol {
// conformingObject is now someObject cast to MyProtocol
conformingObject.protocolValue = 3
}

Resources