Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature request: Support `restrict: 'A'` for downgraded components #16695

Open
AngularOne opened this issue May 10, 2017 · 17 comments
Open

Feature request: Support `restrict: 'A'` for downgraded components #16695

AngularOne opened this issue May 10, 2017 · 17 comments

Comments

@AngularOne
Copy link

@AngularOne AngularOne commented May 10, 2017

I have wrote a simple directive in angular 2, but while downgrade it to work in angular 1, it just fails. I don't think it is being supported. Am I currect?

@Component({
  selector: '[myComponent]',
  template: `<ng-content></ng-content>`
})
export class MyComponent {
  @Input() myComponent : string;
  
  constructor() {}

  @HostListener("mouseenter", ['$event'])
  show(event:MouseEvent): void {
    if(this.myComponent) {
       console.log("YES, its working: " + this.myComponent);
    }

  }

}

Now I'm downgrading this directive:

angular.module('myApp')
    .directive('myComponent', downgradeComponent({component: MyComponent, inputs: ['myComponent'] }) as angular.IDirectiveFactory)

You accepct this directive to work in angular 1.x too, but its not, like it doesn't even exists

@gkalpak

This comment has been minimized.

Copy link
Member

@gkalpak gkalpak commented May 10, 2017

Downgrading is generally supported (that's 50% of the whole point of ngUpgrade 😛).
But it is difficult to tell what the issue is without a reproduction.

@AngularOne

This comment has been minimized.

Copy link
Author

@AngularOne AngularOne commented May 10, 2017

I added my code. If I now try
<div [my-component]="'abc text'"></div>
it will not work.

@gkalpak

This comment has been minimized.

Copy link
Member

@gkalpak gkalpak commented May 10, 2017

I see. Currently, you cannot downgrade attribute directives. downgradeComponent creates directive factories with restrict: 'E', so they will only work on elements.

In most cases it shouldn't matter, but there might be some cases where you need the selector to match against an attribute and not the node name.

So, this is a feature request 😃

If you are desperate you could probably hack around it, by using an AngularJS .decorator() to change the directive's restrict to A.

@gkalpak gkalpak changed the title Does angular 2 support downgrade of directives? Feature request: Support `restrict: 'A'` for downgraded components May 10, 2017
@pierrebeaucamp

This comment has been minimized.

Copy link

@pierrebeaucamp pierrebeaucamp commented Jun 20, 2017

+1 For that feature request. Also fyi, I did actually try to use decorator to change the restrict property, but unfortunately it didn't work.

@vikrambhatla

This comment has been minimized.

Copy link

@vikrambhatla vikrambhatla commented Aug 3, 2017

+1 for the feature request. We're also using attribute directives. There is no way right now to downgrade Directive.
i.e. We want to add <input [focus]="true"> focus directive. There is no way we can do this kind of scenario in the Hybrid app.

I was thinking to implement:

<focus-field> <input #myInputField> </focus-field>
But I am not able to use @ViewChild('myInputField') myInputField:ElementRef. myInputField is always coming as undefined.

For now, the workaround is:
<input-wrapper [autofocus]="true"> <input> <input-wrapper>

And in code accessing it like:
this.elementRef.nativeElement.querySelector('input').focus()

@jbadeau

This comment has been minimized.

Copy link

@jbadeau jbadeau commented Aug 18, 2017

Try ngadapter

@banjankri

This comment has been minimized.

Copy link

@banjankri banjankri commented Oct 13, 2017

If anyone runs into same issue and needs to have attribute component downgraded, here is a workaround I implemented:

static allowAttribute(componentFactory) {
        const wrapper = function($compile, $injector, $parse) {
            const component = componentFactory($compile, $injector, $parse);
            component.restrict = "AE";
            return component;
        };
        wrapper.$inject = ["$compile", "$injector", "$parse"];
        return wrapper;
    }

and you would use it as follows:

angularjsModule.directive("tableColumn", allowAttribute(downgradeComponent({ component: TableColumnComponent })));

Seems to be hooking up correctly to the mechanism and attribute directives are rendered properly in angularjs app.

@gkalpak

This comment has been minimized.

Copy link
Member

@gkalpak gkalpak commented Dec 15, 2017

BTW, if someone feels like submitting a PR for this, please do 😃

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@ngbot ngbot bot modified the milestones: Backlog, needsTriage Feb 28, 2018
@sharikovvladislav

This comment has been minimized.

Copy link

@sharikovvladislav sharikovvladislav commented May 4, 2018

@gkalpak hey! any ideas how to downgrade Angular @Directive to AngularJS (not a @Component)?

downgradeComponent resolves component via ComponentFactoryResolve.resolveComponentFactory. We can not add Angular Directive to entryComponents so downgradeComponent method can't resolve it.

@gkalpak

This comment has been minimized.

Copy link
Member

@gkalpak gkalpak commented May 5, 2018

It is not possible. In order to be able to upgrade/downgrade something, it needs to own the whole element (and its contents); i.e. be a component.

@sharikovvladislav

This comment has been minimized.

Copy link

@sharikovvladislav sharikovvladislav commented May 7, 2018

@gkalpak okay! thank you

How we downgrade Angular Directive's to AngularJS:

  1. We have our directive (which we want to downgrade)
  2. We also have wrapper component which has simple block with applied directive + ng-content inside it.
  3. We downgrade that component using #16695 (comment) or desperate move from this #16695 (comment).

It worked (if anybody needs it).

@aldo-roman

This comment has been minimized.

Copy link
Contributor

@aldo-roman aldo-roman commented Jul 1, 2018

In case anyone runs into this issue and wonders what the hell "#16695 (comment)" said, here is how you achieve the hack:

app.directive('foo', downgradeComponent({component: FooComponent} as angular.IDirectiveFactory));
app.config(['$provide', $provide => {
  $provide.decorator('fooDirective', ['$delegate', $delegate => {
    $delegate[0].restrict = 'EA';
    return $delegate;
  }]);
}]);
@gkalpak gkalpak added comp: upgrade and removed comp: upgrade labels Mar 13, 2019
@jamesbrobb

This comment has been minimized.

Copy link

@jamesbrobb jamesbrobb commented Mar 27, 2019

In case anyone runs into this issue and wonders what the hell "#16695 (comment)" said, here is how you achieve the hack:

app.directive('foo', downgradeComponent({component: FooComponent} as angular.IDirectiveFactory));
app.config(['$provide', $provide => {
  $provide.decorator('fooDirective', ['$delegate', $delegate => {
    $delegate[0].restrict = 'EA';
    return $delegate;
  }]);
}]);

If using this approach, how do you then use the directive within an ngUpgrade application?

So if the selector of the Angular component is [my-directive] and the downgraded angularjs component is declared as myDirective i'm presuming that my mark up should look like this when using the component:

<div my-directive></div>

not like this:

<div [my-directive]></div>

and not like this:

<div [myDirective]></div>

I say that as the first one is the only one that seems to trigger an error in the console, the other 2 just fail silently.

The error i get in the console is:

Error: No component factory found for MyDirective. Did you add it to @NgModule.entryComponents?

But if i then add it to my AppModule entryComponents, when i run ng build myApp i get the following error:

ERROR in : MyDirective cannot be used as an entry component.

@gkalpak

This comment has been minimized.

Copy link
Member

@gkalpak gkalpak commented Mar 27, 2019

@jamesbrobb, the discussion above is not about downgrading Angular @Directives. It is about downgrading Angular @Components with an attribute selector (i.e. selector: '[some-thing]' - notice the [...]). Here is an example: ngupgradestatic-issue-16695

UPDATE: See below for an even simpler approach 👇

@banjankri banjankri mentioned this issue Apr 14, 2019
0 of 12 tasks complete
@gkalpak

This comment has been minimized.

Copy link
Member

@gkalpak gkalpak commented Apr 15, 2019

I just realized it is much easier to do this with a helper function as shown in #16695 (comment) (credits to @banjankri) 😁
Here is a simpler (marginally future-safer) implementation (demo):

const allowAttribute = directiveFactory => [
  '$injector',
  ($injector: angular.auto.IInjectorService) =>
    Object.assign($injector.invoke(directiveFactory), {restrict: 'EA'}),
];

// Use it like this:
// .directive('someSelector', allowAttribute(downgradeComponent({...})))


// Or event make a `downgradeAttributeComponent()` helper:
const downgradeAttributeComponent = info => allowAttribute(downgradeComponent(info));

// Use it like this:
// .directive('someSelector', downgradeAttributeComponent({...}))
@ajitsinghkaler

This comment has been minimized.

Copy link
Contributor

@ajitsinghkaler ajitsinghkaler commented Nov 29, 2019

@gkalpak is the workaround the final thing or we are working on this further. If it's final we can close it.

@gkalpak

This comment has been minimized.

Copy link
Member

@gkalpak gkalpak commented Dec 1, 2019

@ajitsinghkaler, this is a feature request. I am not against implementing it , but it is low priority.
(Therefore it is marked as hotlist: community-help - also see #16695 (comment).)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.