Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntax Error when Decorator mutates original Entity #7687

Closed
sebald opened this issue Mar 25, 2016 · 4 comments
Closed

Syntax Error when Decorator mutates original Entity #7687

sebald opened this issue Mar 25, 2016 · 4 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@sebald
Copy link

sebald commented Mar 25, 2016

I wrote decorators (for Angular 1.x) to have some cleaner syntax when defining a component and using the DI.

The @Component decorator (see below) will mutate the original class in such a way that it will conform the ng.IComponentOptions type (when created). The original class will be "attached" to the controller attribute, etc.

So the decorator basically wraps the class inside another class from type Component. The resulting Component can be used to register itself with Angular (see below). The other decorator called @Inject will add meta data ($inject property) to the original class, so the DI will still work when the code is minified.

In the below example I am importing and creating a SomeComponentController, which is decorated with @Component and @Inject. The TS compiler will show a syntax error, because the imported component is not called with any arguments. But since the Component type doesn't require any, it is fine. Angular will inject the correct dependencies when it instantiates the controller (SomeComponentController).

It looks like a hard problem for the compiler to infer all this information, but is there a way to let it know that everything is fine? :) Because compiling and running the code works finde. Only the editor is not satisfied with the code.

TypeScript Version:

1.8.9

Code

import { Component, Inject } from '../utils/decorator';
import SomeService from '../services/some/some.service';
const template = require('./template.html');

@Component({ 
    template,
    bindings: {
        input: '>'
    }
})
@Inject('SomeService')
export default class SomeComponentController {
    constructor( SomeService:SomeService ) {
        // ...
    }
}
import * as angular from 'angular';
import SomeComponent from './some.component';

export default angular
    .module('app', [])
    .component('someSome', new SomeComponent()); // TS2346: Supplied parameters do not match any signature of call target.
export function Component (config:IComponentDecoratorOptions):ClassDecorator {
    return (target:FunctionConstructor) => {
        const component = class Component implements ng.IComponentOptions {
            public transclude:boolean | string | {[slot: string]: string} = config.transclude || true;
            public template:string|FunctionReturn<string> = config.template;

            public controller:FunctionConstructor = target;
            public bindings:{[binding: string]: string} = config.bindings || {};
            public require:string | string[] | {[controller: string]: string} = config.require || null;
        };
        return component;
    };
}

Expected behavior:

No error. Compiler recognizes the mutation.

Actual behavior:

Editor (vscode) shows "TS2346: Supplied parameters do not match any signature of call target." error.
But compiling the code actually still works without any errors (via webpack).

@mhegazy
Copy link
Contributor

mhegazy commented Mar 25, 2016

So the decorator will pass in SomeService to the constructor of SomeComponentController? is that the point? i do not see how this is implemented from looking Component.

@mhegazy mhegazy added Question An issue which isn't directly actionable in code Needs More Info The issue still hasn't been fully clarified labels Mar 25, 2016
@sebald
Copy link
Author

sebald commented Mar 25, 2016

Sorry, if my example was confusing, I guess it needs some understanding of Angular, which shouldn't be necessary. I try again :)

So, applying the @Inject('SomeService') decorator will result in the follwing:

SomeComponentController.$inject = ['SomeService];

This attached meta information is only relevant to Angular's Dependency Injection. The $inject will make sure that SomeComponentController will be invoked with SomeService as first and only argument. SomeService needs also to be registered with Angular, like:

angular
    .module('app')
    .service('SomeService', SomeService);

class SomeService {}

The second decorator @Component is the more important one. Applying it to SomeComponentController will result in the following "factory":

class Component {
    public transclude = true;
    public template = '<span>foo</span>';
    public bindings = { input: '>' };
    public require = null;
    public controller = SomeComponentController;
}

Obviously this construct doesn't really exist, only through the close created by applying the decorator. But I wanted to show what will get passed to the .component as second argument.

So really, when writing this:

import * as angular from 'angular';
import SomeComponent from './some.component';

export default angular
    .module('app', [])
    .component('someSome', new SomeComponent()); // => new Component()

SomeComponent is not SomeComponentController but rather an Component. Component does not have any arguments required for instantiation, but the TS compiler thinks I am creating a new SomeController an expects me to pass in an argument, thich is a SomeService.

But I do not need (and also can not create the SomeComponentController). Angular will do this for me when the someSome component has to be rendered. The SomeComponentController will then be automatically created by Angular with SomeService injected into it.

tl;dr I am mutation a class via a decorator. The original class does required some arguments for instantiation, but the class originated from the decorator doesn't. The TS compiler does not recognize this at development time and displays an error. Compiling the code does work though.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 25, 2016

The problem is that the type mutation in the decorator is not captured. the assumption is you will return something of the same shape. issue #4881 tracks this work.

for now, as a work around, you can make the constructor argument optional, this will make sure @compoenent gets the type, and the compiler is not complaining about missing arguments.

@mhegazy mhegazy removed the Needs More Info The issue still hasn't been fully clarified label Mar 25, 2016
@sebald
Copy link
Author

sebald commented Mar 25, 2016

Feared I have to do that. Thanks for the help. Really appreciate it! :)

@sebald sebald closed this as completed Mar 25, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

2 participants