0

This is more of a javascript question but it's in the context of angularjs specifically. I'm trying to prepare for our eventually (5+ years? yes I know) migration to Angular 2+. I'd like to create new apps that are similar to Angular 2+ in look and feel. I created a helper function named Component() which acts as the dectorator @Component() and I'd like to place that above a class that acts as the angularjs 1.5+ .component().

However, since my Component() function is executed first it complains that the class isn't defined yet. But if I just use a function for the controller it works just fun even though it wasn't defined yet. Just curious why classes need to be defined in this case first but functions don't?

Component() helper function:

function Component(config) {
    angular.module(config.module)
        .component(config.selector, {
            templateUrl: config.templateUrl,
            controller: config.controller
        });
}

This works:

Component({
    module: 'myApp',
    selector: 'heroList',
    templateUrl: 'Components/Hero/hero.template.html',
    controller: HeroComponent
})
function HeroComponent() {
    console.log("Yay!");
}

This doesn't but only because the class definition is second. If it's first it works but that obviously kills the look and feel to Angular 2+

Component({
        module: 'myApp',
        selector: 'heroList',
        templateUrl: 'Components/Hero/hero.template.html',
        controller: HeroComponent
    })
    class HeroComponent {
        constructor() {
            console.log("Inside hero component!");
        }

        $onInit() {

        }
    }
user441521
  • 6,942
  • 23
  • 88
  • 160
  • 3
    Class definitions are not hoisted like functions. There is a good thread about it here: https://stackoverflow.com/questions/35537619/why-are-es6-classes-not-hoisted – Mark Apr 18 '18 at 14:14
  • Also, decorators aren't part of javascript, they're a typescript thing (http://www.typescriptlang.org/docs/handbook/decorators.html). Those `@Component` declarations are being compiled by typescript/angular into something completely different before they ever run in javascript – jmcgriz Apr 18 '18 at 14:19
  • @jmcgriz I get that, but the most common language used with Angular 2+ is typescript and the languages are similar given it's a superset so like I was saying this is all in a long term idea to get a similar look and feel so the transition a long way down the road is smoother. – user441521 Apr 18 '18 at 14:20
  • @Mark_M Thanks for the link. It's a bummer for my situation but I understand why it was done. – user441521 Apr 18 '18 at 14:21
  • @user441521 The languages aren't similar though, my point is that typescript does not exist or execute in a browser. Writing something that visually looks like `@Component` and comes before your function is not going to do the same thing that a decorator does. When typescript sees the decorator immediately before a function/class declaration, it interprets it and creates javascript output that looks nothing like what you're writing. – jmcgriz Apr 18 '18 at 14:40
  • @user441521 "*It's a bummer for my situation*" - I don't understand your situation well enough. Why can't you just move the `Component` call below the definition? Or just put the `class`/`function` expression in the arguments of the call, instead of using a declaration? – Bergi Apr 18 '18 at 15:16
  • @Bergi I will be putting it below the class definition and that'll be fine but the ideal, in the situation where I'm trying to simulate a more Typescript feel for a smoother transition, would be to have it above. You don't have to agree with my situation but clearly given the situation itself that would be the ideal for a smoother transition. On the other idea, I know in js land it's about nesting functions with functions within functions but I don't like that style (kind of unavoidable to some degree) so this helps me avoid that. Many layers of nesting is something I don't like personally. – user441521 Apr 18 '18 at 15:23
  • @user441521 If you want to emulate decorators, I've got a non-nesting solution for you :-) – Bergi Apr 18 '18 at 15:30

2 Answers2

1

As Mark_M mentioned in the comments, class implementations are not hoisted the way functions are.

In JavaScript, any functions can basically be used in the same scope even if they are defined after, because they are basically "processed first" (over-simplification for illustration):

a();

function a() {
  console.log('hi');
}

Classes are not hoisted in the same manner, or rather, their implementation isn't:

try {
  const a = new A();

  class A {
    constructor() {
      console.log('hi');
    }
  }
} catch (e) {
  console.log('it threw an error');
}

The only way I could realistically see to work around it would be to continue to use functions. You could have inside of the function a self-initializing class:

const a = new A();

function A() {
  class _A {
    constructor() {
      console.log('hi');
    }
  }
  
  return new _A;
}

That said, that is obviously incredibly ugly for a variety of reasons.

I'd highly recommend don't try to future proof your code for Angular 2, especially if you think you are 5 years out from that. It's highly likely that in 5 years, Angular 3 (if not 4) will be a thing.

Heck, with the rate that JavaScript itself has been evolving lately, even the basic syntax would be questionable. Who knows what JavaScript will look like in 5 years.

As someone who worked on a project that was "future-proofed" for Angular 2, I can tell you it only creates a lot of headaches in the long run since you end up with a bastardized syntax that isn't quite either, which makes it harder to onboard new people to the project.

samanime
  • 25,408
  • 15
  • 90
  • 139
  • Thanks for the info. I mean my Component() function is just a helper function so the syntax isn't really bastardized. I agree if I tried to do the class inside a function that would be more of a bastardization and something I agree that I shouldn't do. I do prefer classes and they aren't going anywhere so I guess I'll just have the Component() helper after the component class. It's the closest and in my view nice and clean for creating a new component. While functions inside of functions inside of function is a js thing, I do prefer the cleaner look of class methods for the template to call. – user441521 Apr 18 '18 at 14:27
1

You can emulate the TypeScript decorator quite closely by passing the controller to a call (just what happens when a decorator is used):

function Component(config) {
    return function(controller) {
        angular.module(config.module)
        .component(config.selector, {
            templateUrl: config.templateUrl,
            controller
        });
    };
}

Works with functions

Component({
    module: 'myApp',
    selector: 'heroList',
    templateUrl: 'Components/Hero/hero.template.html',
})
(function HeroComponent() {
    console.log("Yay!");
});

as well as with classes

Component({
    module: 'myApp',
    selector: 'heroList',
    templateUrl: 'Components/Hero/hero.template.html',
})(class HeroComponent {
    constructor() {
        console.log("Inside hero component!");
    }
    $onInit() {

    }
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I would say of the solutions for this specific issue of emulation, one has to agree with that premise first, that last classes part is pretty reasonable. – user441521 Apr 18 '18 at 15:36
  • The other reason this is interesting is because we have a project that is angular 1.3. It seems there aren't plans to upgrade it yet, but we'd like to take advantage of using components which weren't in that version. We could use this to make the directives so we can use them as components. When we upgrade we simply change the Component() function you have to use .component() instead of .direction() and it all keeps working. – user441521 Apr 18 '18 at 15:52
  • Could you provide an alternative approach for Component() that uses a directive() (and also class definition of the directive) for older versons of Angularjs that don't have .component() yet? I'm trying to get it working but not sure how to use scope{} for the directive to start with. The bindings aren't available inside the constructor() when I do it and class methods aren't working when hooked up to the view. – user441521 Apr 18 '18 at 18:33
  • @user441521 I don't really know angular, can you update your question with how `Component` would look like if it used directive and scope, without decorator-like syntax? – Bergi Apr 18 '18 at 19:22