How Do We Detect Component Initialization? [SOLVED]

For example, I want to disable swipe on my side menu, but this can’t be done until the side menu’s ngOnInit method has fired.

@brandyshea I saw you gave this example in issue 5390

import {MenuController} from 'ionic/ionic';

constructor(menu: MenuController) { this.menu = menu; let menuOpen = this.menu.get().isOpen; console.log( menuOpen ); }

In my experience, the MenuController is useless in the constructor of the page, because the menu components have not yet registered themselves with the MenuController. I’ve tested onPageLoaded and onPageDidEnter lifecycle hooks too, and they are no good. The only thing that works for me is to create a setTimeout and wait a few ticks for the MenuController to have its _menu array populated with the menu components on the page.

Would love some advice on this. Seems like a fundamental requirement.Thanks!

I should note that my page contains some very heavy DOM initialization (canvas tag with expensive GPU operations). This might make my page take a lot longer to initialize than a regular page, which is why I really need to explicitly hook into component init events.

This is what I’m currently doing (Ionic 2, beta 4):

import {MenuController} from 'ionic-angular';
import {AppPage} from '../../util/app.page.class';

@Page({
    templateUrl: 'build/pages/adventure/mypage.html'
})
export class MyPage extends AppPage {

    static get parameters() {
        return [[MenuController]];
    }

    constructor(menuCtrl) {
        super();
        this.menuCtrl = menuCtrl;  

        //Expensive DOM init starts here      
    }

    onPageDidEnter() {
        //doesn't work :(
        this.menuCtrl.swipeEnable(false, 'my-menu');
    }

    onPageLoaded() {
        //doesn't work :(
        this.menuCtrl.swipeEnable(false, 'my-menu');

        setTimeout(()=>{
            //Works! :)
            this.menuCtrl.swipeEnable(false, 'my-menu');
        }, 1000);        
    }
}

You might want to look at afterViewInits

https://angular.io/docs/ts/latest/api/core/AfterViewInit-interface.html

1 Like

this called life cycle of an component/directives.

in angular 2 you have life cycle hooks you can import from angular2/core

This are interfaces you can implement in your component class.
If you want to wait until all subviews (child components are ready and their dom are rendered, too) use mikes approach AfterViewInit.

import {OnAfterViewInit} from 'angular2/core';

@component({...})
export class test implements OnAfterViewInit {
  ngOnAfterViewInit() {
     // DO IT
  }
}

I did not test this code but i think you can get an idea how it works.

Thanks @mhartington, that’s exactly what I need!

@bengtler thanks, but it turns out you don’t need to import or implement the interface when using JavaScript (maybe you need to for TypeScript, I’m not sure).

All I had to do was add this to my class and it gets correctly called after the view is fully initialized.

ngAfterViewInit() {
    this.menuCtrl.swipeEnable(false, 'my-menu');
}

It’s good practice anyway. That way static code analysis can warn you if you make stupid capitalization or spelling mistakes in the method name.

@rapropos is it possible to implement interfaces using JavaScript? From my initial research, es6 doesn’t support interfaces, only TypeScript does.

I believe that interface and implements are considered “future reserved words” by ES6, so they would probably error in strict mode. I’m not sure what would happen in non-strict mode.

Hello, I am observing that AfterViewInit() is only firing once, however my child components are getting recreated based on some visibility toggles on the parent page and *ngIf

I am not able to detect when the child components I have instance to via @ViewChild are ready to connect my interface to the currently visible child component.

I think it’s a side effect of using ngIf which I had read years back it deletes and reconstructs the DOM. If I use [hidden] I think it’s going to work.
But I am still finding it hard to implement a simple re-connection of the interface instance variable to the active child in order to call the same methods across a bunch of objects implementing the interface.
Something I do all the time in CSharp.
I though about emitting some Ready.next() from the constructors of the child components, but it does not feel correct.

    /** - 20200228 */
    ReconnectInterfaces() {
        // Connect the needed interfce
        if (this.ViewMode === ViewModeValues.ByNone)
            this.CollapseExpand_I = this.GroupedByChapter

        if (this.ViewMode === ViewModeValues.ByType) {
            this.CollapseExpand_I = this.GroupedByType
        }
        if (this.ViewMode === ViewModeValues.ByDate) {
            this.CollapseExpand_I = this.GroupedByDate
        }
        if (this.ViewMode === ViewModeValues.ByChapter) {
            this.CollapseExpand_I = this.GroupedByChapter
        }
    }
    /**
     * Get access to child instance to call its method from its parent view/code
     * - another approach could be using events
     * - 20200226
     * - https://angular.io/api/core/ViewChild 
     *  */
    @ViewChild('GroupedByChapter1') GroupedByChapter: GroupedByChapter
    @ViewChild('GroupedByDate1') GroupedByDate: GroupedByDate
    @ViewChild('GroupedByType1') GroupedByType: GroupedByType
/**
 * - 20200228
 */
export interface CollapseExpand_I {
    Collapse()
    Expand()    
}

Thank you for any pointers…

While it may very well be idiomatic in that language (which I’ve never touched, so no clue), I don’t think what I see here looks like idiomatic Angular at all. Can you back up and describe what you are really trying to achieve in completely non-coding terminology, like “I have three buttons. When the user clicks on the first one, I want the rest of the screen to show a list of chapters. When they click on the second one, I want that to be replaced by a list of articles sorted by date. When they click on the third one, I instead want to show articles by topic”, or something like that?

Unless you’re not actually asking for implementation/design input, in which case just ignore me and carry on merrily doing whatever you do.

Hi, so this is in the context of a bookmarksPage assigned to a tab.
The data in my bookmarks array of objects can be listed flat “List All”
or “By Chapter” or “By Date” … as shown in the snapshot below.
The bookmarksPage template (html) and code started to get too large to manage so I decided to factor out chunks of code and html into what seemed logical child components
ListAll.ts/html, GroupedByChapter.ts/html, GroupedByDate.ts/html and so on.

All of these child components as well as the parent bookmarksPage inject BookmarksService.ts to access the master bookmarks data.

The filter buttons are hosted at the bookmarksPage and I wanted to control all such child components via some interface(s) they implement.
The interfaces after getting connected to the correct child component instance would be used to perform common things across all of them like SortUp, SortDown, Expand, Collapse, Restore etc…

However when I try to reconnect the interfaces to the non active (hidden) child components (and because of me using *ngIf) I am observing they are still undefined This happen when I try to re-execute the following routine based on the selected view.

/**
     * Connect the needed interfaces 
     * - 20200228 */
    ReconnectInterfaces() {
        // 
        if (this.ViewMode === ViewModeValues.ByNone)
            this.CollapseExpand_I = undefined  // INOTE: not yet

        if (this.ViewMode === ViewModeValues.ByType) {
            this.CollapseExpand_I = this.GroupedByType
        }
        if (this.ViewMode === ViewModeValues.ByDate) {
            this.CollapseExpand_I = this.GroupedByDate
        }
        if (this.ViewMode === ViewModeValues.ByChapter) {
            this.CollapseExpand_I = this.GroupedByChapter
        }
    }

I will add more interfaces above as I go. The instances to the child components I obtained via @ViewChild are evaluating to undefined because they are getting recreated.

One of the benefit of using an interface is that the code behind the buttons like in the following collapse/expand would be simplified to using the interface if one exists and avoids using if-then-else(s) (aka switch statement)
like so:

    CollapseAll() {
        this.IsAllCollapsed_ = true
        if (this.CollapseExpand_I  !== undefined)
            this.CollapseExpand_I.Collapse()
    }
    ExpandAll() {
        this.IsAllCollapsed_ = false
        if (this.CollapseExpand_I  !== undefined)
            this.CollapseExpand_I.Expand()
    }
   SortUp(){ ... }
   SortDown(){ ... }

This is how I code it in the .NET world except here in this app I am dealing with Angular/Ionic ever changing implementations of Page/Components Lifecycles, which I can’t possibly keep abreast of nor their impact.

I am happy how the specifics to each of the views is localized in the corresponding child component code and template.
However, after factoring out the child components I realized, a bit too late, that ionic/angular does NOT extend the lifecycle hooks into inner components.
I don’t believe I have a case here for using @input @output (nothing to hand-in or out).

Hopefully I have explained enough and the snapshot is helpful.
I am open to doing it differently per your suggestion. FYI, I take whatever you post and other contributors like a black box and I keep apply it whether it be observables, promises, etc… until it breaks and then have to come back here for help :slight_smile: No matter, how hard I try to read I forget and it remains foreign to me. By the time the summer comes I am out hunting and that super clears all my memory blank and I even forget there was a CLI and that I need to start Gitbash :slight_smile: Then 3-4 months later around December I may come back into ionic programming and tell myself is this the last time I am going to touch code, the same is true with .NET, …old age :frowning:

Today, I tried to move from ionic 4.0 to 5 and that was OK but everything went haywire after upgrading to Angular 9 following to the migration to 5.0 video.
So I worked hard recovering a running state of this app.

Let me know if you need more clarifications.

Thanks a bunch for all your insights.

Update: BTW, I realized I should not leave ion-list tag at the bookmarkPage level but instead push it as a detail to the child component.
I also switched to using [hidden] instead of *ngIf and that fixes my issue of my child component getting destroyed. ReconnectInterfaces() works as shown above.

In the process I also converted to using dedicated (get) props and enumbs to return the user selected view into the template world, testing against strings with [hidden], like so
[hidden]="!ViewModeString=='ByDate'"
won’t work and would make it unnecessarily complex to express.

I am still curious how you would approach this, when I think there is still perhaps the possibility of my child components getting destroyed (perhaps by future implementation of ionic)
How would you code defensively for such a situation?
Also what if I decide to experiment with ionic/react in the future, I hate to corner myself into angular syntax.
BTW, I know almost nothing about ionic/react nor react.js :slight_smile:

Thanks.

Just to add more where I am headed with my interface(s) I now coded the following:

image

which allows me to hide the eventually 3 buttons Collapse/Expand/Restore which will not apply to the “List All” child component as it does not implement this particular interface.

Incidentally although I am using [hidden] here and there I have hard time locating any actual ionic documentation/references, it must be buried somewhere?.

I wish ionic also provided attribute [shown], I find [hidden] most of time to be very awkward expressing what I want. [shown] would be the same “polarity” as *ngIf :slight_smile:

to me writing

[hidden]="IsNotCollapsable"

is more natural/readable than writing and trying to think the logic behind

[hidden]=" ! IsCollapsable"

I “think” I understand the challenge of implementing [shown] attribute with regards to CSS.
Best of regards

Wow.

I’m hesitant to continue commenting, because you have clearly bought heavily in to the design pattern you’ve decided on.

If at some point, you decide you’re open to a (potentially massive) rearchitecting, I would suggest reading the property binding syntax section of the Angular template syntax overview first. Then, this post on an alternative paradigm to (ab)use of lifecycle events for propagating model changes across an app.

The fundamental problem I think you’re running into (and, believe me, I also bashed headfirst into this for literally months when I first started out with Angular) is an imperative mindset, whereas webapps are reactive in nature. You seem to be looking for ways for children to expose API endpoints for parents to call. Instead, the typical idiomatic approach in Angular would be to do this communication via @Input and @Output bindings declared by the children, so the parent pushes data instead of calling functions (on the child side, ngOnChanges can be used as a hook to know when these have changed) and reacts to events emitted from the children.

Again, as it seems you have a ton of inertia in this code base, it wouldn’t be particularly reasonable of me to expect you to drop everything you have so far, so I will leave things at this for now and see how you want to proceed.