I have two modules wrapped inside of directives called 'bci-directive' and 'bcd-directive' that I use to display data inside of a primary module called 'core'. I am trying to give users a way to view these two modules in two different ways, one where they can view both at the same time, and one where they can see each separated by tabs -- the issue I am running into is that each of these directives has isolated scope so each pair of tags generates a new view which results in incorrect behavior in updating controller variables within the two sub-modules when users make edits. What I would like is to be able to a single view for each of these modules that can switch between the two view options(tab/all) while maintaining the same scope.
So far I've tried moving these views out of directives and into ng-includes with the same results, I wasn't able to find much more for suggestions while researching. Here is what I currently have for the front end:
<div ng-cloak="">
<md-toolbar class="md-primary">
<div class="md-toolbar-tools">
<!-- span tag to align buttons to right -->
<span flex=""></span>
<md-button ng-click="core.initiateSave()" class="md-raised">SAVE ALL</md-button>
<md-checkbox ng-model="core.tabView" aria-label="tabViewSwitch" class="md-warn">
Tab View
</md-checkbox>
</div>
</md-toolbar>
<div ng-show="core.tabView">
<md-content>
<md-tabs md-dynamic-height="" md-border-bottom="" class="md-primary md-hue-2">
<md-tab label="Details">
<bcd-directive></bcd-directive>
</md-tab>
<md-tab label="Items">
<bci-directive></bci-directive>
</md-tab>
</md-tabs>
</md-content>
</div>
<!-- View All: These are new views with new controllers that use same factory as above directives -->
<div ng-show="!core.tabView">
<bcd-directive></bcd-directive>
<bci-directive></bci-directive>
</div>
<div style="position:fixed;top:50%;left:50%">
<md-progress-circular md-mode="indeterminate" class="md-warn loader" md-diameter="60" ng-hide="!loading" ng-disabled="!loading"></md-progress-circular>
</div>
And the directives:
(function(){
'use strict';
angular
.module('app.bci')
.directive('bciDirective', bciDirective);
/* #ngInject */
function bciDirective(){
var directive = {
templateUrl: '../app/sf/bci/templates/bci.html',
controller: 'bciController',
controllerAs: 'bci',
scope: {},
bindToController: true,
restrict: 'E'
};
return directive;
}
})();
Is it possible to switch between these view options without having to instantiate the directives twice?
You can set the template property of your directive definition object to a function that will return your dynamic template:
restrict: "E",
replace: true,
template: function(tElement, attributes) {
return getTemplate(attributes.template);
}
And then assign that from the parent based on your condition where you can access the attributes through attributes.
Now your template is being determined before the compile phase, and you don't need to manually compile it.
Related
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 really need help by solving the following problem:
I try to realize some settings for an application, therefore I want to use the UI-Bootstrap accordion.
I have the following HTML-Code:
<accordion close-others="oneAtATime">
<accordion-group ng-repeat="group in groups" heading="{{group.groupTitle}}">
<accordion-content></accordion-content>
</accordion-group>
</accordion>
The DOM of the "accordion" is a div where ng-controller="AccordionController". In this Controller I have a variable groups which looks like this:
$scope.groups = [{
groupTitle: "title1",
templateUrl: "file1.html"
}, {
groupTitle: "title2",
templateUrl: "file2.html"
}]; // ... and so on
accordionContent is my directive which should give different templateURLs depending on the $index or groupTitle (doesn't matter).
The accordionContent-directive looks like this:
settings.directive("accordionContent", function () {
return {
restrict: "E",
templateUrl: //**here is my problem**
};
});
The content also has some angular-stuff implemented, I read that this need to get considered. (or not ?)
I don't believe you can do that like that. I tried myself once, didn't work if I remember correctly.
What you can do is have a static HTML page in the directive, and in that HTML page you'll have:
<div>
<div class="slide-animate" ng-include="templateUrl"></div>
</div>
Where templateUrl is the variable on your isolated scope (or not isolated..) in the accordion-content directive.
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.
I have the following in Mustache.js:
<div>{{{icon.tmpl}}}</div>
icon.tmpl is a template on its own with the following content:
<div id="{{id}}" class="glyphicon glyphicon-send"></div>
In Mustache.js, thanks to the triple bracers, this works perfectly, both levels of templates gets compiled. Now I can't make this work in Angular.js. The second embedded template does not get compiled, but is instead surrounded by quotation marks "..."
How to make this work in Angular?
You could either use an ngInclude or create a directive. Here is an example of an icon directive that essentially just replaces any icon element with the div info you've specified.
http://plnkr.co/edit/NK5bOFvsgpMGeTkteMif?p=preview
html:
<icon></icon>
js:
app.directive('icon', function ( $compile, $timeout) {
return {
restrict: 'EA',
replace: true,
template: '<div id="{{id}}" class="glyphicon glyphicon-send"></div>'
}
})
The directive could just as easily be something like <div class="icon"> or <div icon> and you could apply the template to it.
An example of the ngInclude:
<ng-include src="'icon.html'"></ng-include>
Where icon.html just has your template info. Make sure that id is in the scope in both cases.
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.