[go: nahoru, domu]

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

Custom decorators not working using AoT and Ivy #31495

Closed
robisim74 opened this issue Jul 10, 2019 · 61 comments
Closed

Custom decorators not working using AoT and Ivy #31495

robisim74 opened this issue Jul 10, 2019 · 61 comments
Labels
area: core Issues related to the framework runtime core: lifecycle hooks freq1: low regression Indicates than the issue relates to something that worked in a previous version state: confirmed type: bug/fix
Milestone

Comments

@robisim74
Copy link
Contributor
robisim74 commented Jul 10, 2019

🐞 bug report

Affected Package

The issue is caused by package @angular/compiler-cli (I guess)

Is this a regression?

I had never tried AoT with Ivy

Description

I have a custom property decorator like this:
export function Language(): PropertyDecorator {
    function DecoratorFactory(target: any): void {
        const targetNgOnInit: () => void = target.ngOnInit;
        function ngOnInit(this: any): void {
            
            // Just for example
            console.log('onInit');

            if (targetNgOnInit) {
                targetNgOnInit.apply(this);
            }
        }
        target.ngOnInit = ngOnInit;
    }
    return DecoratorFactory;
}

used as:

@Language() lang: string;

ngOnInit() {
  // For aot
}
  • With AoT compiler, it works (it writes onInit in console)
  • If I enable Ivy and disable AoT, it works
  • If I enable Ivy and AoT, it doesn't work

🔬 Minimal Reproduction

angular-ivy-decorators

🔥 Exception or Error

No errors during compilation or in console. The ngOnInit method in decorator function is ignored.

🌍 Your Environment

Angular Version:


Angular CLI: 8.1.1
Node: 10.14.2
OS: win32 x64
Angular: 8.1.1
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.801.1
@angular-devkit/build-angular     0.801.1
@angular-devkit/build-optimizer   0.801.1
@angular-devkit/build-webpack     0.801.1
@angular-devkit/core              8.1.1
@angular-devkit/schematics        8.1.1
@ngtools/webpack                  8.1.1
@schematics/angular               8.1.1
@schematics/update                0.801.1
rxjs                              6.4.0
typescript                        3.4.5
webpack                           4.35.2

Anything else relevant?
Tried without success also the next version of Angular.

Thanks

@JoostK

This comment has been minimized.

@robisim74
Copy link
Contributor Author

I had not noticed the removal of emitDecoratorMetadata. However, no, it still doesn't work.

@AndrewKushnir AndrewKushnir added area: compiler Issues related to `ngc`, Angular's template compiler comp: ivy labels Jul 10, 2019
@ngbot ngbot bot added this to the needsTriage milestone Jul 10, 2019
@alexzuza
Copy link
Contributor

Appears the reason here is that Angular places ngComponentDef generated code before all __decorate calls for class. ɵɵdefineComponent function takes onInit as a reference to Class.prototype.ngOnInit and overridding it later has no any effect.

onInit: typePrototype.ngOnInit || null,

dec

Here's a pseudo-code of what happens:

class Test {
  ngOnInit() {
    console.log('original')
  } 
}

const hook = Test.prototype.ngOnInit;

Test.prototype.ngOnInit = () => {
  console.log(1);
}

hook(); // prints 'original'

In case of JIT compilation componentDef is created lazily as soon as getComponentDef gets called. It happens later.

@alxhub
Copy link
Member
alxhub commented Jul 16, 2019

Yeah, this is unfortunately WAI.

Angular's compiler btw has no say in the location of ngComponentDef vs the __decorate call - this is the way TypeScript naturally compiles static fields. See this example. The time at which the lifecycle hook is read off the class is an implementation detail of the runtime, and the way we do things now in Ivy is an optimization.

@ngbot ngbot bot modified the milestones: needsTriage, Backlog Jul 16, 2019
@alxhub alxhub added area: core Issues related to the framework runtime and removed area: compiler Issues related to `ngc`, Angular's template compiler labels Jul 23, 2019
@alxhub
Copy link
Member
alxhub commented Jul 23, 2019

Transferring this to core as there's nothing we can do on this from the compiler side - the behavior in question is the snapshotting of ngOnInit prior to decorator execution.

@jase88
Copy link
Contributor
jase88 commented Oct 5, 2019

Any update on this issue?
Is there a workaround so far?

@stupidawesome
Copy link
stupidawesome commented Nov 5, 2019

Given a class decorator

function Mixin(value) {
    return function(target) {
        target.prototype.ngOnInit = function () {
            console.log("ngOnInit", value)
        }
    }
}

@Component({ ... })
@Mixin({ value: 123 })
export class AppComponent {}

This workaround should work in Ivy

const Extend = Mixin({ value: 123 })

@Component({ ... })
export class AppComponent {
    static mixins = Extend(AppComponent)
}

For ViewEngine compatibility, you also need to add a stub for ngOnInit

@Component({ ... })
export class AppComponent {
    public ngOnInit?()
    static mixins = Extend(AppComponent)
}

Assigning to a static property on the class forces the mixin to be executed before the runtime snapshots the class.

@kumaresan-subramani

This comment has been minimized.

@JoostK
Copy link
Member
JoostK commented Nov 18, 2019

@kumaresan-subramani From within your empty lifecycle methods, you should delegate into the mixin field.

@kumaresan-subramani
Copy link
kumaresan-subramani commented Dec 3, 2019

Hi @JoostK ,

Any update on this?

I have tried with latest v9.0.0-rc.4 but still not working

@kumaresan-subramani
Copy link

HI @JoostK ,

Can you please provide timeline for this issue?

@Splaktar
Copy link
Member

@kumaresan-subramani the team doesn't normally provide ETAs or timelines for issue resolution. Based on the milestone, this is not being considered for version 9 final. It's possible that it could go into a 9.x minor or patch, be pushed to version 10, or it could be pushed even later.

If you feel like the priority needs to be raised, please provide a clear use case that demonstrates a significant issue that does not have a workaround. If this is a problem for a Syncfusion library, please explain the issue, impact, and workarounds attempted.

@kumaresan-subramani
Copy link
kumaresan-subramani commented Jan 27, 2020

Appears the reason here is that Angular places ngComponentDef generated code before all __decorate calls for class. ɵɵdefineComponent function takes onInit as a reference to Class.prototype.ngOnInit and overridding it later has no any effect.

onInit: typePrototype.ngOnInit || null,

dec

Here's a pseudo-code of what happens:

class Test {
  ngOnInit() {
    console.log('original')
  } 
}

const hook = Test.prototype.ngOnInit;

Test.prototype.ngOnInit = () => {
  console.log(1);
}

hook(); // prints 'original'

In case of JIT compilation componentDef is created lazily as soon as getComponentDef gets called. It happens later.

reason - #30818 (comment)

Hi @Splaktar , you can find reasons above

Workaround attempted

#31495 (comment)
#31495 (comment)

@abadakhshan
Copy link
Contributor
abadakhshan commented Apr 17, 2020

@JoostK

Thank you.
#35314 is closed, but I think the problem still exists.

My scenario is exactly like this

Add dynamic components to EntryComponents does not work anymore.

What should we do in this situation?

@JoostK
Copy link
Member
JoostK commented Apr 17, 2020

@abadakhshan Please see #35314 (comment) for how to resolve the problem. The issue is closed because the framework is working as intended.

@willisd2
Copy link

It appears the issue I was running into was the same one that @abadakhshan describes here. My app makes heavy use of dynamically instantiated components that often times aren't referenced by the app other than in the NgModule declarations. It appears the reason my custom decorator isn't working is because those components are being tree-shaken from the build. So in my situation this isn't a defect of angular, but rather an education on Ivy...

@vytautas-pranskunas-
Copy link

Any updates on this? We cannot move our project to ng9.

@acn-masatadakurihara
Copy link

This is a truly awful problem. Please fix it quickly as the version up of the internal project cannot be done.

@acn-masatadakurihara
Copy link

#37068 (comment)

@petebacondarwin
Copy link
Member

Please take a look at #35464. Hopefully this might resolve the issue for you?

@shadow1349

This comment has been minimized.

@tinganho

This comment has been minimized.

@ramtob
Copy link
ramtob commented Jun 14, 2020

See above, there is a PR on this problem.

@khiami
Copy link
khiami commented Jun 27, 2020

Any update after v10 is out?

@hakimio
Copy link
hakimio commented Jun 27, 2020

@khiami you can track progress in PR #35464

@TinyMan
Copy link
Contributor
TinyMan commented Jul 3, 2020

@Splaktar @pkozlowski-opensource Hello, it was written above that we need to specify a use case in order to increase the priority of this issue. Well as you can see, a typical use case is for decorators which override component life-cycle hooks handlers (ngOnInit, ngOnDestrory) in order to more easily and safely subscribe to and unsubscribe from various streams. As you can see above, this feature has been in demand. And it has ceased to work in Ivy. This is not a matter of "regression", but of (significant) "convenience", and safely.
So can you raise the priority of this issue?
TIA

I beg to differ on this not being a regression. It worked in previous versions, Ivy breaks it - regression. Furthermore, Ivy gets in the way of JS nature/features, like overwriting the prototype method. Make no mistake, this is not limited to the decorators only.

Another popular example is a custom takeUntilDestroy rxjs operator, which is nothing else than a function accepting an instance of component class.

This however should not reduce the priority of this issue.

In my custom operator I ended up relying on private ɵcmp property instead of prototype, in hope Angular will eventually fix this:

if (instanceConstructor.ɵcmp) {
  // Using private API until the corresponding Angular issues are resolved.
  // Issues:  https://github.com/angular/angular/issues/31495, https://github.com/angular/angular/issues/30497
  // PR:      https://github.com/angular/angular/pull/35464
  destroy = <Function>instanceConstructor.ɵcmp.onDestroy ?? noop;
  instanceConstructor.ɵcmp.onDestroy = __takeUntilDestroy__destroy__;
} else {
  destroy = <Function>instanceConstructor.prototype.ngOnDestroy ?? noop;
  instanceConstructor.prototype.ngOnDestroy = __takeUntilDestroy__destroy__;
}

I found out that this workaround create errors in JIT if your decorator is after @Component() like this:

@CustomDecorator()
@Component({ ...})
export class Comp {}

because (my guess from debugging it) ɵcmp in JIT is a getter that compiles the component. But when the decorators are executed the compiler is not ready (I think imported modules are not yet availables, as I had errors on ngIf not being an input on div).

So the workaround is to check that we are in AOT before getting the ɵcmp property :

export function getLifecycleHook(instanceConstructor: Function, hook: 'ngOnDestroy' | 'ngOnInit' | 'ngOnChanges') {
	const desc = Object.getOwnPropertyDescriptor(instanceConstructor, ɵNG_COMP_DEF);
	if (!!desc && !desc.get) { // check for ivy AND aot
		// Using private API until the corresponding Angular issues are resolved.
		// Issues:  https://github.com/angular/angular/issues/31495, https://github.com/angular/angular/issues/30497
		// PR:      https://github.com/angular/angular/pull/35464
		return instanceConstructor[ɵNG_COMP_DEF]['o' + hook.slice(3)];
	} else {
		return instanceConstructor.prototype[hook];
	}
}

And this works both in AOT and JIT.

Also if you put your decorators after @Component() (so that yours is executed first), it works because ɵcmp is not yet defined.

Needed this because AOT is still unusable for development with large project.

@wsdt
Copy link
wsdt commented Aug 20, 2020

Using custom decorators with AoT and Ivy is also high priority for us.

@hakimio
Copy link
hakimio commented Aug 20, 2020

@wsdt This was already fixed in Angular v10.0.5.

@wsdt
Copy link
wsdt commented Aug 20, 2020

@hakimio Thank you ! :-)

@kumaresan-subramani
Copy link

Appears the reason here is that Angular places ngComponentDef generated code before all __decorate calls for class. ɵɵdefineComponent function takes onInit as a reference to Class.prototype.ngOnInit and overridding it later has no any effect.

onInit: typePrototype.ngOnInit || null,

dec
Here's a pseudo-code of what happens:

class Test {
  ngOnInit() {
    console.log('original')
  } 
}

const hook = Test.prototype.ngOnInit;

Test.prototype.ngOnInit = () => {
  console.log(1);
}

hook(); // prints 'original'

In case of JIT compilation componentDef is created lazily as soon as getComponentDef gets called. It happens later.

reason - #30818 (comment)

Hi @Splaktar , you can find reasons above

Workaround attempted

#31495 (comment)
#31495 (comment)

HI @hakimio ,

I think issue not get fixed still facing same issue.

My angular packages:

dependencies": {
    "@angular/animations": "~10.0.9",
    "@angular/common": "~10.0.9",
    "@angular/compiler": "~10.0.9",
    "@angular/core": "~10.0.9",
    "@angular/forms": "~10.0.9",
    "@angular/platform-browser": "~10.0.9",
    "@angular/platform-browser-dynamic": "~10.0.9",

@hakimio
Copy link
hakimio commented Aug 20, 2020

@kumaresan-subramani just use ngx-observable-lifecycle if you can not get it to work.

@robisim74
Copy link
Contributor Author

As reported by @hakimio, after over a year this issue was fixed in Angular v10.0.5. If there were other problems, I think more specific issues should be open, and therefore I am closing it.

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 26, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime core: lifecycle hooks freq1: low regression Indicates than the issue relates to something that worked in a previous version state: confirmed type: bug/fix
Projects
None yet
Development

No branches or pull requests