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

Ivy: Input fields inherited from abstract class cause runtime exception #32428

Closed
Enngage opened this issue Aug 31, 2019 · 8 comments
Closed
Assignees
Labels
area: compiler Issues related to `ngc`, Angular's template compiler freq1: low needs reproduction This issue needs a reproduction in order for the team to investigate further type: bug/fix
Projects
Milestone

Comments

@Enngage
Copy link

Enngage commented Aug 31, 2019

Hey,

Just tried 9.0.0-next.4 and it seems that when you have an abstract component class:

export abstract class BaseEditFormComponent {
    @Input() itemId?: number;
}

And then you inherit it by specific components:

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    selector: 'edit-form',
    templateUrl: 'edit-form.component.html'
})
export class EditFormComponent extends BaseEditFormComponent {
    constructor() {
        super();
    }
}

And use it like:

<edit-form [itemId]="itemIdProp"></edit-form>

You get runtime exception such as

global-error.handler.ts:59 Application encountered an error:  Error: Template error: Can't bind to 'itemId' since it isn't a known property of 'edit-form'.
    at createUnknownPropertyError (core.js:12888)
    at validateAgainstUnknownProperties (core.js:12827)
    at elementPropertyInternal (core.js:12732)
    at ɵɵproperty (core.js:19801)
    at EditFormComponent(template.html:2)
    at executeTemplate (core.js:12315)
    at refreshView (core.js:12179)
    at refreshComponent (core.js:13484)
    at refreshChildComponents (core.js:11914)
    at refreshView (core.js:12238)

Adding itemId property to EditFormComponent component fixes the issue and app runs as expected.

Is such behavior expected?

🌍 Your Environment

Angular Version:


Angular CLI: 8.3.2
Node: 10.16.0
OS: win32 x64
Angular: 9.0.0-next.4
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... platform-server, router, service-worker

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.2
@angular-devkit/build-angular     0.803.2
@angular-devkit/build-optimizer   0.803.2
@angular-devkit/build-webpack     0.803.2
@angular-devkit/core              8.3.2
@angular-devkit/schematics        8.3.2
@angular/cdk                      8.1.4
@angular/cli                      8.3.2
@angular/flex-layout              8.0.0-beta.27
@angular/material                 8.1.4
@ngtools/webpack                  8.3.2
@schematics/angular               8.3.2
@schematics/update                0.803.2
rxjs                              6.5.2
typescript                        3.5.3
webpack                           4.39.2

@ngbot ngbot bot added this to the needsTriage milestone Sep 1, 2019
@AndrewKushnir
Copy link
Contributor

Hi @Enngage,

With Ivy, base classes should also be annotated using @Directive() decorator. There is a migration that should help transition existing code to the new pattern.

Please try to add the @Directive() decorator to the base class and let us know if that fixes the problem.

Thank you.

@AndrewKushnir AndrewKushnir self-assigned this Sep 3, 2019
@AndrewKushnir AndrewKushnir added the area: core Issues related to the framework runtime label Sep 3, 2019
@Enngage
Copy link
Author

Enngage commented Sep 4, 2019

Hi @AndrewKushnir,

Tried adding Directive to my base class such as:

@Directive()
export abstract class BaseEditFormComponent {
    @Input() itemId?: number;
}

But then my app won't start due to this error :/

Error: Uncaught (in promise): Error: Dependency array must have arguments.
Error: Dependency array must have arguments.
    at reflectDependency (core.js:16144)
    at core.js:16117
    at Array.map (<anonymous>)
    at convertDependencies (core.js:16113)
    at reflectDependencies (core.js:16104)
    at directiveMetadata (core.js:38066)
    at getDirectiveMetadata (core.js:38036)
    at Function.get (core.js:38009)
    at getDirectiveDef (core.js:1812)
    at addBaseDefToUndecoratedParents (core.js:38094)
    at resolvePromise (zone-evergreen.js:793)
    at resolvePromise (zone-evergreen.js:752)
    at zone-evergreen.js:854
    at ZoneDelegate.invokeTask (zone-evergreen.js:400)
    at Object.onInvokeTask (core.js:39829)
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at Zone.runTask (zone-evergreen.js:168)
    at drainMicroTaskQueue (zone-evergreen.js:570)
    at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:485)
    at invokeTask (zone-evergreen.js:1596)

Not sure if that is relevant, but I do have dependencies injected into my component which are passed to base class via super call.

@alxhub alxhub added area: compiler Issues related to `ngc`, Angular's template compiler freq1: low severity3: broken type: bug/fix and removed area: core Issues related to the framework runtime labels Sep 5, 2019
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Sep 5, 2019
@alxhub alxhub added this to Backlog in Compiler via automation Sep 5, 2019
@AndrewKushnir
Copy link
Contributor

Hi @Enngage,

I tried to reproduce the problem locally, but failed. Here is my TestBed test (which is passing):

  it('should call ngOnInit before ngDoCheck if creation mode', () => {

    @Injectable()
    class MyService {}

    @Directive()
    abstract class BaseEditFormComponent {
      @Input() itemId?: number;
      constructor(service: MyService) {}
    }

    @Component({
      selector: 'edit-form',
      template: '...',
    })
    class EditFormComponent extends BaseEditFormComponent {
      constructor(service: MyService) { super(service); }
    }

    @Component({
      template: '<edit-form [itemId]="itemIdProp"></edit-form>',
    })
    class App {
      itemIdProp: string = 'id';
    }

    TestBed.configureTestingModule({
      declarations: [App, EditFormComponent],
      providers: [MyService],
    });

    expect(() => {
      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
    }).not.toThrow();
  });

Could you please have a look at the test and let me know how it should be changed to better mimic your use-case and repro the problem?

Thank you.

@AndrewKushnir AndrewKushnir added the needs reproduction This issue needs a reproduction in order for the team to investigate further label Sep 13, 2019
@Ristaaf
Copy link

Ristaaf commented Oct 17, 2019

I have the same problem (in our giant codebase with lots of abstract component classes)

Not sure why the testbed above works, but this is my simplest possible reproduction:

Using @angular/cli@next I do an ng new and then:

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<car [topSpeed]="100"></car>'
})
export class AppComponent {
}

car.component.ts

import { Component, Directive, Input } from '@angular/core';

@Directive()
export abstract class VehicleAbstract {
    @Input()
    topSpeed;
}

@Component({
    selector: 'car',
    template: '<div>Car</div>'
}) export class CarComponent extends VehicleAbstract {
}

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { CarComponent } from './car.component';

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

And then run ng build
Without ivy it builds fine, with ivy I get:

ERROR in src/app/app.component.ts:5:19 - error TS8002: Can't bind to 'topSpeed' since it isn't a known property of 'car'.

5   template: '<car [topSpeed]="100"></car>'

@AndrewKushnir
Copy link
Contributor

@Ristaaf thanks for additional information. It looks like PR #33131 should fix this problem. Thank you.

@alxhub
Copy link
Member

alxhub commented Oct 28, 2019

This was resolved in #33131 :D

@alxhub alxhub closed this as completed Oct 28, 2019
@Enngage
Copy link
Author

Enngage commented Nov 1, 2019

@AndrewKushnir, thanks! Yeah, that indeed fixed the issue I initially raised but wasn't able to easily reproduce due to the huge code base I have.

Though it raised another issue. I'm looking at https://next.angular.io/guide/migration-undecorated-classes which explains the change and it makes sense.

However, the compiler failes with

global-error.handler.ts:57 Application encountered an error:  Error: Uncaught (in promise): Error: Dependency array must have arguments.
Error: Dependency array must have arguments.
    at reflectDependency (core.js:17416)
    at core.js:17389
    at Array.map (<anonymous>)
    at convertDependencies (core.js:17385)
    at reflectDependencies (core.js:17376)
    at directiveMetadata (core.js:38922)
    at getDirectiveMetadata (core.js:38866)
    at Function.get (core.js:38843)
    at getDirectiveDef (core.js:1822)
    at addDirectiveDefToUndecoratedParents (core.js:38950)
    at resolvePromise (zone-evergreen.js:793)
    at resolvePromise (zone-evergreen.js:752)
    at zone-evergreen.js:854
    at ZoneDelegate.invokeTask (zone-evergreen.js:400)
    at Object.onInvokeTask (core.js:40655)
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at Zone.runTask (zone-evergreen.js:168)
    at drainMicroTaskQueue (zone-evergreen.js:570)
    at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:485)
    at invokeTask (zone-evergreen.js:1596)

If you have a constructor with dependencies that cannot be resolved. In my case I have constructor in my abstract class like:

   constructor(protected dependencies: ComponentDependencyService, protected cdr: ChangeDetectorRef, config: {
      service: BaseTypeService<TItem>
    }) {
    }

dependencies and cdr are resolved correctly, but the config isn't because child class explicitely sets config in the super call.

I'm guessing that ignoring that property would work? Is there a way to ignore it?

Edit:

Seems like using Optional() does the trick :-)

@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 Dec 2, 2019
@alxhub alxhub moved this from Backlog to Done in Compiler Jan 23, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: compiler Issues related to `ngc`, Angular's template compiler freq1: low needs reproduction This issue needs a reproduction in order for the team to investigate further type: bug/fix
Projects
Development

No branches or pull requests

5 participants