I want to display two elements on a page controlled by different instances of the same controller, but I need to register some external information that will be unique (one "joystick" gets an identifying property set, like "player = one" while the other gets "player = two").I'm not sure of the best way of pulling this off exactly
Here's a generic example of what I'm trying to accomplish:
<!-- These need to have different configurations -->
<div ng-include src="'joystick/joy.tpl.html'"
ng-controller="JoystickCtrl">...</div>
<div ng-include src="'joystick/joy.tpl.html'"
ng-controller="JoystickCtrl">...</div>
Should I:
Use a directive?
<div ng-include src="'joystick/joy.tpl.html'"
ng-controller="JoystickCtrl" player="one">...</div>
<div ng-include src="'joystick/joy.tpl.html'"
ng-controller="JoystickCtrl" player="two">...</div>
Use $injector? (fyi - this might be an incorrect implementation)
<div ng-controller="DualJoyCtrl">
<div ng-include src="'joystick/joy.tpl.html'"
ng-controller="joyOne" player="one">...</div>
<div ng-include src="'joystick/joy.tpl.html'"
ng-controller="joyTwo" player="two">...</div>
</div>
-----
.controller('DualJoyCtrl', function ($injector, JoystickCtrl, $scope, $rootScope) {
$scope.joyOne = $injector.instantiate(JoystickCtrl, {$scope: $rootScope.$new(), player:"one"});
$scope.joyTwo = $injector.instantiate(JoystickCtrl, {$scope: $rootScope.$new(), player:"two"});
});
Or... not do this?
I realize this is similar to another, seemingly inconclusive stack post:
Edit
Since ngController is initialized before ngInit, in order to have data available in controller at once, you should wrap ngController in parent element with ngInit:
<div ng-init="player = 'one'">
<div ng-controller="JoystickCtrl">
...
</div>
</div>
Original answer
I think simple ng-init would suffice:
<div ng-controller="JoystickCtrl" ng-init="player='one'">...</div>
<div ng-controller="JoystickCtrl" ng-init="player='two'">...</div>
Store your config values in a data attribute, and retrieve it within the controller using $attrs. (The AngularJS ngInit documentation recommends to say clear of ng-init unless aliasing special properties of ngRepeat. ) A similar answer is here. This code snippet gives you the general idea:
Index.html:
<div ng-include ng-controller="JoystickCtrl" src="'same.html'" data-id="1"></div>
<div ng-include ng-controller="JoystickCtrl" src="'same.html'" data-id="2"></div>
Controller:
function joystickCtrl($scope, $attrs) {
$scope.id = $attrs.id;
};
View:
<h2>Joystick: {{id}}</h2>
Here is the full code in Plunker.
Related
This question already has answers here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
(3 answers)
Closed 5 years ago.
I'm wondering what's happening behind the scenes when using ng-init.
Sometimes ng-init does some unexpected things, and I have a hard time debugging.
Let's say I have the following structure:
<!-- App -->
<div ui-view>
<!-- Component -->
<custom-component>
<!-- Transcluded component -->
<transcluded-component>
<!-- Controller -->
<div ng-controller="MyCtrl">
<!-- Element -->
<div ng-init="myVar = 'hello'">
{{myVar}}
</div>
</div>
</transcluded-component>
</custom-component>
</div>
Which element does myVar bind to?
Edit 2017-07-21: Added an example
In the following template block (especially within an ng-repeat), I may use an ng-init:
<span ng-init="path = getPath()">
<a ng-if="path" ng-href="path">
Click here
</a>
<span ng-if="!path">
Some text
</span>
</span>
In this case, I skipped a function call twice, and kept my template clean.
Sometimes ng-init does some unexpected things, and I have a hard time debugging.
The ng-init directive evaluates an Angular Expression in the context of the scope of its element. Numerous directives (ng-repeat, ng-controller, ng-if, ng-view, etc.) create their own scope.
For more information, see
AngularJS Developer Reference - scopes
AngularJS Wiki - Nuances of Scope Prototypical Inheritance
Avoid using ng-init
Avoid using ng-init. It tangles the Model and View, making code difficult to understand, debug and maintain. Instead initialize the Model in the controller.
From the Docs:
ngInit
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
— AngularJS ng-init Directive API Reference
Update
Q: I've also added an example of a code block I sometime use.
<!-- Controller -->
<div ng-controller="MyCtrl">
<!-- Element -->
<div ng-init="myVar = 'hello'">
{{myVar}}
</div>
</div>
To do the equivalent initialization in the controller:
app.controller("MyVar", function($scope) {
this.$onInit = function() {
$scope.myVar = 'hello';
};
});
By separating concerns of Model and View, the code becomes easier to understand, debug and maintain.
I've gone through other questions, but couldn't find any that dynamically loaded controllers/views the way I am. I still fear this may be a duplicate question, but I have done my due diligence and came up empty. Please point me in the right direction if you're better with search terms.
This is how my app works: My index page loads up RequireJS pointing to a main.js file which outlines the initial includes (app.js, routeResolver, and a data service (unused currently). The routeResolver allows me to dynamically load in my views and respective controllers using code such as below within app.js. (Using a consistent naming convention, passing 'home' loads in home.html and associates it with homeController.js from their respective controllers/views locations.) We do not need to use ng-app='appname' because it’s added at runtime by calling angular.bootstrap() within the app.js file.
//Define routes - controllers will be loaded dynamically
var route = routeResolverProvider.route;
$routeProvider
.when('/', route.resolve('home'))
.when('/createnew', route.resolve('createnew'))
In my controller, I'm loading a variable from sessionStorage. (I have confirmed it is there/available. The test alert displays it correctly.) My problem is it is not displayed on the html page, and the console does not produce any errors. I have confirmed the page is accurately associating itself with the controller because if I remove the expression, I get an error that it is not defined... but despite it containing a value, it still doesn't display. All I get is 'Welcome '.
Controller:
'use strict';
define(['app'], function (app) {
var injectParams = ['$location', '$filter', '$window', '$timeout'];
var homeController = function ($location, $filter, $window, $timeout) {
var userTitle = sessionStorage.getItem('userTitle');
alert(userTitle);
};
homeController.$inject = injectParams;
app.register.controller('homeController', homeController);
});
View:
<div class="container-fluid text-center">
<div class="row content">
<div class="col-sm-2 sidenav">
<p>Placeholder</p>
</div>
<div class="col-sm-8 text-left">
<p>Welcome {{ userTitle }}</p>
</div>
<div class="col-sm-2 sidenav">
<div class="well">
<p>Placeholder</p>
</div>
<div class="well"></div>
</div>
</div>
</div>
I'll gladly share more code, but I didn't want to make this too long and I feel like I'm just missing something silly...
At first glance, I noticed you're making a local variable named userTitle when you want to add that variable to $scope.
Inject $scope into homeController and $scope.userTitle = 'test';. This should get you what you want.
I am using Angular 1.5.7 and am trying to see if I can push the value of an attribute within a directive used on several different pages to an array that lives in the controller.
I am pretty sure that I need to used transclusion in order to do this but I am stuck. Below is a simplified version of what I have so far:
about.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="About">
<div>About Page</div>
</div>
</div>
contact.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="Contact">
<div>Contact Page</div>
</div>
</div>
header.html
<div>{{mypage}}
<div ng-transclude></div>
</div>
cd-header.js
var cdHeader = function() {
return {
scope: {
mypage: "#"
},
transclude: true,
templateUrl: 'header.html',
link: function(scope) {
// Not sure but I think I might need a function here
}
}
}
module.exports = cdHeader;
MainCtrl.js
var MainCtrl = function($scope) {
var nav = [];
// Not sure how items that are pushed to the nav get to this point
}
module.exports = MainCtrl;
main.js
var app = angular.module("myapp", [
'about',
'contact',
])
.controller('MainCtrl', MainCtrl)
.directive('cdHeader', cdHeader)
I am able to get the value of the mypage attribute in the directive as well as its transcluded <div> to appear in the header but only for the current page in view.
The part I am missing is how to get all of the mypage values from each page into the header regardless of the current page in view. I am somewhat new to Angular and have read a lot but have not come across anything that explains how this can be done. Maybe this is achieved with a service? If so, I'm not sure how to go about it.
To clarify with a visual. This is what I see:
But this is what I want to see:
As you rightly pointed out, there are several ways to do it.
Perhaps you can pass the array from the MainCtrl as an attribute to the directive. For instance, nav-array="nav". However, before that, you need to set the array nav as such
var $scope = this;
this.nav = [];
The second option is to consume a service. Create an angular service, pass it as a dependency in the directive, and consume it.
Lets create an array in MainCtrl as $scope.headers = ['about':'About','contact':'Contact','home':'Home'] or create a factory/service to share the headers data and in header.html use ng-repeat to display the header name according to myPage value like below
<div ng-repeat="page in headers[myPage]">{{page}}
<div ng-transclude></div>
</div>
This works:
<div ng-include="'login.html'" flex ng-if="!loggedIn" ng-controller="LoginController"></div>
However, this doesn't ({{test}} outputs nothing):
<ng-include src="'login.html'" flex ng-if="!loggedIn" ng-controller="LoginController"></ng-include>
Is there any reason? Or is it a bug?
login.html:
<p>{{test}}</p>
LoginController:
function LoginController($scope){
$scope.test = 'login';
}
I'm not exactly sure where "loggedIn" is defined, but when I define it on a parent controller, both syntaxes work as expected.
Plunkr
<div ng-controller="PrntCtrl">
<div ng-include="'test.html'" flex ng-if="!loggedIn" ng-controller="TestCtrl"></div>
<ng-include src="'test.html'" flex ng-if="!loggedIn" ng-controller="TestCtrl"></ng-include>
</div>
Some questions to consider:
What version of angular are you using?
How is your app defined and how is the controller registered with the app?
Where is "loggedIn" defined?
Because this directive accepts only 3 arguments:
ngInclude | src = string
onload (optional) = string
autoscroll (optional) = string
...according to the ngInclude Docs
EDIT:
you can solve simply wrap it in another element...
<div ng-if="!loggedIn" ng-controller="LoginController">
<div ng-include="'login.html'" flex></div>
</div>
Here is the mock up of my app:
"Left side bar" is for directories, while the "main content" is for files. The files are enumerated from the selected directory ([list mode]).
The "left side bar" is to be reused for 2 other purposes:
display form to create new directory ([create mode])
display form to edit selected directory ([edit mode])
Similarly, the main content could also be in [list mode], [edit mode], and [create mode]. So, in total there would be 3 x 3 possible combinations.
Using ng-switch, this one can be modeled quite easily.
<div class="left-bar">
<div ng-switch on="directory.mode">
<div ng-switch-when="list"></div>
<div ng-switch-when="create"></div>
<div ng-switch-when="edit"></div>
</div>
</div>
<div class="main-content">
<div ng-switch on="files.mode">
<div ng-switch-when="list"></div>
<div ng-switch-when="create"></div>
<div ng-switch-when="edit"></div>
</div>
</div>
However, I wish to model this using angular-ui router. I am new to angular-ui, and the state model I could think of now is something like:
.state('main.folder-list.file-list', views: {'left-sidebar':{templateUrl:'directory-list.html'}, 'main-content':{templateUrl:'file-list.html'}})
.state('main.folder-list.file-edit', ...)
.state('main.folder-list.file-create', ...)
.state('main.folder-edit.file-list', ...)
.state('main.folder-edit.file-edit', ...)
.state('main.folder-edit.file-create', ...)
.state('main.folder-create.file-list', ...)
.state('main.folder-create.file-edit', ...)
.state('main.folder-create.file-create', ...)
One important requirement: when the mode of "left side bar" is switched, the content of "main content" shouldn't be changed (it should still be in the same mode as before), and vice versa.
How to simplify that?
First think first, remember that angular-ui router able to handle nested state. I usually use .state('root'), .state('root.app'), .state('root.app.specific'), etc. With that characteristics of angular-ui router, we can simply set $scope at root state like $scope.data = { status: 'ok' } and in root's child, we still able to call that $scope.data.
Also if your create, list, edit is a generic "class" or singleton. You can also make it simple using angular controller inheritance in your controller type $controller('[parent-controller]', {$scope: $scope});. So your parent-controller's $scope like method that they have etc. will be able to called inside your new controller's $scope. Angular-ui router's nested state is similar to this. For example:
Application.controller('AppCtrl', ['$scope', '$controller', function($scope, $controller) {
$controller('AdminCtrl', {$scope: $scope});
}]);
In that example my AppCtrl will have the AdminCtrl's $scope.