angularjscontrollercomponentsngrouteangularjs-bindings

Angular 1.5 component updating parent controller through ngRoute


I'm using ngRoute to create an Angular single-page app. Want to move to a component-based version.

Problem is isolated scopes. I need access to main controller props and methods. Trying to use bindings but does not work. I cannot find the problem with this one. This app works fine without using components. When I try to change the homepage view into a component it crashes. These are the main parts of the code:

framework

<html ng-app="angularModule" >
<body ng-controller="angularController as angCtrl" >
    <div ng-show="angCtrl.user.isLoggedIn" >Sign Out</div>
    <div ng-hide="angCtrl.user.isLoggedIn" cd-visible="angCtrl.showSignIn">Sign In</div>
    <div id="contentLayer" class="contentLayer" ng-view ></div>

homepage template

<h1 class="pageLabel" >HomePage</h1>
<blockquote>This can be anything. No bindings.</blockquote>

angularController

var app = angular.module ('angularModule', ['ngRoute'] );

app.directive ('cdVisible', 
    function () {
        return  function (scope, element, attr) {
                    scope.$watch (attr.cdVisible, 
                        function (visible) {
                            element.css ('visibility', visible ? 'visible' : 'hidden');
                        }
                    );
                };
    }
);

app.config ( [ '$locationProvider', '$routeProvider',
    function config ($locationProvider, $routeProvider) {
        $locationProvider.hashPrefix ('!');
        $routeProvider
        .when ('/sign-in', {
            templateUrl:    '/ng-sign-in',
            controller:     signInController
        })
        ... more routes
        .when ('/home', {
            template:   '<home-page showSignIn="angCtrl.showSignIn" menuSelect="angCtrl.menuSelect" ></home-page>'
        })
        .otherwise ('/home');
    }
]);

function homePageController () {
    this.menuSelect ('Devices');  // this statement has no effect on angularController.menuSelection chrome shows it as an anonymous function
    this.showSignIn = false;  // this bombs: Expression 'undefined' in attribute 'showSignIn' used with directive 'homepage' is non-assignable!
}

app.component ('homePage', {
    templateUrl:    '/ng-homepage',
    controller:     homePageController,
    bindings: {
        menuSelect: '&',
        showSignIn: '='
    }
});

app.controller ('angularController', [ '$http', '$window', '$location',
    function ($http, $window, $location) {
        var self = this; 
        this.user = {
            "isLoggedIn":       false
        };
        this.showSignIn = true;
        this.menuSelection = "";
        this.errorMessage = "";
        this.menuSelect = 
            function (selection) {
                self.menuSelection = selection;
            };
        this.setUserData =
            function (userData) {
                self.user = userData;
            };
        this.setShowSignIn =
            function (show) {
                self.showSignIn = show;
            };
        this.menuSelect ('');
        this.getUserData();     // I removed this for this post
    }
]);

I added a comment to the spot where it throws an exception. The homepage controller attempts to update the model of the angularController. The first does nothing the second throws an exception. What am I doing wrong?


Solution

  • First of all showSignIn is a primitive, therefore angular will handle it the exact same way as doing showSignIn="7+2". If you need to modify that value inside the component then you should use an object with the showSignIn property.

    Now menuSelect is a little tougher, probably Chrome console is showing something like

    function (locals) {
        return parentGet(scope, locals);
    } 
    

    This is because you're just passing the reference to angCtrl.menuSelect to the component.

    In order to execute the menuSelect function from inside homePageController you'd have to do (in the HTML):

    <home-page menu-select="angCtrl.menuSelect(myMsg)"></home-page>
    

    And then call it like this in the component's controller:

    this.menuSelect({ myMsg:"Devices" })
    

    The (myMsg) in the HTML is a call to angular to return this reference, then in the execution we pass the parameter { myMsg:"Devices" } to match the parameter in the reference we just did. You can check this answer that explains it way more detailed.