Search code examples
angularjstypescriptlifecycleangularjs-material

Why doesn't $onInit() fire in an angular material dialog?


Background:
On a project I'm working on, we've adopted the Angular Material Design framework for the UI part of the system. We're using this in conjunction with AngularJS 1.6.2, and TypeScript 2.1.5.

One piece of advice we keep encountering, is to use the AngularJS lifecycle hooks, to be ready for when AngularJS gets deprecated in favor of Angular 2. Specifically, the lifecycle hooks are introduced to make it easier to upgrade, since these hooks - of which $onInit() is a member - are part of the purely component-based setup that is a main feature of Angular 2.

Problem:
We've found that, when you define a dialog controller that implements angular.IController, and has $onInit() defined with contents...those contents are not executed.

Problem Example TypeScript:

// Bootstrap Angular to our example.
class App {
    public constructor() {
        angular.module('app', ['ui.router', 'ngMaterial']);
    }
}

let app: App = new App();

// Set up routing for the example...
class RouteConfig {
    constructor(
        $stateProvider: angular.ui.IStateProvider,
        $urlRouterProvider: angular.ui.IUrlRouterProvider,
        $locationProvider: angular.ILocationProvider
    ){
        $stateProvider.state('Main', {
            url: '/Main',
            templateUrl: 'app/Main.html',
            controller: 'mainController'
        });

        $urlRouterProvider.otherwise('/Main');

        $locationProvider.html5Mode({
            enabled: true,
            requireBase: false
        });
    }
}

angular.module('app').config(['$stateProvider', '$urlRouterProvider', '$locationProvider', RouteConfig]);

// Controller for the page that will launch the modal...
class MainController implements angular.IController {
    public static $inject: string[] = ['$mdDialog'];
    public constructor(public $mdDialog: angular.IDialogService) {
    }

    public $onInit(): void {
        console.log('This works.');
    }

    public onButtonClick(): void {
        this.$mdDialog.show({
            controller: ModalController,
            controllerAs: '$ctrl',
            templateUrl: '/App/myModalTemplate.html', // Contents of modal not important.
            parent: angular.element(document.body),
            fullscreen: true
        })
            .then(() => console.log('Modal closed.'),
                  () => console.log('Modal cancelled.'));
    }
}

angular.module('app').controller('mainController', MainController);

// Controller for the actual modal that should be triggering $onInit.
class ModalController implements angular.IController {
    public static $inject: string[] = ['$mdDialog'];
    public constructor(public $mdDialog: angular.IDialogService) {
        alert('ModalController.ctor fired!');
    }

    public $onInit(): void {
        // PROBLEM!  This never actually executes.
        alert('$onInit fired on modal!');
    }

    public ok(): void {
        this.$mdDialog.hide();
    }
}

angular.module('app').controller('modalController', ModalController);

app/Main.html

<div ng-controller="mainController as $ctrl"
     md-content
     layout-padding>
    <h4>Modal Lifecycle Hook Problem Example</h4>
    <md-button class="md-raised md-primary"
               ng-click="$ctrl.onButtonClick()">
        Show Modal
    </md-button>
</div>

app/myModalTemplate.html:

<md-dialog>
    <md-toolbar class="md-theme-light">
        <h2>Modal</h2>
    </md-toolbar>
    <md-dialog-content class="md-padding">
        Stuff, y'all.
    </md-dialog-content>
    <md-dialog-actions>
        <md-button class="md-primary"
                   ng-click="$ctrl.ok()">
            OK
        </md>
    </md-dialog-actions>
</md-dialog>

If you set all of this stuff up, and run the project, you'll see a page with a button. When you click the button you will only get one alert - that the constructor has fired. What I was expecting that you'd get is two alerts: the constructor fired message, as well as the $onInit fired message.

Thus, this leads to my...

Question: Why is it that, if a Material Design Dialog controller implements IController, that apparently the lifecycle hooks don't fire?


Solution

  • I'm beginning with AngularJS and I recently had the same issue during my internship. I think that the problem is your ModalController is not called as a component (it's called with $mdDialog) so it does not have any life cycle.

    To solve this issue, you have to call explicitly your $onInit method. Maybe you can do that in your ModalController constructor.