Skip to content

OnInit is not respecting inheritance #6781

Closed

Description

@trinarytree

Suppose you have this class hierarchy
A
|
B
and A implements OnInit but B does not explicitly declare that it implements OnInit, only that it extends A. Then angular will (incorrectly) not call ngOnInit when it creates an instance of B.

Activity

  1. PatrickJS commented on Jan 30, 2016

    @PatrickJS
    Contributor

    are you calling super.ngOnInit?

    github-tipe-logo

  2. zoechi commented on Jan 30, 2016

    @zoechi
    Contributor

    If B doesn't have (override) ngOnInit() there is no place where super.ngOnInit() could be called.

  3. PatrickJS commented on Jan 30, 2016

    @PatrickJS
    Contributor

    This is the setup correct?

    import {bootstrap} from 'angular2/platform/browser';
    import {Component, OnInit} from 'angular2/core';
    
    class B {
      ngOnInit() {
        console.log('B ngOnInit');
      }
    }
    
    @Component({ selector: 'a-b', template: 'AB' })
    class A extends B implements OnInit {
      constructor() {
        super();
      }
      ngOnInit() {
        super.ngOnInit(); // call B's ngOnInit
        console.log('A ngOnInit');
      }
    }
    
    bootstrap(A);

    github-tipe-logo

  4. zoechi commented on Jan 30, 2016

    @zoechi
    Contributor

    @gdi2290 > but B does not explicitly declare that it implements OnInit, only that it extends A

    Ok, so you mean this only refers to implements OnInit. I had the impression that this isn't necessary in TS anyway for lifecycle methods to work (in Dart which I use, implements OnInit is mandatory).

    @trinarytree can you please confirm?

  5. robwormald commented on Jan 31, 2016

    @robwormald
    Contributor

    i can't reproduce this at all. please provide a plunker reproduction? See https://plnkr.co/edit/f3kcWBPF64IRCGUmUKPF?p=preview for a functioning version.

    Note that implements OnInit is purely a compile time construct, it has no effect during runtime (in TS)

  6. trinarytree commented on Jan 31, 2016

    @trinarytree
    Author

    I'm amazed at how many comments this got between fri 22:52 and sat. I don't normally work so much on saturday morning. :-)

    @gdi2290

    are you calling super.ngOnInit?

    No. A already implements ngOnInit, and in my case B is fine with inheriting A's version.

    This is the setup correct?

    No. It looks like this (in Dart):

    abstract class A implements OnInit {
      @override
      void ngOnInit() {
        print('A#ngOnInit');  // This never gets called, but should.  Hence the bug report.
      }
    }
    
    @Component(
        selector: 'b',
        template: 'angular should but does not call my ngOnInit'
    )
    class B extends A {}

    I suspect that this bug only happens when Angular compiles Dart code - it might not show up in TypeScript. Imagine what would happen if the code transformer used reflection incorrectly, asking whether B explicitly declared that it implements OnInit instead of asking whether it's a subclass.

  7. zoechi commented on Jan 31, 2016

    @zoechi
    Contributor

    Could you please try to add implements OnInit to B and report back if this makes it work?

  8. thorstenhans commented on Feb 1, 2016

    @thorstenhans

    having the same issue here, giving the advice of @zoechi a chance now

  9. thorstenhans commented on Feb 1, 2016

    @thorstenhans

    for me the following combination works in beta2

    // file base.ts
    import {OnInit} from 'angular2/core';
    
    export abstract class BaseComponent implements OnInit{
    
      ngOnInit(){
        console.log('base init');
      }
    }
    
    // file child.ts
    import {Component, OnInit, Input,ChangeDetectionStrategy} from 'angular2/core';
    import {BaseComponent} from './base';
    
    @Component({
        selector: 'child-1',
        templateUrl: 'app/components/children/child.html'
    })
    export class Child1Component extends BaseComponent{
        constructor(){
            super()
        }
    }
    

    Also when I remove abstract it keeps working
    Removing the constructor of the ChildComponent has also no side effects

  10. trinarytree commented on Feb 1, 2016

    @trinarytree
    Author

    If you add implements OnInit to B, then, when Angular creates an instance of B, yes it will call A's ngOnInit, but this isn't what the bug is about. The problem is when B does not make such a declaration but relies upon the fact that B extends A, which implements OnInit. In that case, B is a subtype of OnInit, i.e. if b is an instance of B, then b is OnInit is true.
    The behavior I get in Dart differs from what I get in TypeScript (plunker example).
    The TypeScript behavior is correct, the Dart behavior is wrong.

    When I search the Angular2 code, I can't find any references to isSubtypeOf, which I would expect if this were to work correctly. I do however see what looks like a somewhat buggy use of superinterfaces (you can see that _interfacesFromMirror ignores the case when the given class has an interface I1 that implements an interface I2). Anyway, that doesn't yet fully explain the bug at hand. I can't follow the code that decides whether to call ngOnInit - it has something to do with ProtoRecord, which has no docs.

  11. kegluneq commented on Feb 1, 2016

    @kegluneq

    This is the intended behavior of Dart's compiled output - you must explicitly declare that B implements OnInit. I'll make sure this is surfaced in the documentation - sorry for the unexpected + undocumented behavior.

    This was a design decision to keep the build process fast. Supporting this would mean that changes to an arbitrary file base.dart could affect the actions taken at compile time for class subclass.dart - we effectively need knowledge of the full application to make the work.

  12. kegluneq commented on Feb 2, 2016

    @kegluneq

    I got a recommendation to add a dev-mode-only check to throw if it the user has not added the requried implements On*, and I think this is a great idea.

    Spun off #6849 to track.

  13. zoechi commented on Feb 9, 2016

    @zoechi
    Contributor

    @kegluneq #4222 seems to tell the opposite

  14. kegluneq commented on Feb 9, 2016

    @kegluneq

    That's correct, #4222 will be reverted and I will add some better comments & documentation explaining why things work the way they do. https://goo.gl/b07Kii is one initial piece, and I'm in contact with the folks who are writing the Dart docs on angular.io to ensure that this behavior is well documented.

  15. zoechi commented on Feb 9, 2016

    @zoechi
    Contributor

    @kegluneq that's real sad. That makes inheritance and mixins pretty much useless.

  16. kegluneq commented on Feb 9, 2016

    @kegluneq

    I agree that it's sad 😞

    I disagree that it makes inheritance & mixins useless. They do require additional boilerplate to function the way you expect, and this does violate encapsulation, but they can still be used effectively.

  17. zoechi commented on Feb 10, 2016

    @zoechi
    Contributor

    @kegluneq
    If I only have to add implements SomeLifecycle then it isn't too bad.
    It also depends how #6953 will turn out.

  18. Tomucha commented on Feb 11, 2016

    @Tomucha

    I just run into this one too (in Dart).

    @kegluneq Please reconsider this:
    This is the intended behavior of Dart's compiled output - you must explicitly declare that B implements OnInit. I'll make sure this is surfaced in the documentation - sorry for the unexpected + undocumented behavior.

    I would say that your "intended bahavior" violates basic OOP contracts :-/ If "A extends B" and "B is OnInit" then "A is OnInit" too.

    Real world example: Imagine backend/admin application, with global menu and big area for main content (Google Cloud Console, for example). I would create "BaseContentComponent" and all main screens would inherit from it: "MessagesContentComponent extends BaseContentComponent".

    BaseContentCompoment declares what is "a content" - it has a headline, it has a sub-menu and probably it has some lifecycle callback, let's say BaseContentComponent implements OnInit to do some generic stuff.

    Now I have to declare "MessagesContentComponent extends BaseContentComponent implements OnInit" ... I don't like it, but I can do it. Now let's say I create 50 content components like this. And then I decide to add new lifecycle callback to BaseContentComponent - for example OnDestroy I want to put each visited screen into "history/shortcuts" panel. Now I have to go through all 50 content components to add OnDestroy? Noooooooooooo ...

  19. kegluneq commented on Feb 13, 2016

    @kegluneq

    @Tomucha , I agree with you and am working toward a world where this isn't the case. I wrote a doc to explain the state of things right now and the direction we're heading in to solve this issue.

  20. Tomucha commented on Feb 15, 2016

    @Tomucha

    @kegluneq Great, thanks!

  21. pkozlowski-opensource commented on Oct 4, 2016

    @pkozlowski-opensource
    Member

    Let's centralize discussion on inheritance in #11606. Closing as a duplicate.

  22. johnnytshi commented on Aug 16, 2017

    @johnnytshi

    The master issue is closed

    Is this now fixed for AOT? Or is there another issue tracking OnInit in case of AOT?

  23. yubaoquan commented on Mar 25, 2019

    @yubaoquan

    I also run into this, with typescript 3.3.3. The ngOnInit of subclass won't get called if I omit a declaration of ngOnInit. So in every subclass I have to add implements OnInit and

    ngOnInit() {
      super.ngOnInit();
    }
    

    This is pretty rediculous.
    What's more, this bug doesn't appear in develop mode, but in production packed environment. I Think I should create another issue.

  24. NickStrupat commented on May 1, 2019

    @NickStrupat

    is the minification to blame?

  25. angular-automatic-lock-bot commented on Sep 15, 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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions