Reputation: 83
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?
Upvotes: 1
Views: 609
Reputation: 83
In the process of reading your answer one mistake jumped at me: the home-page component of the /home route should use kebab case show-sign-in menu-select for attributes not lower-camelCase as was coded initially.
Your suggestions both worked. Thanks. Without components I was using prototypal inheritance to access properties and methods in the parent scope. Due to the nature of javascript prototypal inheritance the only way to modify scalars in the parent scope is to invoke setter methods on the parent scope. Apparently something similar applies here. More about prototypal inheritance in javascript.
This was a proof-of-concept exercise. I wanted to test my ability to update properties and objects in a parent scope from a component as well as execute a method in the parent scope. This example updates a scalar in the parent scope 2 different ways:
and demonstrates invoking a function while doing so.
The final working code is: (homepage template does not change)
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.loginData.showSignIn">Sign In</div>
<div id="contentLayer" class="contentLayer" ng-view ></div>
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 login-data="angCtrl.loginData" menu-select="angCtrl.menuSelect(mySelection)" ></home-page>'
})
.otherwise ('/home');
}
]);
function homePageController () {
this.menuSelect ( { mySelection: 'Overview' });
this.loginData.showSignIn = true;
}
app.component ('homePage', {
templateUrl: '/ng-homepage',
controller: homePageController,
restrict: 'E',
bindings: {
menuSelect: '&',
loginData: '='
}
});
app.controller ('angularController', [ '$http', '$window', '$location',
function ($http, $window, $location) {
var self = this;
this.user = {
"isLoggedIn": false
};
this.loginData = {
"showSignIn": false
};
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
}
]);
Upvotes: 0
Reputation: 4628
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.
Upvotes: 2