Feature Request: angular.component helper #10007

Closed
johnlindquist opened this Issue Nov 11, 2014 · 119 comments
@johnlindquist
Member

Please see this demo: https://jsbin.com/yixote/11/edit?html,js,output

Thanks to "bindToController" in angular 1.3, we're able to create a much simpler api for building components.

Just as "angular.service" helps us to easily build an "angular.provider", I strongly believe a simplified "angular.component" could help us easily build components with "angular.directive".

I'm sure everyone will have plenty of feedback :)

@caitp
Contributor
caitp commented Nov 11, 2014

I feel like it's adding another way to do the same thing, which I tend to not like doing, buuuuut, maybe.

@petebacondarwin what do you think?

@caitp caitp added this to the 1.3.x milestone Nov 11, 2014
@johnlindquist
Member

@caitp does anyone use angular.provider? Nope, the use angular.factory or angular.service. I think it's time for angular.directive to get the same treatment :)

@caitp
Contributor
caitp commented Nov 11, 2014

people use angular.provider if they want to be able to operate on the object meaningfully within config blocks :> anyways, my personal opinion is that we sort of offer too many flavours of koolaid already, it's much simpler for people if there's just one way to do everything.

On the other hand, the directive API is ridiculously complicated, so maybe some sugar would be useful for it. Lets see what people think

@pkozlowski-opensource

IMO convince shortcuts are cool / important but I would rather not do it in 1.3 for 2 reasons:

  • learning directives is already hard and introducing yet another approach will only further confuse people. On the other hand guys who know what they are doing are probably having convenience shortcuts;
  • component is kind of bad name / timing given Angular2 effort.

I'm not opposed but I would prefer that we don't do it. My private, personal opinion :-)

@johnlindquist
Member

@petebacondarwin I agree with your points, but those of us building convenience shortcuts would love to standardize on these shortcuts for the community, teaching, etc. Having something blessed by the angular team would be wonderful, but we can figure out something else if not.

@petebacondarwin
Member

@johnlindquist - hey I haven't even commented yet!

I think this would defeat a strong reason to migrate to v2!!

But seriously while it would be nice to make directive development easier I agree that 1.3 is probably not the place.

From what I see this helper would change

mod.directive ('cmpnt', function () {
  return {
    controllerAs: 'cmpnt',
    link: linkFn,
    scope: scopeDef,
    template: template,
    controller: ControllerFn
  };
});

Into

mod.component ('cmpnt', template, scopeDef, controller, linkFn);

While it certainly has fewer chars I am not convinced this is much more grokable. Also as a general rule we are trying to move away from optional positional params.

Personally I don't think this should go in the core for 1.x but if there was large community pressure we could look at improving the defaults for the DDO in a 1.4 release.

@johnlindquist
Member

@petebacondarwin sorry, I thought @pkozlowski-opensource was you :)

  1. grokable - I would argue that:
mod.component('card', template);

is _much_ more grokable than

mod.directive('card', function(){
return {
template: template
}
})

The goal is to lessen the barrier between creating directives and cover the 90% scenario. Also, if the focus is on injecting providers into the controller (and not the directive), the factory function (the second param in .directive) just becomes cruft.

  1. params - I totally agree on position params. If you want lots of {options}, use .directive(), if you want to cover the 90% scenario, use .component() or whatever you want to name it.
  2. 1.3 - Agreed, 1.3 is probably the wrong place, but the Angular community now has years of experience under its belt and it feels to me like it's time to start wrapping some of the most common tasks with more helpful APIs and revisit how we introduce newcomers to Angular.

Anyway, this all stems from the larger community sentiment that "angular is hard". I teach angular all the time and I've seen people get super confused over what should be the easiest concepts because of the API. I know angular is easier than it lets on, but I think a simpler API blessed by the angular team would go a long way to convince everyone else :P.

Obviously none of this is necessary and it doesn't add any functionality, but that doesn't mean it's not worth it!

/end rant?

@petebacondarwin
Member

In many ways I agree with you John. The complicated directive api is a major factor in the drive to v2 where the api is completely reworked to get away from this unnecessary complexity.

I also note that most of the service confusion comes from the difference between service and factory rather than with provider.

The question is where is the right place to make the improvement.

@johnlindquist
Member

@petebacondarwin are you at Google HQ now? I'm coming over next month or so to talk with the guys about doing new videos for the homepage, etc. Would be fun/constructive to play around with simple APIs, even if they land in a different project.

@petebacondarwin
Member

I am as always in London, UK. Give me a shout when you are next over this side of the pond

@demisx
demisx commented Dec 12, 2014

I would like to add my 2 cents since we are in the business of developing various enterprise applications. Please keep in mind that I share our team opinion only hoping it may add value.

I like the idea of simplifying the directive API, but calling this variant a 'component' would be very confusing. In our understanding, A component bears a much higher abstraction. A component is a stateless isolated chunk of product functionality (think of app features) that entire app is broken down into. A component may encapsulate a various number of resources/assets that are necessary to deliver that particular app feature. For example, a 'profile' component, besides directives, may contain services, images, SASS, filters etc. - everything that is necessary for this component/feature to work. What @johnlindquist shows here, appears to be more of a UI widget, then component, since it is implemented via one directive. So, maybe, if this was defined as module.widget instead, it would make more sense. Otherwise, there would be an overlap of concepts.

@kentcdodds
Member

Heard this is actually going to happen! ['hip', 'hip'] 👏

@vivainio

Hopefully using templateUrl instead of direct template is going to be straightforward.

@thorn0
Contributor
thorn0 commented Dec 15, 2014

#5893 is related to this kind of directives, so it might make sense to consider doing it as well

@kentcdodds
Member

Oh, just wanted to make sure that this is mentioned, but I believe it is the desire of everyone to move to the controllerAs syntax. If this angular.component supported that by default it would be great. Not sure how to make that happen without requiring the developer to specify the controller's name (vm for example), but I definitely think that angular.component should default to bindToController and controllerAs: 'somethingSpecifiedByTheDeveloper'.

@petebacondarwin
Member

@kentcdodds - this is included in @johnlindquist's proposal. By default it uses the name provided as the first parameter to the call unless it is specifically provided as a controllerAs property on the config parameter.

@kentcdodds
Member

Duh, must have missed that. Thanks for setting me straight @petebacondarwin :-)

@Zizzamia
Contributor

In angular-seed there is already an orientation to build Component related app. I can understand can be confusing have Web Components and Angular Components, but have an official angular.component will totally #simplify the way to scaffold new projects and improve your angular.component version by version. +1 for angular.component

@petebacondarwin petebacondarwin modified the milestone: 1.4.x, 1.3.x Jan 12, 2015
@a-lucas
a-lucas commented Jan 22, 2015

@demisx

I totally agree on the component naming. widget is much more appropriate than component.

@demisx
demisx commented Jan 22, 2015

@a-lucas 👍 FYI, here is how we define and break down app into components: AngularAtom component based organization) post. A component is so much more, than just a directive.

@a-lucas
a-lucas commented Jan 22, 2015

@demisx
I still don't understand the difference between a module and a component. This folder structure described in the link you provided matches almost exactly the one used in the MEAN.js project.

I usually organize my code the same way, because it makes it easier to find and visualize files in large projects. ( The project I actually work on has around a hundred controllers)

For re-usability purpose, it is better to have every folder being an independent module. But this is not that easy. Taking the example of a profile module (or profile component). One of the mandatory condition for re usability of the profile 'component' is that both project uses a very similar data structure.

But again, this can be tweaked into sub-module : I do have for example my profile module organized this way :

  • profile module
    • personal informations sub-module
      /partials & controllers & routes
    • address sub-module
      /partials & controllers & routes
    • documents sub-modules
      /partials & controllers & routes
    • status sub-modules
      /partials & controllers & routes
      and the list goes on.

I just don't get what advantages components would bring to this existing code organisation.

@demisx
demisx commented Jan 22, 2015

@a-lucas We currently don't use the concept of a widget at all. Entire app is broken down into components. A component can be a widget too. I've just suggested the term widget if Angular team were still going to implement this directive-based approach. So it would be angular.widget instead of angular.component, since directives deal with UI and components deal with so much more, than UI. We see a component as a grouping of various files and other sub-components. Components are similar to modules in other languages, but we chose the term component so it's not confused with the Angular modules themselves.

@thorn0
Contributor
thorn0 commented Jan 22, 2015

Agree on this. Widget is a better word.

@pkozlowski-opensource

I don't think naming is the main problem here... Regardless of the name used there are many flavours of widgets / components, ex.: do I use transclude or not? Should a template by embedded or downloaded from a URL? If we start to combine all those possibilities, how this new API will differ as compared to the existing one?

There are certain class of directives and we could have different APIs for those (this is what Angular2 is trying to do) but I don't think one class of directives will help a lot here, regardless of its name...

@demisx
demisx commented Jan 22, 2015

@pkozlowski-opensource Agreed. I don't think introducing this in 1.x will add any value.

@thorn0
Contributor
thorn0 commented Jan 22, 2015

I see no harm in expressing explicitly this knowledge about the different kinds of directives in the API of 1.x. This distinction is a really important gotcha about the framework.

@pkozlowski-opensource

@thorn0 the only harm I can see is introducing multiple-way-of-doing-the-same-thing. I'm still astonished how much difficulty people are having with various ways of registering services in DI (.service, .factory, .value etc.).

But this is kind of secondary - the main issues are:

  • what distinct classes of directives we can see (regardless of their names)
  • what are the APIs proposed for those
  • how the proposed API differs from the existing one (conceptual difference and actual difference in the code)

I think that answering the above questions is more important that debating if we want to have it in core or not at this point. If we can clearly identify distinct concepts and find good API for those, I'm all for embedding this in AngularJS. So let's focus the discussion on the concrete proposals.

@ajoslin
Contributor
ajoslin commented Mar 6, 2015

I was trying to get from Angular 1's directive syntax to Angular 2's component syntax in a simple way. The best I could do was this:

app.directive('ngTrademark', function() {
  return {
    restrict: 'E',
    scope: {},
    controller: function() {},
    controllerAs: 'ngTrademark',
    bindToController: {
      version: '='  
    },
    template: 'The version is {{ngTrademark.version}}'
  };
});

This is way too verbose. How about some sugar that will set some defaults for us and pave the way for angular 2 migration:

app.component('ngTrademark', function() {
  return {
    bind: {
      version: '='
    },
    template: 'The version is {{ngTrademark.version}}'
  };
});

And then adding a custom controller for example's sake:

myModule.component('ngTrademark', function() {
  return {
    bind: {
      name: '&'
    },
    controller: TrademarkController,
    template: 'The version is {{ngTrademark.fancyVersion()}}'
  };
  function TrademarkController() {
     this.fancyVersion = function() {
       return '~~' + this.version + '~~';
     };
  }
});

And module.component simply maps to a directive with some overridable defaults:

{ 
  restrict: 'E', 
  controller: function() {}, 
  controllerAs: directiveName, 
  scope: {},
  bindToController: componentDef.bind
}

There could also be a similar function (module.decorator?) for attribute/decorator directives.

Thought of this at ng-conf with @pkozlowski-opensource, @carmenpopoviciu and @mzgol.

@demisx
demisx commented Mar 6, 2015

👍 Nice. I like the bind syntax. The only thing I don't like naming it component. As I've mentioned earlier, all of our aps are divided into components and many of these components are a higher abstraction, then a single directive. We woudn't be able to keep 1-to-1 relationship between components and directives. Our components may or may not include directives and they usually include a bunch of other files, besides directives (e.g. services, providers, templates, configs - just about anything). If you called this a widget, then it may work.

Anyway, just wanted to add my 2 cents and my highly subjective opinion.

@kentcdodds
Member

My alternative highly subjective opinion is I like the term component as I
think it maps well to what the JavaScript community at large is referring
to these things. React has components and there's Web Components. So I
prefer the name component.

  • Kent C. Dodds

(Sent from my mobile device, please forgive typos or brevity)
On Mar 5, 2015 11:31 PM, "Dmitri Moore" notifications@github.com wrote:

[image: 👍] Nice. I like the bind syntax. The only thing I don't like
naming it component. As I've mentioned earlier, all of our aps are
divided into components and many of these components are a higher
abstraction, then a single directive. We woudnt be able to keep 1-to-1
relationship between components and directives. Our components may or may
not include directives and they usually include a bunch of other files,
besides directives (e.g. services, providers, templates, configs - just
about anything). If you called this awidget`, then it may work.

Anyway, just wanted to add my 2 cents and my highly subjective opinion.


Reply to this email directly or view it on GitHub
#10007 (comment)
.

@pkozlowski-opensource

So, there are many "overloaded" words that we use in IT (module, package, component, widget, controller etc.). People use those words to mean different things and will have different mental models, so there will always difference in preferences here. So make the discussion more productive, here is my proposal:

  • let's say that we are going to stick to the same name as Angular2 is using (currently: component)
  • let's re-focus the discussion on the more important part: API and the semantic of this helper. As long as we've got API and semantics right, we can stick any name on it.

How does it sound?

@CarmenPopoviciu

+1 to what @pkozlowski-opensource proposed

@kentcdodds
Member

+1 to focusing on what really matters.

But in all seriousness, could we just have it be angular.igor? I think that would remove the ambiguity entirely :-)

Oh, jk. I really liked @ajoslin's proposal. I think I like using the term bind instead of scope in this context and I'm a fan of (most of) the defaults. (I only say "most of" because I prefer that all of my controllerAs use vm instead of the controller's name or the directive's name because it's easier to switch between templates. But I can deal with having to specify it and not forcing others to bend to my will)

@petebacondarwin
Member

@kentcdodds - Are you implying that @IgorMinar is a component??

Yes, let's move this forward. How about this for a crazy idea? Let's make it even more Angular 2 like and use "annotations" on the controller function:

In atScript/TypeScript it might look like:

@Bind({ name: '&' }),
@Template('The version is {{ngTrademark.fancyVersion()}}')
class Trademark() {
  constructor() {
    this.fancyVersion = () => { return '~~' + this.version + '~~'; };
  }
}

myModule.register('ngTrademark', TradeMark);

In ES5 it might look like:

function Trademark() {
  this.fancyVersion = function() {
    return '~~' + this.version + '~~';
  };
}
Trademark.annotate = [
  new Bind({ name: '&' }),
  new Template('The version is {{ngTrademark.fancyVersion()}}')
]

myModule.register('ngTrademark', TradeMark);
@ajoslin
Contributor
ajoslin commented Mar 6, 2015

@petebacondarwin to clarify, if I wanted to inject $http I would just put it in the constructor?

class Trademark() {
  constructor($http) {
    this.fancyVersion = () => { return '~~' + this.version + '~~'; };
  }
}
myModule.register('ngTrademark', TradeMark);

I'm not sure of this idea of using annotations in angular 1. The main question I have is where do Bind and Template come from?

We could put Bind, Template, etc on the angular object...

@angular.Bind({ name: '&' }),
@angular.Template('The version is {{ngTrademark.fancyVersion()}}')
class Trademark() {
  constructor() {
    this.fancyVersion = () => { return '~~' + this.version + '~~'; };
  }
}
myModule.register('ngTrademark', TradeMark);

But this still leaves me the question: What if I want a property of my component to depend upon an injected dependency? For example, in an angular 1 directive I could do:

app.directive('ngTrademark', function($media) {
  return {
    templateUrl: $media('(min-width: 768px)') ? 
      'desktop-template.html' : 
      'mobile-template.html'
  };
});

This is impossible in Angular 1 if we move these declarations outside of the dependency injection closure.

@kentcdodds
Member

I like the idea of using annotations because it's more angular 2ish, but I
think the insistency with the rest of the app would be confusing enough for
developers that many would not use it IMO.

  • Kent C. Dodds

(Sent from my mobile device, please forgive typos or brevity)
On Mar 6, 2015 7:28 AM, "Andrew Joslin" notifications@github.com wrote:

@petebacondarwin https://github.com/petebacondarwin to clarify, if I
wanted to inject $http I would just put it in the constructor?

class Trademark() {
constructor($http) {
this.fancyVersion = () => { return '' + this.version + ''; };
}
}
myModule.register('ngTrademark', TradeMark);

I'm not sure of this idea of using annotations in angular 1. The main
question I have is where do Bind and Template come from?

We could put Bind, Template, etc on the angular object...

@angular.Bind({ name: '&' }),
@angular.Template('The version is {{ngTrademark.fancyVersion()}}')class Trademark() {
constructor() {
this.fancyVersion = () => { return '' + this.version + ''; };
}
}
myModule.register('ngTrademark', TradeMark);

But this still leaves me the question: What if I want a property of my
component to depend upon an injected dependency? For example, in an angular
1 directive I could do:

app.directive('ngTrademark', function(dep) {
return {
template: dep.name ? 'template1' : 'template2'
};
});

This is impossible in Angular 1 if we move these declarations outside of
the dependency injection closure.


Reply to this email directly or view it on GitHub
#10007 (comment)
.

@pkozlowski-opensource

So, at the moment my personal take would be to steer away from using annotations in Angular 1. The arguments are:

  • it is "non-standard" extension to the language and many people are not comfortable with introducing those
  • it would require a transpilation step with custom processor which means that people can't use "standard" ES6 transpilers
  • I'm not sure that we could annotations syntax to match one of ng2 and if we can't the main advantage of introducing them disappears

IMO the last proposal from @ajoslin paints a very solid picture (hay, look, your component is just a ES6 class and it is going to look the same in ng2 - what changes are just meta-data) while being easy to implement and usable today with almost no overhead for people using this approach.

So I would dig into this initial proposal to see if / when we could ship it and then take it further based on the initial feedback.

@kentcdodds
Member

Just to be sure, you're saying that you liked @ajoslin's ES5 version of annotations right? I'll just throw out an opposing opinion and say I liked @ajoslin's next-to-last proposal which I believe resembles the rest of angular 1.x much more clearly. I just think it would be confusing for developers. But I don't know what the plans are going forward with 1.x. If you're planning on making things more 2.xish then maybe this makes sense.

@ajoslin
Contributor
ajoslin commented Mar 6, 2015

I think @pkozlowski-opensource is saying he likes the original proposal as well. I'm not a fan of the Angular 1 annotations either.

@petebacondarwin
Member

I am not suggesting that we actually start writing code in TypeScript 1.5. but it is true that we already use "annotations" in Angular 1.x: Just think about the $inject property.

@demisx
demisx commented Mar 6, 2015

As a developer, I'm not afraid to start doing things differently in AngularJS 1.x as long as it's closer to Angular 2 way of doing things and will ease the transition to ng2 in the future. I'd rather spread the learning curve over time vs. being bombarded with it when ng2 gets released. We learn new way of doing things almost everyday anyway.

@ajoslin
Contributor
ajoslin commented Mar 7, 2015

Maybe these annotations are a good option. But I still think staying closer
to 1.x is a good idea. The use case I presented is very common.
On Fri, Mar 6, 2015 at 16:44 Dmitri Moore notifications@github.com wrote:

As a developer, I'm not afraid to start doing things differently in
AngularJS 1.x as long as it's closer to Angular 2 way of doing things and
will ease the transition to ng2 in the future. I'd rather spread the
learning curve over time vs. being bombarded with it when ng2 gets
released. We learn new way of doing things almost everyday anyway.


Reply to this email directly or view it on GitHub
#10007 (comment)
.

@gautelo
gautelo commented Mar 8, 2015

I quite like this as a step on the road to v.2.0. And the bind keyword as a shorthand for lots of other properties is a great idea that should make it into .directive() in general by setting certain defaults (for scope, controllerAs, bindToController and restrict). In fact I'm not even sure we need the .component() alias. All we need is to allow bind as a config option in .directive() which when used allow us to ignore a bunch of other options.

With that out of the way, how does injecting required controllers fit into this picture? I see that the linking function isn't part of the component, and that's where I get required controllers. A lot of my directives have some requires, and I think a plan for that should be part of this. Otherwise you will fall way short of the 90% goal stated. Maybe this is actually a 10% type scenario for most folks (?) and I'm just weird, I don't know. (Where did 90% come from anyway? Isn't it usually the 80% one speaks of? Anyway...)

A solution (which would actually solve a lot of pain in general for me) and also allow us to keep ignoring the linking function here would be to allow requires to be injected into the controller and not only into the linking function. I'm sure I've seen discussion along those lines somewhere, but I don't think it's moving along, for some reason. Maybe requires is an antipattern you're trying to move away from, and we should rather do this kind of thing in some other way. Though I don't know how. (If that's the case, please nod me in the right direction.)

@kentcdodds
Member

@gautelo, I would say 90%+ of my directives don't use required controllers (maybe I'm missing out on some way of doing things that simplifies life?)

The idea of just adding something to the DDO makes me shudder. As an instructor, teaching the DDO can be so difficult sometimes. However, I think that using bind to teach the DDO would definitely ease people into it a bit. Outside of teaching, I believe I actually really like that api. It allows for maximum flexibility, so I'm a fan of that. Eager to hear what other's thoughts are on it.

@kentcdodds kentcdodds added a commit to kentcdodds/angular.js that referenced this issue Mar 9, 2015
@kentcdodds kentcdodds An example implementation of .directive `bind`
Based on [this comment](angular#10007 (comment)) by @gautelo.

**Note** This PR was created using the github editor and is expected to be more instructional than anything. Not expected to be merged.
c56d4d8
@kentcdodds
Member

I don't know whether people get an email when a PR is opened on an issue they've commented on so... ping

@jgoux
jgoux commented Mar 9, 2015

+1 for this component feature.
I'm trying to build a new app with ES6, component oriented.
I've two major problems so far :

  • The amount of code to write a simple component is too high
  • Register a directive using a class isn't possible

For the class registration, I use a hack to transform the transpiled class (found here : https://github.com/michaelbromley/angular-es6/blob/master/src/app/utils/register.js).

This is an example of one of my component + the component rewritten with @ajoslin syntax proposal : https://gist.github.com/jgoux/cdcfb9657ee05d282ee3

EDIT : If you don't want to introduce annotation, what about using some mecanism like we do with $inject ? It allows to extract the component class configuration and focus on its behaviour : https://gist.github.com/jgoux/31409b6a3bef009ec199 (no more controller as all the logic is in the component class)

@btford btford referenced this issue in angular/router Mar 9, 2015
Open

feat(1.x): support routing to a directive #161

@btford btford assigned btford and unassigned IgorMinar Mar 9, 2015
@btford
Contributor
btford commented Mar 9, 2015

I think this API is worthwhile because it could help make concepts in the new router easier to understand.

I'm going to take ownership of this issue. My next step is going to be to review all of the different proposals and summarize them.

@kentcdodds
Member

Hadn't thought about it in context of the new router. Makes a lot of sense. In that case, I vote that we drop the overloading the DDO approach (as illustrated in my PR) and stick with the component approach. I also upvote @ajoslin's original suggestion for the API:

myModule.component('ngTrademark', function() {
  return {
    bind: {
      name: '&'
    },
    controller: TrademarkController,
    template: 'The version is {{ngTrademark.fancyVersion()}}'
  };
  function TrademarkController() {
     this.fancyVersion = function() {
       return '~~' + this.version + '~~';
     };
  }
});

Where you can override defaults very easily via:

myModule.component('ngTrademark', function() {
  return {
    bind: {
      name: '&'
    },
    controller: TrademarkController,
    controllerAs: 'vm', // <-- this would override the default of 'ngTrandemark' (component name)
    template: 'The version is {{ngTrademark.fancyVersion()}}'
  };
  function TrademarkController() {
     this.fancyVersion = function() {
       return '~~' + this.version + '~~';
     };
  }
});

So this would actually be very similar to the suggested PR, except instead of using the directive function it would be a component function that maybe invokes the directive function with some defaults.

@hannahhoward
Contributor

If you would like to use annotations but want to keep that out of the angular core, I have a library for using annotation syntax with angular 1.x, if this feature moves forward, I'd be happy to add the annotations for component to this library: https://github.com/hannahhoward/a1atscript

@btford
Contributor
btford commented Mar 10, 2015

This is very cool! Was thinking of doing something similar myself.

On Mon, Mar 9, 2015 at 5:25 PM Hannah Howard notifications@github.com
wrote:

If you would like to use annotations but want to keep that out of the
angular core, I have a library for using annotation syntax with angular
1.x, if this feature moves forward, I'd be happy to add the annotations for
component to this library: https://github.com/hannahhoward/a1atscript


Reply to this email directly or view it on GitHub
#10007 (comment)
.

@gdi2290
Member
gdi2290 commented Mar 10, 2015

+1 I'm with @kentcdodds and @ajoslin on component name, maintaining consistency with ng1 api (even if it's bad), while not adding anything new other than sugar with overwriting defaults

myModule.component('ngTrademark', function() {
  return {
    bind: {
      name: '&'
    },
    controller: TrademarkController,
    template: 'The version is {{ ngTrademark.fancyVersion() }}'
  };
  function TrademarkController() {
     this.fancyVersion = function() {
       return '~~' + this.version + '~~';
     };
  }
});

Just for fun, another hack for ng2 style api in ng1 would be a double annotate and inject ;)

myModule.component('ngTrademark', function(Template, Component) {
  return [
    Component({
      bind: {
        name: '&'
      }
    }),
    Template({
      inline: 'The version is {{ ngTrademark.fancyVersion() }}'
    }),
    // class works too
    function TrademarkController() {
      this.fancyVersion = function() {
        return '~~' + this.version + '~~';
      };
    }
  ];

});
@SanderElias
Member

To wrap my head around this discussion I wrote a small plunk
While I was creating this, It hit me that I didn't like the proposal in here, I think its still way to verbose.. NG2 will have different kind of directives, so i decided to create a Component that just creates an templateDirective as a starter.
In my eye's this a abstraction that is easier to grok, as somewhat simpler DDO provided by the bespoken component extension.
beside the templateDirective, there should also be an Decorator. That would do the same for simple behaviors. Anything more fancy should use the normal directives.
Of coarse, the intergration into NG should be way better then my crude experiment.

Or did I miss something obvious in this thread?
EDIT: moved to the NG2 naming conventions.

@pkozlowski-opensource

@SanderElias templateDirective and behaviorDirective would introduce new terminology. NG2 has @Component and @Decorator annotations and I think we should use those names. Also please check my previous comment on the topic.

@SanderElias
Member

@pkozlowski-opensource, You are right. I'll updtae the plunker, and my previous comment.

@jwcarroll
Contributor

I actually really like this idea. Pulling this into TypeScript is pretty simple, and would make the transition to Angular2 that much easier. The only thing that would need to change is the actual DDO and pretty much everything else stays the same.

With the right tooling, you could even automate this.

//*** Begin actual TypeScript component ***
//
// Class is outside of function at the moment
// because TypeScript currently doesn't support "local" types,
// but will in the future. Could use a junk "module"
//
// see: https://github.com/Microsoft/TypeScript/issues/206#issuecomment-56220911 
class TrademarkController {
 version:string;

 fancyVersion() {
   return '~~' + this.version + '~~';
 }
}

myModule.component('ngTrademark', function() {
  return {
    bind: {
      name: '&'
    },
    controller: TrademarkController,
    template: 'The version is {{ ngTrademark.fancyVersion() }}'
  };
});

The basic interfaces in TypeScript would be pretty simple as well:

interface IComponentDefinition {
    bind: IComponentBinding;
    controller: string|any[]|Function;
    template?:string;
    templateUrl?:string;
}

interface IComponentBinding {
    [index:string]:string
}

type ComponentDefinitionFactory = (...deps:any[]) => IComponentDefinition;

interface IModule {
    component: (componentName:string, factory:ComponentDefinitionFactory) => void;
}

Just in case anybody wants to see what this would feel like in TypeScript, here is the above example in the TypeScript Playground

@SanderElias
Member

Updated my plunk to allow a version that works with the es5 substitute of annotations. It does work. It also shows that the injector approach is a lot more verbose as the simple function wrapper. I like the simplicity of the function call, and at the same time, I like the added flexibility of the annotations.
However, in my opinion the function calls wins. It's less to code, and less to grok. It is also easier to teach to a new developer. That should be the main concern for any api, right?

@ajoslin
Contributor
ajoslin commented Mar 10, 2015

@gdi2290 interesting idea with annotations as angular 1 injections! I think I like it, but I'm not sure yet.

So as far as I can see, we all agree that .component() as a simple sugar for .directive() is a good idea.

I'll open a PR for this as soon as I can. EDIT: I missed @btford's comment about taking ownership. Brian, sounds like you have this under wraps.

The other question is do we add annotations? I think this is a separate discussion to have. It's a good discussion to have, but in the meantime we should move forward with .component().

@robianmcd

I also have a library for doing angular 2 style directives in Angular 1 if anyone wants to play around with it https://github.com/robianmcd/angular-next . It also lets you do angualr 2 style modules, services, injection, etc. And I'm working on supporting a subset of Angular 2 style templates

@petebacondarwin
Member

I think we are agreed that the way to go is with the simple new method(s) on modules. A possible clarifying naming could be module.componentDirective(...), which would also allow a future module.decoratorDirective(...) method.

Regarding annotations: They are cool but add unnecessary mental overhead to the current Angular 1 project. That being said, I could imagine an additional project or module providing support for this separately. So people could opt-in to annotating if they are already using TypeScript anyway.

@kentcdodds
Member

👍 to everything @petebacondarwin said. I actually really like the idea of componentDirective because it makes the black box a little less black boxy which I think is a good thing :-)

@gdi2290
Member
gdi2290 commented Mar 11, 2015

.componentDirective is more clear since it's implying it's a directive. The angular2 version inherits from Directive base class so the name transfers over well

@gkalpak
Member
gkalpak commented Mar 11, 2015

I hope it's not too late to join the party here, but I would like to share some (random) thoughts:

  • It's not a big deal, but I think we shouldn't have illusions of angular.component/xyz() in any way simplifying the DDO. It will just be one extra thing to learn, it will have a "dependency" angular.directive() (so you have to understand the latter in order to (fully) get the former) and it will mainly be a helper for more "seasoned" developers (which is fine by me; just saying).
  • Since it seems that we have decided to go down this path, why not push it a little further:
    Basically, a componentDirective or a decoratorDirective are plain directives with presets, which aim to avoid boilerplate. And while we can generally agree what works for most people and come up with a scheme that does almost what we want, so we just have to tweak only a few properties (e.g. controllerAs: 'whatEver'), I believe it would make more sense to actually provide a way of creating arbitrary "named" presets for directives. Let me explain with some code:
// We introduce a way to register "directive presets" (e.g. a new method)
angular.directivePreset('component', {
  restrict: 'E'
  scope: {},
  controllerAs: 'vm',   // or maybe a function that gets passed the directive name
                        // as an argument and returns that as the controller's alias
  bindToController: true,
  ...
});

angular.directivePreset('decorator', {
  restrict: 'A',
  scope: false,
  ...
});

// Then we can (optionally) pass an extra argument into the `directive` function,
// specifying the preset to be used as base for constructing the DDO
angular.directive('component', 'myCoolComponent', function myCoolComponentDirective() {
  return {
    controller: MyCoolComponentCtrl
    controllerAs: 'ctrl'
  };
});

In the above example, the DDO for the myCoolComponent directive will be constructed by extending (a copy of) the "component" preset and the DDO defined with angular.directive.

The main benefits are that:

  1. People can tweak the presets to exactly much their preferred style.
  2. People can register as many custom presets as they like.

Angular can define it's own "default" presets (e.g. "component" and "decorator").
It can even go as far as aliasing angular.directive('component', ...) as angular.componentDirective(...) etc.


I understand there are some "issues"/rough edges to be worked out, but I believe this is a viable approach.
@pkozlowski-opensource

@gkalpak your proposal would open doors to "many ways of doing the same thing" while what we want to achieve here is "do things as they are going to look in ng2".

@kentcdodds
Member

Interesting concept, but there's a fatal flaw in that let's say I'm a library author and I create a preset, then someone else comes along and creates a different preset with the same name, what do we do in that scenario? It would be unpredictable.

@gkalpak
Member
gkalpak commented Mar 11, 2015

@pkozlowski-opensource: angular.xyz() does already opens a door to "many ways of doing the same thing" (even if the justification reads "enable people to do things as they are going to look in ng2").
I am afraid that ng1 is not yet ready (will it ever be ?) to make things similar to how they are going to be done in ng2 and just naming something component or decorator won't make it more or less ng2-ish than naming it directive with controllerAs and bindToController.

I would very much like to be able to do things in a more ng2-ish way in ng1, but I am afraid we are focusing more on the naming of stuff and not on the semantics. Just being sceptical; might as well be wrong.
In any way, from what I understand, angular.xyz() will not add any new functionality or semantics, it will just be a helper function, not any different than a helper someone could implement outside of the core.
(Not sure how the new router changes the picture in respect with that, though.)

@kentcdodds: Is that any different than you creating a service in your module and me creating another service with the same name in mine ?


Anyway, thinking about it again, I don't expect my suggestion to make it to the core, as it adds more complexity to an already complex concept.
The main point is: Is the angular.component/whatever() feature making things easier or messier and is it really worth the trouble (i.e. is it going to ease migration to ng2 more than it will cause confusion for (new) ng1 users) ?
(I don't claim to know the answer; been wondering myself.)

@caitp
Contributor
caitp commented Mar 11, 2015

SO, yes, angular 1.x has had kind of a bad wrap of letting you do lots of different things in lots of different ways, with no clear reason to pick one or the other (or at least, no clear reason which is obvious to most developers). People are confused about service vs provider vs factory, confused about transclusion, confused about many core filters and directives.

But, I think it might be okay to include a .component() helper if there's a clear reason to prefer it over the ordinary directive API. EG, if it enables features that didn't exist previously while still allowing the legacy API to function (a la CreateWindowEx() vs CreateWindow() in win32 land), or if it significantly simplifies api for common use-cases.

I don't think it's something to do just on a whim though, really need to figure out if it actually achieves the goals of 1) looking "more angular2-ish", 2) simplifying the api for common use cases, and 3) enables new functionality that wasn't there in the legacy API

@gkalpak
Member
gkalpak commented Mar 11, 2015

Another idea:

I realized that we have 2 (to some extent contradicting) goals in ng1:

  1. Make it easy for developers not familiar with the framework to jump onto the Angular train.
  2. Make it easy for more advanced developers to prepare for their migration to ng2.

Since the means for achieving these two goals are probably mutually exclusive (i.e. what serves one goal will most probably hurt the other and vice versa) and since they are addressing two distinct groups of people, how about this:

  1. We try to keep improving ng1 (as the team has been doing constantly all these years) in an ng1-ish way, not being carried away by the ng2 hive and the "preparation for migration" fever, thus avoiding perplexing things for newcomers.
  2. Have an external module, targeting more advanced users, that adds functionality dedicated to easying migration to ng2 (i.e. introducing helpers and decorators and stuff that will allow people to write ng2-proof code).
@johnlindquist
Member

Just frame the messaging "The new router is a 'component' router, we've added an easier way to make components to support it" then release the same time as router 1.0. I'm happy to just let @btford take care of it since everyone is just arguing syntax and naming now. I don't really care as long as the intent of easily building a component with a template and a controller is there.

@mgol
Member
mgol commented Mar 11, 2015

@gkalpak

I would very much like to be able to do things in a more ng2-ish way in ng1, but I am afraid we are focusing more on the naming of stuff and not on the semantics.

Sure, the component helper will not in any way create a new semantics that would aid the migration to ng2. That doesn't mean nothing is being done! controllerAs was one semantics addition that takes us closer to ng2, bindToController is another. For me the module.componentDirective's (or whatever it's named) main goal is to send a clear message to Angular users that this is a good way to write code and that it'll get easier to migrate. If you just say to people to use:

app.directive('ngTrademark', function() {
  return {
    restrict: 'E',
    scope: {},
    controller: function() {},
    controllerAs: 'ngTrademark',
    bindToController: {
      version: '='  
    },
    template: 'The version is {{ngTrademark.version}}'
  };
});

to prepare for migration, they might be afraid they're going into an arcane direction that is not sanctioned in any way, that they'll end up in a dark unsupported corner that no one will no about.

People expect to be able to get to Stack Overflow & get help. The above looks like not something that would be commonly used which in turn decreases the number of people being able to help. Creating an official wrapper-helper will send a clear message this is a good way to write code.

So yes, while it doesn't introduce any new semantics, I consider such sanctioned wrappers to be extremely valuable. At the same time, if there are any doubts about how exactly it should work I'm fine with deferring it to 1.5.

I support the proposal that @ajoslin, @CarmenPopoviciu, @pkozlowski-opensource & me authored, i.e. the one from comment #10007 (comment).

@demisx
demisx commented Mar 11, 2015

👍 for module.componentDirective over module.component. Makes it more clear what it is. Though, I am fine with the current DDO and find it pretty flexible once you're passed the learning curve. So, not sure we'll be have a real need to use this new componentDirective preset at all. Maybe people new to Angular will find it useful. Don't know - we'd have to see. As long as the current DDO is still being supported.

@gkalpak
Member
gkalpak commented Mar 11, 2015

@mzgol: Just to make sure I was clear: There is no doubt a helper like the one proposed would be very helpful for people looking into ways to prepare for ng2 (for the reasons you described above).

My concern (and again: it's just a concern) is that people that:

  1. Just because someone uses angular.xyzDirective(), it doesn't mean that they had a component (in the ng2-ish sense of the term) or that their migration would be easier. They have to use it correctly.
  2. In order to use angular.xyzDirective() correctly, one should have a fair understanding of what is going on behind the scenes (i.e. how the DDO works).
  3. angular.xyzDirective() is basically targeted to people "knowing what they are doing", which is exactly the people that already know how to properly define a component to be ng2-proof, which is exactly the people that could (if not already done so) create such helpers themselves (even better, because they can configure them to suit their needs, instead of having to overwrite the angular defined one).
  4. For people new to Angular (i.e. "not knowing what their doing yet"), angular.xyzDirective() would be either irrelevant or another way to "make mistakes", because for it to be useful, one has to understand the concept of a component (which I don't think is even crystallized yet), know a bit about what ng2 will look like, understand what all these "hidden" predefined options of the DDO do etc.

So, bottom-line:
This feature would be potentially helpful for a certain group of people (potentially because they might already know how to do things the "right way") and it would be potentially confusing/harmful for a certain, different group of people (new to Angular; harmful in the sense that it will add to the learning curve (and the frustration that comes along) and provide new ground for making mistakes).

I am trying to weight these two aspects. Or maybe I being too short-isioned and missing a bigger picture. Anyone's insights are most welcome :)

@jwcarroll
Contributor

@gkalpak I don't want to take this thread too far off track, but you could argue the same thing for the introduction of the controller as syntax.

Before that came out, people who "knew what they were doing" were already doing something similar by just assigning the entire controller to scope using $scope.ctrl = this;.

Instead of thinking of it being something new, think of it as simply codifying an accepted good pattern. Doing this gives it more credibility.

Telling people to "prefer using .component()" is no different than telling people to prefer using controller as. As @johnlindquist already mentioned, it's all about framing the argument, as it would seem nearly everyone here is in agreement that adding a .component() method as syntactic sugar over .directive() is highly desirable.

@kylecordes

This thread is getting very long, but I suppose it doesn't matter making it slightly longer. I would like to weigh in in support of this idea also. I believe that having a mechanism to specifically construct component directives and decorator directives would be an excellent step forward for 1.x; it both guides developers down good paths now, and prepares them more for A2.

I think it is also wise to direct that for directives which follow these main "roles", the standard practice should be to use these specialized constructors, leaving that the generic directive constructor for edge cases that don't fit in the typical roles. This again makes better code under 1.x and an easier transition to A2.

The argument the other way, that it is not necessary, is similar to arguing that .factory .service etc are not necessary, you could just use .provider; you could, but it is better to have the more specific things. Perhaps in 1.5 we could think of .directive the same way as we think of .provider now, a low-level mechanism to do edge case things that don't fit in the mainstream high-level mechanism.

@hannahhoward
Contributor

Hey. Not sure when this will land, but if you'd like to try components in 1.x right now, I've added support in A1AtScript

@Component({
  selector: "awesome",
  bind: {
    apple: "apple"
  },
  services: ["ExampleService"]
})
@Template({
  url: "awesome.tpl.html"
})
class AwesomeComponent {
  constructor(exampleService) {
    this.exampleService = exampleService;
    this.test = "test";
  }
  setValue() {
    this.value = this.exampleService.value;
  }
}
<awesome apple="stringLiteral"></awesome>
<awesome bind-apple="expression"></awesome>

It'll normally be restrict: 'E', but [directiveName] will convert it to restrict: 'A' and .directiveName will make it restrict: 'C'

Also, there no @ or = on bind. Instead, it will setup two properties -- attr-name for string literals and bind-attr-name for expressions -- to match NG2's style

Supported in 1.3+

@gdi2290
Member
gdi2290 commented Mar 13, 2015

@hannahhoward you can probably support the pipe bindings that are in ng2 with ng1 filters.

@Component({
  selector: "awesome",
  bind: {
    apple: "apple | uppercase"
  },
  services: ["ExampleService"]
})
@Template({
  url: "awesome.tpl.html"
})
class AwesomeComponent {
  constructor(exampleService) {
    this.exampleService = exampleService;
    this.test = "test";
  }
  setValue() {
    this.value = this.exampleService.value;
  }
}
@LeonardoBraga
Contributor

I'd like to suggest an approach very close to Angular 2.0, still 100% compatible with 1.X. The helpers Module, Template, Component and Class would allow the following syntax:

Module({
  name: 'ng2Helpers'
})
.Template({
  inline: 'Cool {{ dirA.stuff }}'
})
.Component({
  selector: 'dirA'
})
.Class(function dirA() {
  this.stuff = 'stuff';
});

It's implemented here: http://plnkr.co/edit/UR75ioZK0xb6TbSL6zwq?p=preview

I refrained from introducing many of the new concepts into the DDO, but the idea is that we can get as close as 2.0 as we consider useful and safe or keep the exact same DDO, still having a 2.0ish syntax. I can elaborate the concept (and add unit tests) if the feedback is positive.

@pauleveritt

I watched the new router presentation @btford and noted its use of the word "component". I don't think he means "directive", whereas this proposal's angular.component does indeed mean directive.

I worry about this skew. Going forward, does the word "component" in Angular mean "Web Component" which means "directive" in Angular1? Or can their be headless components? Does New Router intend for its components to be thought of, semantically, as Web Components?

@petebacondarwin
Member

There is a lot of discussion about this feature. Since we don't want to rush into things, or delay the release of 1.4.0, we are going to move this to the 1.5 milestone, which actually fits well with its migration-assistance theme.

@ewinslow
Contributor

1.5 sounds like a great milestone for this.

I'm surprised no one has mentioned the scope object. It is magic symbols. I can never remember them. I can never remember the difference between the keys and values. People arbitrarily decide between interpolation (@) and expressions (=).

Misko made it pretty clear at ng-conf that components have 3 APIs: events, properties, and methods. Let's just codify that and get rid of the crazy scope object, yea?

angular.component('thing', {
  controller: ThingCtrl,
  deps: [...], // encourage people to list deps explicitly, similar to ng2
  events: ['foo'],
  properties: ['bar'],
  template: '<div>Bar is: {{ctrl.bar}}<button ng-click="ctrl.triggerFoo()">Trigger foo event</button></div>', 
});

Usage looks like

<thing on-foo="doSomething()" bind-bar="toSomething()">
  Optionally transcluded content...
</thing>

The definition above "desugars" to something like:

// NB: creates a new module!
angular.module('thingComponent', [...]).directive('thing', function() {
  return {
    restrict: 'E',
    replace: false,
    // Had to look up this API again to get it right, which kind of makes my point...
    scope: {
      'triggerFoo': '&onFoo', // Prefix events to make it clear that this is an event
      'bar': '=bindBar', // Prefix properties to make it clear that this is an expression
    },
    controller: ThingCtrl,
    controllerAs: 'ctrl', // There is no point in customizing the name. It is in an isolate scope
    transclude: true, // If you don't need this, just don't put ng-transclude in your template
    templateUrl: '/path/to/the/url.ng',
    bindToController: true, 
  }
})
@wesleycho
Contributor

I mentioned this sort of idea to @lgalfaso during ng-conf without knowing of this issue, and I really like this idea to unify ideas and make migration now easier.

Syntactically, I would like an api set that behaves similar to Angular 2 and its @Component/@Template annotations, perhaps even identical names. This would ease migration since one would then be able to largely pick up their directives and migrate over with minor syntax changes without much pain.

I would also like the possibility of using the content tag to specify transclusion in a template as well, similar to how Web Components handles it. Multiple content tags bound to selectors would be nice too, but probably not worth a high priority.

@LeonardoBraga
Contributor

I created a repo with a set of helpers to bring a similar syntax into 1.3+: https://github.com/LeonardoBraga/ng2-helpers

I plan on finishing the tests cases this weekend, but the library should be functional already.

I got to read more about @ewinslow points on properties and events, because they can be easily included too.

@Hendrixer
Contributor

@ewinslow I like this. The scope object is definitely a hurdle when creating directives today. Making it easier is a win and making it more like ng2 is a icing on the cake.

@maseh87
maseh87 commented Apr 6, 2015

I have been working on an easier way to create your directives and it is a lot like the way ng2 is going to soon. https://github.com/maseh87/ngComponent

@drpicox
Contributor
drpicox commented Apr 14, 2015

More alternatives: https://github.com/ngtags/ngtags/blob/master/examples/index.md
And it will be 100% cooler when bindToController: true can be replaced by bindToController: { ... }

About controllerAs: naming, I guess that John Papa convention of using vm it would be handy for components, but not for decorators.

@btford btford referenced this issue in MikeRyan52/angular-decorators May 19, 2015
Closed

Join forces with similar projects? #2

@armorik83

Nice to meet you. I rushed to hear the topic of "Join forces with similar projects?".
My only one prayer is that the transition to the Angular 2 goes smoothly.

I have embarked on the development of the library called Toccata.

https://github.com/crescware/toccata

import {toccata as toccata_} from 'toccata';
import * as angular from 'angular'; // AngularJS 1.x
// or 
// import * as angular2 from 'angular2/angular2'; // Angular 2

const toccata = toccata_(angular);
// or 
// const toccata = toccata_(angular2);

const {Component, View, bootstrap} = toccata;

// The syntax such as Angular 2 in AngularJS 1.x
@Component({
  selector: 'hello'
})
@View({
  template: `<p>Hello!</p>`
})
class HelloController {
  constructor() {
    // noop
  }
}

bootstrap(HelloController);

This can be described to simplify the AngularJS 1.x by decorators syntax, however It seems people are many which are thinking the same ideas.
If possible I want to make a lot of feedback to Angular 2 it out. If the force of transition library of project is to gather, I will not overdo the development of its own library.

Rather than that, I am frustrated that it is difficult to do significantly the development of Angular 2. My wish is that you want to be able to develop in major ES6 environments, such as Babel and Browserify!

@btford
Contributor
btford commented May 20, 2015

Thanks for weighing in @armorik83. This is an important topic, so I'm glad to have your input and expertise as well. I realize it must have been exhausting to read this whole thread. Thanks for your time!

As for improving the development experience in Angular 2: completely agreed. We're still working hard on this. We've had some conversations with the Babel team. We definitely want Babel and Angular 2 to work better together.

If you encounter specific problems, and it's not too much trouble, feel free to file issues on github.com/angular/angular. Even just a stack trace or short reproduction is valuable.

@armorik83

Glad! I'm looking forward. thanks.

@MikeRyan52
Member

Like @armorik83 and a few others who have contributed to this issue I have also been working on a library that implements Angular 2 style components for Angular 1: https://github.com/mikeryan52/angular-decorators. Brief example of what using it looks like:

import {Component, Template, Inject, Module} from 'angular-decorators';

@Component({ selector : 'my-component' })
@Template({ url : '/path/to/template.html' })
@Inject('$timeout')
class MyComponentCtrl{
   constructor($timeout){ ... }
}

export default Module('my-module', []).add(MyComponentCtrl);

I've been using angular-decorators on a large project with @brandonroberts and it has definitely been to our benefit. Like @ajoslin, we were trying to get our components to be as similar to Angular 2's as possible but found that we had to be way too verbose without a helper library like angular-decorators.

With that being said if something like this were to be brought into the core of Angular 1 I'd push to make sure that using the decorator syntax in ES5 is as painless as possible. I like the proposals made by @LeonardoBraga and @gdi2290 where either an array or fluent style API exists for achieving decorator-like syntax in ES5. You could also simulate decorators with a config object:

angular.module('my-module', []).add({
      Component : { selector : 'simple-selector' },
      View : { url : '/path/to/template.html' },
      Transclude : true
   },
   function MyComponentCtrl($interval){ ... }
);

Internally Angular could map the keys of the config object to decorators and apply them to the controller function. This would also make it so that in an ES next environment you could just import the decorators and apply them yourself:

import angular from 'angular';
import {Component, View, Transclude} from 'angular/decorators';

@Component({ selector : 'simple-selector' })
@View({ url : '/path/to/template.html' })
@Transclude
class MyComponentCtrl{
   constructor($inteval){ ... }
}

export default angular.module('my-module', []).add(MyComponentCtrl);

One important caveat regarding the above approach is that it doesn't have a great way of letting the developer access the link function. This is solved in angular-decorators by letting you add a static method to your class:

import {Component, Require} from 'angular-decorators';

@Component({ selector : 'gallery-panel' })
@Require('^gallery', 'galleryPanel')
class GalleryPanelCtrl{
   static link(scope, element, attrs, requires){
      let [gallery, galleryPanel] = requires;
      gallery.add(galleryPanel);
   }
}

I don't think having a static link method is a good idea, however, because the ES5 equivalent is not nearly as clean and ruins the decorator simulation:

function GalleryPanelCtrl(){ ... }
GalleryPanelCtrl.link = function(scope, element, attrs, requires){ ... };

angular.module('my-module', []).add({
   Component : { selector : 'gallery-panel' },
   Require : ['^gallery', 'galleryPanel']
}, GalleryPanelCtrl);

This could be solved by letting you inject required controllers into your component's controller (see issue #5893). You could also create a Link decorator, though it may not look great for really complex linking functions. While I agree with @kentcdodds that using the require feature of the DDO isn't very common, it is really helpful when building complex components like galleries that require some kind of cross component linking.

@timkindberg timkindberg referenced this issue in angular-ui/ui-router May 27, 2015
Closed

Feature: Route to Component Directive #1989

@cmelion
cmelion commented Jun 1, 2015

+1 @jwcarroll - "As @johnlindquist already mentioned, it's all about framing the argument, as it would seem nearly everyone here is in agreement that adding a .component() method as syntactic sugar over .directive() is highly desirable."

I've been using a form of the .component syntactic sugar on a couple projects over the last year or two and it's certainly helped my teams organize our applications as well as become more comfortable with transitioning to Angular2.

https://github.com/cmelion/generator-angular-cmelion/blob/master/README.md#component

@timjacobi

My proposal

  • Use class instead of DDO
  • Use bind instead of bindToController
  • controllerAs defaults to "model"
  • restrict defaults to "EA"
  • scope defaults to {} (isolate scope)
  • Components are registered using the .component method on an angular module instance
  • selector is inferred from class name
class MyComponent {
  this.bind = {
    val: '='
  };

  this.controller = MyComponentController;

  this.template = `
    <div>
      From HTML element: {{ model.val }}
      From controller: {{ model.fromCtrl }}
    </div>
  `;
}

class MyComponentController {
  constructor(){
    this.fromCtrl = "x";
  }
}

angular.module('myApp', []).component(MyComponent);
<div my-component val="somevalue"> </div>

<my-component val="somevalue"> </my-component>
@pbastowski
Member

@MikeRyan52 Hi Mike. Specifically regarding the "link" function issue you were talking about, see this: https://github.com/pbastowski/angular2-now#how-do-i-access-ngmodel-and-other-components-controllers
I also have a much cleaner solution, where you can inject other component's controllers directly into your constructor, but it requires disabling Babel's "_classCallCheck".

@hannahhoward I like your work :)

@petebacondarwin
Member

@caitp has a PR for this here: #12166
Please take a look

@ChristianKohler
Contributor

I really like the idea of decorating the directive controller. It would be the same syntax like $inject.

ExampleController.$inject = ['$http']

So when you want to define a template it would look like this:

ExampleController.$view = { template: '<div>Hello World</div>' }

The component factory method would then create the ddo based on the decorations. This could be part of angular core. The annotations should be in an other library.

Here is an example how it could look with and without decorators:

http://jsbin.com/riveqo/edit?html,js,output

@nfantone
nfantone commented Jul 3, 2015

Like the idea about replicating the $inject syntax. It's an already existing API that may very well suit these needs.

@timjacobi

I don't like it, sorry. Why not use something that is similar to the directive syntax, just simplified. Makes it easier to recognize the pattern.

@ChristianKohler
Contributor

Because we want to move away from the ddo syntax and move to a syntax closer to angular 2.

We'll always have the ddo syntax in Angular 1.x and it doesn't make much sense to just simplify the ddo which would make it only more complicated for beginners (like factory vs. service). The main reason imho for this feature is to align with angular 2.

I think most of the people would use it in combination with ES7/Typescript decorators which would make a component almost angular 1/2 agnostic :-)

@caitp
Contributor
caitp commented Jul 3, 2015

well, you'd need the same decorators exposed by Angular2 for that, and who knows if those are going to change more than they already have or not. Matching the API 100% is very difficult because angular2 is still sort of in flux

@DavidSouther
Contributor

@caitp it's looking less and less in flux every release. Exposing decorators with as similar an API as possible will ease migration. Though the decorators in 1.x should not land until after 2.0 has stabilized.

@caitp
Contributor
caitp commented Jul 3, 2015

I'm skeptical, we've historically been pretty bad at api stability, but especially during the beta cycle I would not want to expose these

@pbastowski
Member

Guys, what do you think of decorator libraries, like my angular2-now library for Angular 1: https://github.com/pbastowski/angular2-now

It gives Angular 1the current Angular 2 component syntax, as shown below. Is this something that you would be interested in or am I completely off the topic here?

import {SetModule, Component, State, View, Inject, bootstrap, Filter, Options} from 'angular2now';

@State({ name: 'root', url: '', abstract: true, html5Mode: false })

@Component({ selector: 'my-app' })
@View({ templateUrl: 'client/app/app.html' })
@Inject(['$rootScope', '$state', '$location', 'helpers'])

class ftDesiree {
    constructor($rootScope, $state, $location, helpers) {
        ...
    }
}

Even if Angular 2 changes its API somewhat, this is still much nicer than the Angular 1 DDO, etc, right?

@caitp
Contributor
caitp commented Jul 3, 2015

it's pretty much identical to the DDO when you get down to it

@pbastowski
Member

Along the same lines of thinking, it's also pretty much identical to Angular 2 as it currently stands.

So, are we looking for something much simpler than this even?

@awerlang
awerlang commented Jul 3, 2015

I don't believe in something being new/different as to being nicer.

Shall we rewrite our directives to be more Angular2-like so we'll have a little less work when/if (emphasis on if) porting to the some future major release? Do we have any garantees of that work? Is this an effort we wish to be in? Shall this in any case put a constraint in Angular2 design?

As a sidenote, angularjs.org still doesn't feature controllerAs, will it feature a new syntax to something that can be achieved already?

Just take the best out of Angular 1 while you can. Stop trying to fulfill your programmers' egos, many trying to market your own extension, no one really willing to join efforts, e.g. as soon as Angular2 gets out, all these are past.

Didn't want to look harsh, no offence intended in case you feel like that.

@pbastowski
Member

@awerlang No offence :) There is nothing wrong with Angular 1 syntax. If you like it then keep on using it.

However, these are not dreams or egos. My team is using the decorator library for developing production apps for our customer. There are several other libraries that do the same or similar job and some of them are also used by various people for building production systems.

It's good to have choice ;)

@caitp
Contributor
caitp commented Jul 3, 2015

arguably, it's actually not very good to have a choice. It creates multiple ways to do a given thing, and that's generally not very good for an API (leads to confusion). This is a problem with the angular.component helper itself, but I think we've sort of let it slide this time? The point of the angular.component helper isn't really a "make migration to angular2 easier" or "make angular1 look like angular2" --- it's really just for making the common use case that a lot of people following john papa's style a lot easier to write.

We've discussed this, and we don't actually want to make angular1 look like angular2, because this will effectively make migration more difficult, not easier. The approach we're leaning on for migration right now is to support running angular1 stuff in angular2, and vice versa (I do not speak for Herr AngularTeam, but this is basically the conclusion you get from reading the plans)

@pbastowski
Member

@caitp Ok. Good to know.

@petebacondarwin
Member

Thanks everyone who is showing an interest in this issue. We are keen to get some form of this into Angular 1.5. As is pointed out, the primary reason is to make it easier to write the most common form of directive; but there are some upgrade side-effects too.

This component style of writing directives is certainly closer conceptually to components in Angular 2, and while I am totally in agreement that we should not be trying to sugar the Angular 1 syntax to make it look superficially like that of Angular 2 the benefits of writing components in this way will lead to easier maintenance and cleaner code.

It is worth linking in here the team's document on proposed 1->2 upgrade strategies.
https://docs.google.com/document/d/1xvBZoFuNq9hsgRhPPZOJC-Z48AHEbIBPlOCBTSD8m0Y/edit
This is still a WIP progress but you can see the directions that we are considering. Feel free to comment if you would like to contribute to the discussion.

@orizens
orizens commented Aug 23, 2015

hi.
I just saw this thread.
a couple of months ago, after reading pascal precht post about using angular 2 with ES5, I created angular2to1 npm module in order to experiment with the new component syntax.
Up to now, I implemented some of these annotations: Component, View, Class in its simple form. Still, a lot of work to do, however, it gave me the oppoertunity to explore some of angular 2 syntax and concepts whle working in angular 1.
Currently, I'm using it in my open source project Echoes Player, i.e in the "youtube-videos" component.
Do you think (as i do) that its a module that will give benefits to others?

@petebacondarwin
Member

@orizens You should be getting involved with https://github.com/ngUpgraders. This is the team that are working towards a single solution for this, which has the approval of the Angular team.

@btford btford added a commit to btford/angular.js that referenced this issue Sep 21, 2015
@btford btford wip: module.component helper
Closes #10007
6fe7ce5
@petebacondarwin petebacondarwin assigned shahata and unassigned btford Sep 22, 2015
@shahata shahata added a commit to shahata/angular.js that referenced this issue Sep 24, 2015
@shahata shahata feat(loader): add convenience method for creating components
Closes #10007
3f2ed72
@shahata shahata added a commit to shahata/angular.js that referenced this issue Oct 7, 2015
@shahata shahata feat(loader): add convenience method for creating components
Closes #10007
3acb01f
@shahata shahata added a commit to shahata/angular.js that referenced this issue Oct 7, 2015
@shahata shahata feat(loader): add convenience method for creating components
Closes #10007
b03fa5d
@shahata shahata added a commit to shahata/angular.js that referenced this issue Oct 13, 2015
@shahata shahata feat(loader): add convenience method for creating components
Closes #10007
292b37d
@shahata shahata added a commit that closed this issue Oct 29, 2015
@shahata @petebacondarwin shahata + petebacondarwin feat(Module): add helper method, `component(...)` for creating compon…
…ent directives

Since we are promoting component directives as the building blocks of
Angular applications, this new helper provides a simpler method for
defining such directives. By using sensible, widely accepted, conventions
the number of parameters needed has been cut down dramatically.

Many component directives can now be defined by simply providing a `name`,
`template`/`templateUrl`, a `controller`, and `bindings`:

```js
myMod.component('myComp', {
  template: '<div>My name is {{myComp.name}}</div>',
  controller: function() {
  },
  bindings: { name: '=' }
});
```

Closes #10007
Closes #12933
54e8165
@shahata shahata closed this in 54e8165 Oct 29, 2015
@zachlysobey zachlysobey referenced this issue in johnpapa/angular-styleguide Nov 11, 2015
Open

bindToController vs scope angular v1.4 #430

@shairez
Contributor
shairez commented Dec 22, 2015

Sorry to nudge in with yet another question -

The problem I run into when writing lots of "component like" directives is the unnecessarily repeated configuration over and over again.

The first being the .controller registration by name, and the second one being the controllerAs as "vm".

Registering controllers

Registering controllers by name in a component based architecture is useful only for unit tests, where you'd want to use $controller to create an instance of the component's controller.

But registering it over and over again is redundant and can be done by the .component.

controllerAs

The controllerAs: "vm" is the best practice AFAIK and I think it should be the default moving forward, you can always override it.

Maybe we should add an "opt in" configuration for people who want to use a default "controllerAs", because adding it for every component is very annoying.

Example

So we should end up with something where the .component becomes a true "annotation like" thing, something like:

angular
 .module('myModule')
 .component('myComp', {
   controller: MyCompController,
   templateUrl: 'my-comp.html'
  })

function MyCompController(){

}

$controller('myComp') // <-- should be an instance of MyCompController

And we could refer to our component as "vm" in our templates.

I'd love to hear what you think about it and what are the cons of this approach

@wesleycho
Contributor

I absolutely would not want to see controllerAs: vm as default, and would be very unhappy if that were the case, as would a lot of the angular community as well that I interact with daily - as far as I've seen, controllerAs: vm is divisive, and IMO, far from a best practice.

Anyhow, such a feature request probably should belong in a separate issue rather than clutter up this one.

@shahata
Contributor
shahata commented Dec 22, 2015

@shairez I think you are saying two things:

  1. controllerAs should default to vm and not to component name - I agree that defaulting to component name might not be the correct thing to do. It would be easier for people to always use the same name unless they need to reference different controllers in the template, which is where they would configure a custom controllerAs. We are already discussing this and will probably default to $ctrl.
  2. there should be an easy way to get the controller of a component for unit tests - $controller('myComp'). That's a nice idea. For ES6 apps you can actually do something like $controller(MyCompController) but I understand how in ES5 apps you wouldn't want to put MyCompController on the global namespace. @petebacondarwin WDYT?
@Narretz
Contributor
Narretz commented Dec 22, 2015

We've talked about the default controllerAs name and we will probably
change it before 1.5. We're just not sure what it'll be and we're going to
be open for suggestions.
Am 22.12.2015 20:34 schrieb "Shahar Talmi" notifications@github.com:

@shairez https://github.com/shairez I think you are saying two things:

  1. controllerAs should default to vm and not to component name - I
    agree that defaulting to component name might not be the correct thing to
    do. It would be easier for people to always use the same name unless they
    need to reference different controllers in the template, which is where
    they would configure a custom controllerAs. We are already discussing
    and will probably default to $ctrl.
  2. there should be an easy way to get the controller of a component
    for unit tests - $controller('myComp'). That's a nice idea. For ES6
    apps you can actually do something like $controller(MyCompController)
    but I understand how in ES5 apps you wouldn't want to put
    MyCompController on the global namespace. @petebacondarwin
    https://github.com/petebacondarwin WDYT?


Reply to this email directly or view it on GitHub
#10007 (comment)
.

@shairez
Contributor
shairez commented Dec 22, 2015

@wesleycho you're right, I should have probably opened up 2 separate issues for these questions, there is a nicer way of suggesting it though.

@shahata @Narretz thanks for the comments, let me know if there is another open issue for the discussion of the consistent controllerAs name (couldn't find it), I'd love to help!

About the controller registration, I'm also waiting for @petebacondarwin response, and then we can decide whether to open up a new issue for that as well. (I will also submit a PR if we'll agree it's a good addition)

Thanks!

@drpicox
Contributor
drpicox commented Dec 23, 2015

+1 to controllerAs: 'vm' as default, and my reason is the following:

If all directives have the same controller name you can leverage on that:

<my-logic-toggle as="someToggle"></my-logic-toggle>
<some-content ng-show="vm.someToggle.opened></some-content>

or

<button ng-click="vm.pager.first()">First</button>
<my-pager as="pager" elements="element as vm.elements">
  <my-element element="element"></my-element>
</my-pager>
<button ng-click="vm.pager.next()">Next</button>

or

<button ng-click="vm.play()">Play</button>
<audio as="audio">
    <source src="some.mp3" type="audio/mpeg">
</audio>

and controller contains (it requires a controller because Angular forbids use DOM objects in template expressions):

vm.play = function() {
  vm.audio.play();
};

It assigns it inside vm because it is a variable for all template, it does not matters about scopes in the same template. But I have to confess that I have a second one directive to connect things inside scopes (for example inside an ng-repeat).
Anyway, it is very handy to connect component controllers just like that, ng2 introduces it but we can use it in ng1 (I'm doing it since 1.2).

Btw: it can be vm, or ctr, or anyother else, but it should be always the same name, short to save some bytes, if possible

@petebacondarwin
Member

@drpicox - I am not convinced that your suggestions are a good idea, if I understand it right.
I think you are saying that these snippets would be contained within a component that has a controller and that this controller would be attached to the scope as vm, right?

That is OK but I don't like that you are making that assumption for your custom directives, such as my-logic-toggle. This is brittle, since it is always possible that someone might choose to name their controller something else, overriding the default of vm or whatever it is. Moreover it is inconsistent, since you have one bit of template referring to something simply as someToggle and another bit referring to it as vm.someToggle.

I would be more comfortable if the as attribute referenced the controller explicitly...

<my-logic-toggle as="vm.someToggle"></my-logic-toggle>
<some-content ng-show="vm.someToggle.opened></some-content>

in which case this particular argument to have a consistent naming is no longer necessary.


That being said, I still do think that we need a consistent naming for directive controllers that are made available on the scope; just not for the reason given.

@petebacondarwin
Member

I made a new issue #13664

@drpicox
Contributor
drpicox commented Jan 2, 2016

@petebacondarwin You're right and I agree.

Also about being explicit, thanks, I'll introduce it.
I missed it because I was more concerned in performance than expressivity, implicit version of vm there is no need for $parse.

@shairez
Contributor
shairez commented Jan 6, 2016

As for the second point I raised - auto creating a controller - I've opened up a separate issue #13683

let me know what you think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment