Skip to content

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

Closed

Description

@Enngage

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

Activity

  1. added this to the needsTriage milestone on Sep 1, 2019
  2. AndrewKushnir commented on Sep 3, 2019

    @AndrewKushnir
    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.

  3. self-assigned this
    on Sep 3, 2019
  4. Enngage commented on Sep 4, 2019

    @Enngage
    Author

    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.

  5. modified the milestones: needsTriage, Backlog on Sep 5, 2019
  6. AndrewKushnir commented on Sep 13, 2019

    @AndrewKushnir
    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.

  7. added
    needs reproductionThis issue needs a reproduction in order for the team to investigate further
    on Sep 13, 2019
  8. Ristaaf commented on Oct 17, 2019

    @Ristaaf

    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>'
    
  9. AndrewKushnir commented on Oct 17, 2019

    @AndrewKushnir
    Contributor

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

  10. alxhub commented on Oct 28, 2019

    @alxhub
    Member

    This was resolved in #33131 :D

  11. Enngage commented on Nov 1, 2019

    @Enngage
    Author

    @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 :-)

  12. angular-automatic-lock-bot commented on Dec 2, 2019

    @angular-automatic-lock-bot

    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.

  13. locked and limited conversation to collaborators on Dec 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: compilerIssues related to `ngc`, Angular's template compilerfreq1: lowneeds reproductionThis issue needs a reproduction in order for the team to investigate furthertype: bug/fix

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions