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

Extend/Inherit angular2 components annotations #7968

Closed
fernandocode opened this Issue Apr 8, 2016 · 46 comments

Comments

Projects
None yet
@fernandocode

fernandocode commented Apr 8, 2016

I would like to create extensions for some components already deployed in Angular 2, without having to rewrite them almost completely, as the base component could undergo changes and wish these changes were also reflected in its derived components.

Example

I created this simple example to try to explain better the problem:

With the following base component app/base-panel.component.ts:

import {Component, Input} from 'angular2/core';

@Component({
    selector: 'base-panel',
    template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>',
    styles: [`
    .panel{
    padding: 50px;
  }
  `]
})
export class BasePanelComponent { 

  @Input() content: string;

  color: string = "red";

  onClick(event){
    console.log("Click color: " + this.color);
  }
}

Would you like to create another derivative component only alter, for example, a basic component behavior in the case of the example color, app/my-panel.component.ts:

import {Component} from 'angular2/core';
import {BasePanelComponent} from './base-panel.component'

@Component({
    selector: 'my-panel',
    template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>',
    styles: [`
    .panel{
    padding: 50px;
  }
  `]
})
export class MyPanelComponent extends BasePanelComponent{

  constructor(){
    this.color = "blue";
  }
}

Example in Plunker

Problem/Limitation

As you can see in the implementation of the derivative component app/my-panel.component.ts, much of the implementation was repeated, and the single part really inherited was the class BasePanelComponent, but the annotation @Component had to basically be completely repeated, not just the changed portions, as the selector: 'my-panel'.

Feature request

After a few days of research around this possible limitation (question on StackOverflow and StackOverflowPt (my native language)), concludes that even the current version there is no mechanism for the annotations inheritance. So I wonder if it is possible to be implemented this possibility/feature functionality for future versions of the angular library?

@aminebizid

This comment has been minimized.

Show comment
Hide comment
@aminebizid

aminebizid commented Apr 10, 2016

+1

@zoechi

This comment has been minimized.

Show comment
Hide comment
@zoechi

zoechi Apr 10, 2016

Contributor

There is a nice new GitHub feature to "add reaction"s instead of +1 posts. Please use this instead 👍

Contributor

zoechi commented Apr 10, 2016

There is a nice new GitHub feature to "add reaction"s instead of +1 posts. Please use this instead 👍

@robwormald

This comment has been minimized.

Show comment
Hide comment
@robwormald

robwormald Apr 12, 2016

Member

@decorators are just functions (so inheritance doesn't really make sense)

You can compose them relatively easily (that is, wrap them in a function that itself is a decorator) but I'd advise against this, as it's possible this will interfere with build tooling that looks for @component decorators.

I do understand the arguments re: DRY, but in general angular2 errs in favor of explicitness.

Member

robwormald commented Apr 12, 2016

@decorators are just functions (so inheritance doesn't really make sense)

You can compose them relatively easily (that is, wrap them in a function that itself is a decorator) but I'd advise against this, as it's possible this will interfere with build tooling that looks for @component decorators.

I do understand the arguments re: DRY, but in general angular2 errs in favor of explicitness.

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode Apr 12, 2016

@robwormald ,

I do not know if I understand well your arguments, but this behavior makes it difficult to build components with similar behaviors. Other languages that use Annotation keeps them accessible by reflection on objects that inherit them. So this would be expected behavior for Angular or typescript in the construction of its components?

@component, keeps much of a Angular component behavior, it can not be inherited is something that does not seem to me the best, or is there any other way to configure template, selector, etc., without using Annotation, so that it can be inherited and / or overwritten?

fernandocode commented Apr 12, 2016

@robwormald ,

I do not know if I understand well your arguments, but this behavior makes it difficult to build components with similar behaviors. Other languages that use Annotation keeps them accessible by reflection on objects that inherit them. So this would be expected behavior for Angular or typescript in the construction of its components?

@component, keeps much of a Angular component behavior, it can not be inherited is something that does not seem to me the best, or is there any other way to configure template, selector, etc., without using Annotation, so that it can be inherited and / or overwritten?

@aprinaresh97

This comment has been minimized.

Show comment
Hide comment
@aprinaresh97

aprinaresh97 May 17, 2016

We are also in need of inheritance. Right now we are duplicating the code unnecessarily to extend a component.

aprinaresh97 commented May 17, 2016

We are also in need of inheritance. Right now we are duplicating the code unnecessarily to extend a component.

@mhevery

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery May 17, 2016

Member

This has been discussed in depth before, and the conclusion is that it is not clear what does it mean to inherit decorators / annotations. Yes we could make a set of rules, but it would be rather arbitrary, and than we would have to define a set of merge rules, (how do you merge parent and child properties on the decorator). While this sounds like a great idea at first, the more you dig into it the more arbitrary it becomes as to how to handle corner cases is. For this reason, we will not support this. Sorry.

Member

mhevery commented May 17, 2016

This has been discussed in depth before, and the conclusion is that it is not clear what does it mean to inherit decorators / annotations. Yes we could make a set of rules, but it would be rather arbitrary, and than we would have to define a set of merge rules, (how do you merge parent and child properties on the decorator). While this sounds like a great idea at first, the more you dig into it the more arbitrary it becomes as to how to handle corner cases is. For this reason, we will not support this. Sorry.

@mhevery mhevery closed this May 17, 2016

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode May 18, 2016

@mhevery, Ok. I understand their arguments, and difficulties in making it viable and not create something arbitrary.
But there would be some way to allow set up what is now done through Annotation via code directly on objects where compiler stores these settings thus become much simpler to inherit and/or override these settings:

Something similar to this, for example:

app/base-panel.component.ts:

import {Component, Input} from 'angular2/core';

export class BasePanelComponent {

    constructor() {
        // Set @Component in code
        this._component = {
            selector: 'base-panel',
            template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>',
            styles: [`
              .panel{
                padding: 50px;
              }
              `]
        };
    }

    @Input() content: string;

    color: string = "red";

    onClick(event) {
        console.log("Click color: " + this.color);
    }
}

app/my-panel.component.ts:

import {Component} from 'angular2/core';
import {BasePanelComponent} from './base-panel.component'

export class MyPanelComponent extends BasePanelComponent {

    constructor() {
        super();
        // Set @Component override properties
        this._component.selector = 'my-panel';
        this.color = "blue";
    }
}

Wherethis._component would be the object where the compiler builds the Annotation.

This is a feature of extreme importance to build a concise application. Please consider an alternative approach.

fernandocode commented May 18, 2016

@mhevery, Ok. I understand their arguments, and difficulties in making it viable and not create something arbitrary.
But there would be some way to allow set up what is now done through Annotation via code directly on objects where compiler stores these settings thus become much simpler to inherit and/or override these settings:

Something similar to this, for example:

app/base-panel.component.ts:

import {Component, Input} from 'angular2/core';

export class BasePanelComponent {

    constructor() {
        // Set @Component in code
        this._component = {
            selector: 'base-panel',
            template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>',
            styles: [`
              .panel{
                padding: 50px;
              }
              `]
        };
    }

    @Input() content: string;

    color: string = "red";

    onClick(event) {
        console.log("Click color: " + this.color);
    }
}

app/my-panel.component.ts:

import {Component} from 'angular2/core';
import {BasePanelComponent} from './base-panel.component'

export class MyPanelComponent extends BasePanelComponent {

    constructor() {
        super();
        // Set @Component override properties
        this._component.selector = 'my-panel';
        this.color = "blue";
    }
}

Wherethis._component would be the object where the compiler builds the Annotation.

This is a feature of extreme importance to build a concise application. Please consider an alternative approach.

@aprinaresh97

This comment has been minimized.

Show comment
Hide comment
@aprinaresh97

aprinaresh97 May 18, 2016

Today without a choice to inherit, I had to duplicate the code. Our's is enterprise application and was planning to use Angular 2 for our web UI development.

We want to make some common services and common fields available to all the components as part of BaseComponent and the inherited component can avail those services and data easily. Looks like this is not feasible. Now we have to inject these services in every component we are developing.

Also for some cases, we want to only change the component selector and style for semantic reasons rest of the code will remain the same; without inheritance we have to duplicate the code.

Right now only few of us are trying to use Angular 2 as part of Research, eventually lot many developers will be involved to develop components at that time they should not be worrying about injecting common services in their components it should be available to them from the BaseComponent.

I am surprised that Angular team is not considering this seriously.

aprinaresh97 commented May 18, 2016

Today without a choice to inherit, I had to duplicate the code. Our's is enterprise application and was planning to use Angular 2 for our web UI development.

We want to make some common services and common fields available to all the components as part of BaseComponent and the inherited component can avail those services and data easily. Looks like this is not feasible. Now we have to inject these services in every component we are developing.

Also for some cases, we want to only change the component selector and style for semantic reasons rest of the code will remain the same; without inheritance we have to duplicate the code.

Right now only few of us are trying to use Angular 2 as part of Research, eventually lot many developers will be involved to develop components at that time they should not be worrying about injecting common services in their components it should be available to them from the BaseComponent.

I am surprised that Angular team is not considering this seriously.

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode May 30, 2016

@mhevery,

If Decorator/Annotation have these limitations, which according to its arguments can not be easily circumvented in the library, I make you a question:

  • Why use them?
  • Would it not be a better option to ignore/discards them to develop a library the size and complexity of angular2?

Or pretender invest time/money in a library with primary limitations as listed in this issue? I believe this is even the time for major changes that can alter the route of angular2 as the library is still in RC1. It would be extremely disappointing to see a stable version of a library as angular2 be released with this kind of limitation.

fernandocode commented May 30, 2016

@mhevery,

If Decorator/Annotation have these limitations, which according to its arguments can not be easily circumvented in the library, I make you a question:

  • Why use them?
  • Would it not be a better option to ignore/discards them to develop a library the size and complexity of angular2?

Or pretender invest time/money in a library with primary limitations as listed in this issue? I believe this is even the time for major changes that can alter the route of angular2 as the library is still in RC1. It would be extremely disappointing to see a stable version of a library as angular2 be released with this kind of limitation.

@templth

This comment has been minimized.

Show comment
Hide comment
@templth

templth May 30, 2016

Contributor

The following answers on StackOverflow could interest you regarding this issue:

The solution is to implement a custom decorator that gets annotations from the parent class and merge it into the ones of the current one.

Contributor

templth commented May 30, 2016

The following answers on StackOverflow could interest you regarding this issue:

The solution is to implement a custom decorator that gets annotations from the parent class and merge it into the ones of the current one.

@watzon

This comment has been minimized.

Show comment
Hide comment
@watzon

watzon Jun 1, 2016

Is there any reason you couldn't extend the ComponentMetadata class and then set some default values?

watzon commented Jun 1, 2016

Is there any reason you couldn't extend the ComponentMetadata class and then set some default values?

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode Jun 1, 2016

@iDev0urer, how I would inherit the decorator definitions of the base class? I do not know if I understood well how it would be. You could create a sample in Plunker?

fernandocode commented Jun 1, 2016

@iDev0urer, how I would inherit the decorator definitions of the base class? I do not know if I understood well how it would be. You could create a sample in Plunker?

@templth

This comment has been minimized.

Show comment
Hide comment
@templth

templth Jun 3, 2016

Contributor

I posted an article on this:

I would be interested in your feedbacks ;-)

Contributor

templth commented Jun 3, 2016

I posted an article on this:

I would be interested in your feedbacks ;-)

@mhevery

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Jun 4, 2016

Member

@templth I would advise against @CustomComponent and friends because they will break offline compile. Please have a look at https://docs.google.com/document/d/16D3U3wdDBMEncUUEXBxiCaI11SnhjBijggZGq8JMjP0 where this was discussed in depth.

The basic gist is that Composition is better than Inheritance. I would love to see a blog post which discusses how composition is intended to work in Angular2. Would you be willing to work on such an article? If yes, I would be happy to explain the details and limitations and work with you.

Member

mhevery commented Jun 4, 2016

@templth I would advise against @CustomComponent and friends because they will break offline compile. Please have a look at https://docs.google.com/document/d/16D3U3wdDBMEncUUEXBxiCaI11SnhjBijggZGq8JMjP0 where this was discussed in depth.

The basic gist is that Composition is better than Inheritance. I would love to see a blog post which discusses how composition is intended to work in Angular2. Would you be willing to work on such an article? If yes, I would be happy to explain the details and limitations and work with you.

@templth

This comment has been minimized.

Show comment
Hide comment
@templth

templth Jun 5, 2016

Contributor

@mhevery I see what you mean. I would definitely be pleased / interested in working on such a blog post!

Contributor

templth commented Jun 5, 2016

@mhevery I see what you mean. I would definitely be pleased / interested in working on such a blog post!

@mhevery

This comment has been minimized.

Show comment
Hide comment
@mhevery

mhevery Jun 6, 2016

Member

@templth write it up, and I can review. Perhaps start with a google doc?

Member

mhevery commented Jun 6, 2016

@templth write it up, and I can review. Perhaps start with a google doc?

@templth

This comment has been minimized.

Show comment
Hide comment
@templth

templth Jun 7, 2016

Contributor

@mhevery sure! Shared a google doc with you...

Contributor

templth commented Jun 7, 2016

@mhevery sure! Shared a google doc with you...

@templth

This comment has been minimized.

Show comment
Hide comment
@templth

templth Jun 13, 2016

Contributor

@fernandocode: you can notice that you could also override the value of the color property with a directive that you would apply to your component. See the selector regarding how it applies.

import {Directive} from 'angular2/core';
import {BasePanelComponent} from './base-panel.component'

@Directive({
  // same selector than the base 
  selector: 'basepanel[mypanel]'
})
export class MyPanelComponent {
    constructor(private basePanel:BasePanelComponent) {
        this.basePanel.color = "blue";
    }
}

You can use the component and the directive this way:

<!-- Color is red -->
<basepanel></basepanel>
<!-- Color is blue -->
<basepanel mypanel></basepanel>
Contributor

templth commented Jun 13, 2016

@fernandocode: you can notice that you could also override the value of the color property with a directive that you would apply to your component. See the selector regarding how it applies.

import {Directive} from 'angular2/core';
import {BasePanelComponent} from './base-panel.component'

@Directive({
  // same selector than the base 
  selector: 'basepanel[mypanel]'
})
export class MyPanelComponent {
    constructor(private basePanel:BasePanelComponent) {
        this.basePanel.color = "blue";
    }
}

You can use the component and the directive this way:

<!-- Color is red -->
<basepanel></basepanel>
<!-- Color is blue -->
<basepanel mypanel></basepanel>
@getmetorajesh

This comment has been minimized.

Show comment
Hide comment
@getmetorajesh

getmetorajesh Jun 24, 2016

@templth well written article on decorator inheritance. Looking forward for the article on the composition on angular2

getmetorajesh commented Jun 24, 2016

@templth well written article on decorator inheritance. Looking forward for the article on the composition on angular2

@dgroh

This comment has been minimized.

Show comment
Hide comment
@dgroh

dgroh Jul 12, 2016

We are using inheritance and we would like to avoid it, but we still don't know how to go for composition with Angular2. So we are also looking forward for this article.

dgroh commented Jul 12, 2016

We are using inheritance and we would like to avoid it, but we still don't know how to go for composition with Angular2. So we are also looking forward for this article.

@johnchristopherjones

This comment has been minimized.

Show comment
Hide comment
@johnchristopherjones

johnchristopherjones Aug 4, 2016

The @Component decorator is just taking an object as a parameter, right? What's wrong storing it in a variable and using Object.assign to extend it?:

/**
 * Define component options for the base class
 */
const baseComponentOptions = {
    selector: 'base-panel',
    template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>',
    styles: [`
        .panel {
        padding: 50px;
       }
    `]
};

/**
 * Base class
 */
@Component(baseComponentOptions)
export class BasePanelComponent {

  @Input() content: string;

  color: string = "red";

  onClick(event){
    console.log("Click color: " + this.color);
  }
}

/**
 * Subclass that over-rides the selector and constructor
 */
const myPanelComponentOptions = Object.assign({}, baseComponentOptions, {
    selector: 'my-panel'
});
@Component(myPanelComponentOptions)
export class MyPanelComponent extends BasePanelComponent{

  constructor(){
    this.color = "blue";
  }
}

It's not as elegant as JUST doing "MyThing extends BaseThing", but is it good enough?

johnchristopherjones commented Aug 4, 2016

The @Component decorator is just taking an object as a parameter, right? What's wrong storing it in a variable and using Object.assign to extend it?:

/**
 * Define component options for the base class
 */
const baseComponentOptions = {
    selector: 'base-panel',
    template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>',
    styles: [`
        .panel {
        padding: 50px;
       }
    `]
};

/**
 * Base class
 */
@Component(baseComponentOptions)
export class BasePanelComponent {

  @Input() content: string;

  color: string = "red";

  onClick(event){
    console.log("Click color: " + this.color);
  }
}

/**
 * Subclass that over-rides the selector and constructor
 */
const myPanelComponentOptions = Object.assign({}, baseComponentOptions, {
    selector: 'my-panel'
});
@Component(myPanelComponentOptions)
export class MyPanelComponent extends BasePanelComponent{

  constructor(){
    this.color = "blue";
  }
}

It's not as elegant as JUST doing "MyThing extends BaseThing", but is it good enough?

@opsxcq

This comment has been minimized.

Show comment
Hide comment
@opsxcq

opsxcq Aug 10, 2016

Not looking into some internal details, but just focusing on code aesthetics, some times we have to repeat ourselves on injecting directives, parametrize our component, importing code that is imported everywhere and sometimes child component have different styles and/or html tags.

As a suggestion, and following object orientation principles, we can make an component have a tag, like used on the router, to specify where the child template will be rendered. Let the communication between those two components, flow using super() on constructor, super.(), or even super.variable.

For illustration of the solution, below the code:

import { ALotOfThings, ALotOfDirectives } from '@angular/core';
import { ALotOfThings2, ALotOfDirectives2 } from 'somecomponent';

@Component({
    template: '<h1 (click)="click()">{{ title }}</h1> <child> </child> <footer>...</footer>',
    directives: [ALotOfDirectives, MoreDirectives, EvenMoreDirectives, MoreDirectivesThatICanHandle]
})
abstract class Parent {
    constructor(private title: string){
    }

   abstract click();
}

On child component

import { Parent } from './parent';

@Component({
    template: '<p> Im the child component </p>'
    directives: [ OnlyWhatChildNeeds, NoJunkDirectives, CleanCodeForEveryOne ]
})
class Child extends Parent{
    constructor(){
       super('Title from child');
    }

  click(){
    console.log('Click from parent component');
  }
}

What we get with this implementation ? A good use of the strong type checking, that will require that the child component implement what is abstract on parent, or be abstract. And a flexible way to position child content inside the parent component.

opsxcq commented Aug 10, 2016

Not looking into some internal details, but just focusing on code aesthetics, some times we have to repeat ourselves on injecting directives, parametrize our component, importing code that is imported everywhere and sometimes child component have different styles and/or html tags.

As a suggestion, and following object orientation principles, we can make an component have a tag, like used on the router, to specify where the child template will be rendered. Let the communication between those two components, flow using super() on constructor, super.(), or even super.variable.

For illustration of the solution, below the code:

import { ALotOfThings, ALotOfDirectives } from '@angular/core';
import { ALotOfThings2, ALotOfDirectives2 } from 'somecomponent';

@Component({
    template: '<h1 (click)="click()">{{ title }}</h1> <child> </child> <footer>...</footer>',
    directives: [ALotOfDirectives, MoreDirectives, EvenMoreDirectives, MoreDirectivesThatICanHandle]
})
abstract class Parent {
    constructor(private title: string){
    }

   abstract click();
}

On child component

import { Parent } from './parent';

@Component({
    template: '<p> Im the child component </p>'
    directives: [ OnlyWhatChildNeeds, NoJunkDirectives, CleanCodeForEveryOne ]
})
class Child extends Parent{
    constructor(){
       super('Title from child');
    }

  click(){
    console.log('Click from parent component');
  }
}

What we get with this implementation ? A good use of the strong type checking, that will require that the child component implement what is abstract on parent, or be abstract. And a flexible way to position child content inside the parent component.

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode Aug 10, 2016

@opsxcq . I could not understand where his approach would help to resolve the annotations inheritance problem, the problem is reported in issue. I tried to create a Plunker to follow what your solution proposes, but could not understand what the relationship of this with the problem of the issue.

If you can create a Plunker demonstrating what use of this solution to the problem of the question! = D

fernandocode commented Aug 10, 2016

@opsxcq . I could not understand where his approach would help to resolve the annotations inheritance problem, the problem is reported in issue. I tried to create a Plunker to follow what your solution proposes, but could not understand what the relationship of this with the problem of the issue.

If you can create a Plunker demonstrating what use of this solution to the problem of the question! = D

@opsxcq

This comment has been minimized.

Show comment
Hide comment
@opsxcq

opsxcq Aug 10, 2016

@fernandocode this is an hypothetical code, it won't work on actual angular2, but is a suggestion about how the code would be. Just an example of its construction. And as angular2 is an open source project, we can all share ideas and code.

I'm facing the same problem here, and this construction would bring more clarity and flexibility to my code. Take your problem at the most generic level, where components act like objects in typescript. Keeping the same @component annotation so new coders will understand what is going on. And the extends keyword, that will make a hardlink, between the two implementations, using the Object Orientation protocol, like parent calling an abstract method that will be implemented by a child component, child component calling the parent object.

The <child> that, would work in a similar way to router, so you can get some flexibility about where the children component will be rendered inside the parent.

Focusing on the implementation on angular2, I think that it won't be impossible, considering the tag information and the object hierarchy. Not focusing only on your problem, but on a complete component inheritance solution, did you think that this construction solve what you would ever need about @component inheritance ?

opsxcq commented Aug 10, 2016

@fernandocode this is an hypothetical code, it won't work on actual angular2, but is a suggestion about how the code would be. Just an example of its construction. And as angular2 is an open source project, we can all share ideas and code.

I'm facing the same problem here, and this construction would bring more clarity and flexibility to my code. Take your problem at the most generic level, where components act like objects in typescript. Keeping the same @component annotation so new coders will understand what is going on. And the extends keyword, that will make a hardlink, between the two implementations, using the Object Orientation protocol, like parent calling an abstract method that will be implemented by a child component, child component calling the parent object.

The <child> that, would work in a similar way to router, so you can get some flexibility about where the children component will be rendered inside the parent.

Focusing on the implementation on angular2, I think that it won't be impossible, considering the tag information and the object hierarchy. Not focusing only on your problem, but on a complete component inheritance solution, did you think that this construction solve what you would ever need about @component inheritance ?

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode Aug 10, 2016

@opsxcq yes, now I understand your publication, I think not having much fluidity in English I have understood some words in the wrong context, and just not realizing that it was a suggestion and not a possible solution. (I thought I had something with the launch of angular rc.5).

Thanks for clarification and suggestion!

fernandocode commented Aug 10, 2016

@opsxcq yes, now I understand your publication, I think not having much fluidity in English I have understood some words in the wrong context, and just not realizing that it was a suggestion and not a possible solution. (I thought I had something with the launch of angular rc.5).

Thanks for clarification and suggestion!

@templth

This comment has been minimized.

Show comment
Hide comment
@templth

templth Aug 19, 2016

Contributor

I finally published the first part of the article on the composition on angular2: https://medium.com/@ttemplier/component-composition-in-angular2-part-1-33f50f402906#.duudocsbc.

Feel free to make me your comments!

Contributor

templth commented Aug 19, 2016

I finally published the first part of the article on the composition on angular2: https://medium.com/@ttemplier/component-composition-in-angular2-part-1-33f50f402906#.duudocsbc.

Feel free to make me your comments!

@opsxcq

This comment has been minimized.

Show comment
Hide comment
@opsxcq

opsxcq Aug 19, 2016

It's a good article @templth , but I don't think that it will solve the inheritance problem that we are discussing about. But your technique is interesting, I will give it a try today.

opsxcq commented Aug 19, 2016

It's a good article @templth , but I don't think that it will solve the inheritance problem that we are discussing about. But your technique is interesting, I will give it a try today.

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Oct 8, 2016

@templth I would advise against @customcomponent and friends because they will break offline compile. Please have a look at https://docs.google.com/document/d/16D3U3wdDBMEncUUEXBxiCaI11SnhjBijggZGq8JMjP0 where this was discussed in depth.

The basic gist is that Composition is better than Inheritance. I would love to see a blog post which discusses how composition is intended to work in Angular2. Would you be willing to work on such an article? If yes, I would be happy to explain the details and limitations and work with you.

@mhevery There seem to be a few contradictory statements here.
In addition to being a severe limitation, the fact that custom decorators will not work with AOT is completely orthogonal to the questions of inheritance and composition.

I completely agree that composition is preferable to inheritance (in fact composition is far superior).
But the point is that Decorators are, both literally and conceptually a composition technique, not an inheritance technique.

For one thing decorators are literally syntactic sugar for function composition. They can be chained (again sugar for composition) to produce new values as they aggregate information and return to the decorator above them.

What you are basically saying is that inheritance is problematic and so thusly inheritance of decorators is problematic. I buy that argument. But I have no need to use decorators on classes with an extends clause if I can wrap common behavior by abstracting over decorators.

Since inheritance of decorators is not supported and since I cannot abstract over angular decorators, I conclude two things.

  1. You are saying there is no work around to this issue. Copy and paste coding is completely unacceptable.
  2. Since the framework doesn't support custom decorators when using AOT, I infer that decorators, the foundation of the frameworks entire syntactic API subset of TypeScript, are not first class citizens in Angular, the framework that encourages their use the most. Basically, AOT relegates them to static annotations (something TypeScript has plenty of other syntax for) while ignoring the fact that they were designed to augment structures dynamically.

Furthermore, with TSC-wrapped and AOT, which were not part of the original design, TypeScript's semantics are being changed, limited, and altered by Angular. I feel like this is the @script debacle all over again

aluanhaddad commented Oct 8, 2016

@templth I would advise against @customcomponent and friends because they will break offline compile. Please have a look at https://docs.google.com/document/d/16D3U3wdDBMEncUUEXBxiCaI11SnhjBijggZGq8JMjP0 where this was discussed in depth.

The basic gist is that Composition is better than Inheritance. I would love to see a blog post which discusses how composition is intended to work in Angular2. Would you be willing to work on such an article? If yes, I would be happy to explain the details and limitations and work with you.

@mhevery There seem to be a few contradictory statements here.
In addition to being a severe limitation, the fact that custom decorators will not work with AOT is completely orthogonal to the questions of inheritance and composition.

I completely agree that composition is preferable to inheritance (in fact composition is far superior).
But the point is that Decorators are, both literally and conceptually a composition technique, not an inheritance technique.

For one thing decorators are literally syntactic sugar for function composition. They can be chained (again sugar for composition) to produce new values as they aggregate information and return to the decorator above them.

What you are basically saying is that inheritance is problematic and so thusly inheritance of decorators is problematic. I buy that argument. But I have no need to use decorators on classes with an extends clause if I can wrap common behavior by abstracting over decorators.

Since inheritance of decorators is not supported and since I cannot abstract over angular decorators, I conclude two things.

  1. You are saying there is no work around to this issue. Copy and paste coding is completely unacceptable.
  2. Since the framework doesn't support custom decorators when using AOT, I infer that decorators, the foundation of the frameworks entire syntactic API subset of TypeScript, are not first class citizens in Angular, the framework that encourages their use the most. Basically, AOT relegates them to static annotations (something TypeScript has plenty of other syntax for) while ignoring the fact that they were designed to augment structures dynamically.

Furthermore, with TSC-wrapped and AOT, which were not part of the original design, TypeScript's semantics are being changed, limited, and altered by Angular. I feel like this is the @script debacle all over again

@fugwenna

This comment has been minimized.

Show comment
Hide comment
@fugwenna

fugwenna Oct 8, 2016

@johnchristopherjones

https://plnkr.co/edit/2ufBCokJXNHNwscw9rAA?p=preview

I've just been scratching the surface of Angular2 recently, but I messed around in a plnkr I found to see what would happen... seems to handle the class extension better than the const object approach.

//our root app component
import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'


export class Metadata {
    protected readonly selector: string = 'my-app';
    protected readonly template: string = 'Test';
}

export class MetadataChild extends Metadata {
    protected readonly template: string = this.template + ' child';

    constructor() {
        super();
    }
}

@Component(new MetadataChild())
export class App {
  name:string;
  constructor() {
    this.name = 'Angular2'
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

fugwenna commented Oct 8, 2016

@johnchristopherjones

https://plnkr.co/edit/2ufBCokJXNHNwscw9rAA?p=preview

I've just been scratching the surface of Angular2 recently, but I messed around in a plnkr I found to see what would happen... seems to handle the class extension better than the const object approach.

//our root app component
import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'


export class Metadata {
    protected readonly selector: string = 'my-app';
    protected readonly template: string = 'Test';
}

export class MetadataChild extends Metadata {
    protected readonly template: string = this.template + ' child';

    constructor() {
        super();
    }
}

@Component(new MetadataChild())
export class App {
  name:string;
  constructor() {
    this.name = 'Angular2'
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}
@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode Oct 10, 2016

I fully agree with you @aluanhaddad, and I also agree that the composition is better than inheritance. But not always the problem can be solved with composition, if that were true there would be no need to have inheritance. This should be seen as a recommendation is not as a rule. This should be seen as a recommendation is not as a rule, is being used here this argument is a recommendation, to justify a limitation of the library.I agree that there will always be limitations, just do not agree to limitations on key features such as inheritance, in a large library as angular2.

Look at this scenario:

I library using a of third UI component (primeng) for development. To require a specific behavior to the Dropdown component of the library (source Dropdown component), for example, make the query directly in the database for the filter options, and this behavior is not implemented with ability to register an external event, the only choice short-term is to extend the component and implement the method according to the need, there is no possibility to solve this with composition, unique possibilities I see are:

  1. Extend the component overwriting the methods required for the implementation behavior;
  2. Change the component so that customization is possible (event register) the way the filter is implemented;

Being that the latter is not feasible because is of a third component.
Then the extension having not support inheritance of decorators, @component would be duplicated in the extended component, which is not a good practice! Possibly worse or equivamente using inheritance.

Example extends Dropdown component in plunker.

Question: Is there any way to do this without changing the original component or duplicate code in extended component?

fernandocode commented Oct 10, 2016

I fully agree with you @aluanhaddad, and I also agree that the composition is better than inheritance. But not always the problem can be solved with composition, if that were true there would be no need to have inheritance. This should be seen as a recommendation is not as a rule. This should be seen as a recommendation is not as a rule, is being used here this argument is a recommendation, to justify a limitation of the library.I agree that there will always be limitations, just do not agree to limitations on key features such as inheritance, in a large library as angular2.

Look at this scenario:

I library using a of third UI component (primeng) for development. To require a specific behavior to the Dropdown component of the library (source Dropdown component), for example, make the query directly in the database for the filter options, and this behavior is not implemented with ability to register an external event, the only choice short-term is to extend the component and implement the method according to the need, there is no possibility to solve this with composition, unique possibilities I see are:

  1. Extend the component overwriting the methods required for the implementation behavior;
  2. Change the component so that customization is possible (event register) the way the filter is implemented;

Being that the latter is not feasible because is of a third component.
Then the extension having not support inheritance of decorators, @component would be duplicated in the extended component, which is not a good practice! Possibly worse or equivamente using inheritance.

Example extends Dropdown component in plunker.

Question: Is there any way to do this without changing the original component or duplicate code in extended component?

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Oct 10, 2016

@fernandocode I agree. Indeed there are cases where inheritance is the necessary or even the elegant solution. As you say, code duplication is a very bad practice indeed.

While, frameworks like Angular have a very real and practical need to constrain the literally hundreds of ad-hoc augmentation patterns that exist in the JavaScript ecosystem, they have an equally strong need to provide and bless a structured subset of these patterns (or introduce their own) to allow developers to reuse code.

For example, AngularJS had a notion of service customization, which while slightly intimidating for newcomers was an extremely powerful composition mechanism. It was great when a service in a shared angular.module needed to be reused but slightly modified by a client app. It was also itself a reusable chunk of code. It was well abstracted in the sense that it was simply a function which did not need to inject the target directly, simply injecting a $delegate service, which aliased the service it was modifying. So even the customization could be reused to modify multiple services. This worked really well in AngularJS + TypeScript apps where the contracts could be formally specified.

Any solution where copy and paste is the recommended approach is flat out wrong.

aluanhaddad commented Oct 10, 2016

@fernandocode I agree. Indeed there are cases where inheritance is the necessary or even the elegant solution. As you say, code duplication is a very bad practice indeed.

While, frameworks like Angular have a very real and practical need to constrain the literally hundreds of ad-hoc augmentation patterns that exist in the JavaScript ecosystem, they have an equally strong need to provide and bless a structured subset of these patterns (or introduce their own) to allow developers to reuse code.

For example, AngularJS had a notion of service customization, which while slightly intimidating for newcomers was an extremely powerful composition mechanism. It was great when a service in a shared angular.module needed to be reused but slightly modified by a client app. It was also itself a reusable chunk of code. It was well abstracted in the sense that it was simply a function which did not need to inject the target directly, simply injecting a $delegate service, which aliased the service it was modifying. So even the customization could be reused to modify multiple services. This worked really well in AngularJS + TypeScript apps where the contracts could be formally specified.

Any solution where copy and paste is the recommended approach is flat out wrong.

@ghetolay

This comment has been minimized.

Show comment
Hide comment
@ghetolay

ghetolay Oct 17, 2016

Contributor

I've been reading almost all issues about inheritance and I try to avoid "me too" comment but I've been working with angular 2 for some time now I would like to say that :

I can totally imagine how complicated or even impossible it could be to make something works correctly as said here by @mhevery.
But you are here talking about full inheritance of Component while the need I came across each time is mostly just about overriding style/template and maybe animate. Just so we can build libraries component and let final user decide about at least the style. Most of the actual component libs out there includes their own css style and if you want to apply a different one it's really tricky to do.

If your concern is also just about style and template you can +1 this post (using reaction emoji) so we'll see if it's worth focusing only on those properties and not into a full inheritance of Components.

Contributor

ghetolay commented Oct 17, 2016

I've been reading almost all issues about inheritance and I try to avoid "me too" comment but I've been working with angular 2 for some time now I would like to say that :

I can totally imagine how complicated or even impossible it could be to make something works correctly as said here by @mhevery.
But you are here talking about full inheritance of Component while the need I came across each time is mostly just about overriding style/template and maybe animate. Just so we can build libraries component and let final user decide about at least the style. Most of the actual component libs out there includes their own css style and if you want to apply a different one it's really tricky to do.

If your concern is also just about style and template you can +1 this post (using reaction emoji) so we'll see if it's worth focusing only on those properties and not into a full inheritance of Components.

@no-more

This comment has been minimized.

Show comment
Hide comment
@no-more

no-more Dec 4, 2016

Hey,

sorry for the late question but wouldn't it be possible to achieve this by not using decorators.
It seems possible to to all this in es5 syntax without decorators at all. But I don't know if it's possible, or how, to do this with typescript.
I haven't looked deeply into this yet, but it seems decorators are 'just' converted to function. Does anyone have some info if this is possible or if it's just stupid to ask ;-)

Thanks for any feedback.

no-more commented Dec 4, 2016

Hey,

sorry for the late question but wouldn't it be possible to achieve this by not using decorators.
It seems possible to to all this in es5 syntax without decorators at all. But I don't know if it's possible, or how, to do this with typescript.
I haven't looked deeply into this yet, but it seems decorators are 'just' converted to function. Does anyone have some info if this is possible or if it's just stupid to ask ;-)

Thanks for any feedback.

@f-mon

This comment has been minimized.

Show comment
Hide comment
@f-mon

f-mon commented Dec 14, 2016

Isn't this finally already implemented in 2.3.0 http://angularjs.blogspot.it/2016/12/angular-230-now-available.html?m=1

@smoke

This comment has been minimized.

Show comment
Hide comment
@smoke

smoke Dec 15, 2016

It looks like f5c8e09 to be specific, has taken care of this issue.

smoke commented Dec 15, 2016

It looks like f5c8e09 to be specific, has taken care of this issue.

@mlc-mlapis

This comment has been minimized.

Show comment
Hide comment
@mlc-mlapis

mlc-mlapis Dec 16, 2016

I do not understand some arguments of @mhevery at discussion at https://docs.google.com/document/d/16D3U3wdDBMEncUUEXBxiCaI11SnhjBijggZGq8JMjP0 about difficulties on decission how to inherit "template/templateUrl" or "styles/styleUrls" between a base and a derived component.

What is wrong on the following rules?

  • If a derived component does not declare template or styles then both are taken from its base component, if exists,
  • if a derived component declares them (in any variant) then definitions are taken from this derived component on the same rules as it would be a normal (base) component.

That is all.

It is no doubt that for some cases the inheritance principle on templates and styles has the real advantages.

mlc-mlapis commented Dec 16, 2016

I do not understand some arguments of @mhevery at discussion at https://docs.google.com/document/d/16D3U3wdDBMEncUUEXBxiCaI11SnhjBijggZGq8JMjP0 about difficulties on decission how to inherit "template/templateUrl" or "styles/styleUrls" between a base and a derived component.

What is wrong on the following rules?

  • If a derived component does not declare template or styles then both are taken from its base component, if exists,
  • if a derived component declares them (in any variant) then definitions are taken from this derived component on the same rules as it would be a normal (base) component.

That is all.

It is no doubt that for some cases the inheritance principle on templates and styles has the real advantages.

@Auxx

This comment has been minimized.

Show comment
Hide comment
@Auxx

Auxx Jan 4, 2017

@f-mon @smoke no, this achieves nothing. Your child component can't override template, styles or anything specified in a decorator of a parent, you can only override the code of the class and export it instead of the parent class in your module. So you can't use both classes/components, can't modify presentation, etc.

The idea of inheritance/proper composition is to be able to create proper UI libs with extendable components (naive example, you have Button which only shows textual caption and have some default click behaviours, then some other dev creates IconButton, which has different template with an icon embedded, everything else stays the same), Angular 2 does not allow that in any sane way. There are two ways right now - use JS API and lose AOT capabilities or hack into handling of decorators and write some custom stuff.

Auxx commented Jan 4, 2017

@f-mon @smoke no, this achieves nothing. Your child component can't override template, styles or anything specified in a decorator of a parent, you can only override the code of the class and export it instead of the parent class in your module. So you can't use both classes/components, can't modify presentation, etc.

The idea of inheritance/proper composition is to be able to create proper UI libs with extendable components (naive example, you have Button which only shows textual caption and have some default click behaviours, then some other dev creates IconButton, which has different template with an icon embedded, everything else stays the same), Angular 2 does not allow that in any sane way. There are two ways right now - use JS API and lose AOT capabilities or hack into handling of decorators and write some custom stuff.

@mlc-mlapis

This comment has been minimized.

Show comment
Hide comment
@mlc-mlapis

mlc-mlapis Jan 4, 2017

@Auxx I do not understand your formulation:

Your child component can't override template, styles ...

because this is what actually creates a problem - we can override templates, styles (Angular 2.3.0 and later) but we also need to inherit them in other case ... and we can't.

mlc-mlapis commented Jan 4, 2017

@Auxx I do not understand your formulation:

Your child component can't override template, styles ...

because this is what actually creates a problem - we can override templates, styles (Angular 2.3.0 and later) but we also need to inherit them in other case ... and we can't.

@DzmitryShylovich

This comment has been minimized.

Show comment
Hide comment
@DzmitryShylovich
Contributor

DzmitryShylovich commented Jan 4, 2017

@Auxx

This comment has been minimized.

Show comment
Hide comment
@Auxx

Auxx Jan 4, 2017

@mlc-mlapis if I have component X and I want to create a descendant Y I also want to inherit all of the @component() AND override selector. Because I want X to be injected as and Y to be injected as . And maybe I want to add additional styles. This is how it works in EVERY UI framework! Starting with some extremely old stuff like Turbo Vision and newest stuff like Android and JavaFX. Re use existing code, extend it, override meta when needed.

Auxx commented Jan 4, 2017

@mlc-mlapis if I have component X and I want to create a descendant Y I also want to inherit all of the @component() AND override selector. Because I want X to be injected as and Y to be injected as . And maybe I want to add additional styles. This is how it works in EVERY UI framework! Starting with some extremely old stuff like Turbo Vision and newest stuff like Android and JavaFX. Re use existing code, extend it, override meta when needed.

@Auxx

This comment has been minimized.

Show comment
Hide comment
@Auxx

Auxx Jan 4, 2017

Parser destroyed < x > and < y >

Auxx commented Jan 4, 2017

Parser destroyed < x > and < y >

@mlc-mlapis

This comment has been minimized.

Show comment
Hide comment
@mlc-mlapis

mlc-mlapis Jan 4, 2017

@Auxx Fine, it is true but I am missing something ... how it relates with your previous post?
Specifically the first part to which was related my question? What do you want to formulate? The actual status of component inheritance in Angular or the general concept?

mlc-mlapis commented Jan 4, 2017

@Auxx Fine, it is true but I am missing something ... how it relates with your previous post?
Specifically the first part to which was related my question? What do you want to formulate? The actual status of component inheritance in Angular or the general concept?

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode Jan 4, 2017

@Auxx, your example:

(naive example, you have Button which only shows textual caption and have some default click behaviours, then some other dev creates IconButton, which has different template with an icon embedded, everything else stays the same)

Is a perfect situation where angular inheritance does not facilitate the extension of components.

My original problem that motivated this issue is similar to that. And if I have not lost anything that has been done so far, this problem continues. I understand that it is not an easy change, but it discourages me in relation to angular2 future.
I see angular2 as something extencible, where we would create components for parts of our applications, where each component has its own responsibility and can be reused easily in any application. This is great and the best angular2 has (in my opinion). This allows them to develop components to be used by third parties in various applications. But by architecture it does not allow that third party to extend and modify/override functionality in a component without having to practically rewrite all @component annotations.

fernandocode commented Jan 4, 2017

@Auxx, your example:

(naive example, you have Button which only shows textual caption and have some default click behaviours, then some other dev creates IconButton, which has different template with an icon embedded, everything else stays the same)

Is a perfect situation where angular inheritance does not facilitate the extension of components.

My original problem that motivated this issue is similar to that. And if I have not lost anything that has been done so far, this problem continues. I understand that it is not an easy change, but it discourages me in relation to angular2 future.
I see angular2 as something extencible, where we would create components for parts of our applications, where each component has its own responsibility and can be reused easily in any application. This is great and the best angular2 has (in my opinion). This allows them to develop components to be used by third parties in various applications. But by architecture it does not allow that third party to extend and modify/override functionality in a component without having to practically rewrite all @component annotations.

@chuckjaz

This comment has been minimized.

Show comment
Hide comment
@chuckjaz

chuckjaz Jan 4, 2017

Member

@fernandocode Would the combination of #13764 and #13766 be sufficient to address your concerns?

Member

chuckjaz commented Jan 4, 2017

@fernandocode Would the combination of #13764 and #13766 be sufficient to address your concerns?

@Auxx

This comment has been minimized.

Show comment
Hide comment
@Auxx

Auxx Jan 5, 2017

@chuckjaz #13764 is very relevant and would be extremely useful.

Auxx commented Jan 5, 2017

@chuckjaz #13764 is very relevant and would be extremely useful.

@fernandocode

This comment has been minimized.

Show comment
Hide comment
@fernandocode

fernandocode Jan 5, 2017

@chuckjaz, #13764, Is something that would be useful to solve this problem. I just do not know if Include a special value called inherited is the best option to have access to the value of the ancestor component. I'll detail better my suggestion there in the issue.

fernandocode commented Jan 5, 2017

@chuckjaz, #13764, Is something that would be useful to solve this problem. I just do not know if Include a special value called inherited is the best option to have access to the value of the ancestor component. I'll detail better my suggestion there in the issue.

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