Is it correct to use multiple controllers like below?
<div ng-if="actionButtonText=='Confirm'" ng-controller="upsrCtrl" ng-controller="pt3Ctrl" ng-controller="spmCtrl">
<button ng-click="checkAnswer()" class="button button-confirm-outline">
{{actionButtonText}}
</button>
</div>
The reason I used these were I make this as footer and the content change dynamically based on different controller.
I'm assuming your ng-if handles the switch on which ng-controller to use. And the ng-clickis the event that triggers the switch. If thats the case just render all 3 blocks only show based on parentCtrl.
<div ng-controller="parentCtrl">
<div ng-if="parentCtrl.actionButtonText=='upsrCtrl'" ng-controller="upsrCtrl">
<button ng-click="checkAnswer()" class="button button-confirm-outline">
{{actionButtonText}}
</button>
</div>
<div ng-if="parentCtrl.actionButtonText=='pt3Ctrl'" ng-controller="pt3Ctrl">
<button ng-click="checkAnswer()" class="button button-confirm-outline">
{{actionButtonText}}
</button>
</div>
<div ng-if="parentCtrl.actionButtonText=='spmCtrl'" ng-controller="spmCtrl">
<button ng-click="checkAnswer()" class="button button-confirm-outline">
{{actionButtonText}}
</button>
</div>
</div>
Providing wrappers of controller may be a way to find your solution. But if you will see it on element level, An element can not carry more then one one controller.
As i can see there is a way To make one controller the main controller and pass another controller as some other parameter. like vm i used in bellow example . Here vm is is the page level controller instance(controller of the page where the element is present). use Isolated scope here to access vm
<div ng-if="actionButtonText=='Confirm'" ng-controller="upsrCtrl">
<button ng-click="checkAnswer()" class="button button-confirm-outline" vm='vm'>
{{actionButtonText}}
</button>
</div>
But the better way i will suggest is To use more then one directive with its separate controllers and use it. It will be some kind of attribute level directive. which will make your purpose solve.
app.directive('dirUp', function() {
return {
controller: function(scope) {
//directive controller
}
};
});
app.directive('dirDown', function() {
return {
controller: function(scope) {
//directive controller
}
};
});
and in html
<div dir-up use-down></div>
Hope it will help you.
Try to use custom directive like below. Take a look on a plunker:
https://plnkr.co/edit/ileyNcaSlJYLc2bCbJeq?p=preview
app.directive('ifController', function factory($compile) {
var handler = {
restrict: 'A',
scope: {
ifController: '#'
},
link: function(scope, element, attrs){
var lastElement = null;
var update = function() {
if (lastElement !== null) {
lastElement.remove();
}
var template = '<div ng-controller="' + scope.ifController + '">'+
'<button ng-click="checkAnswer()" class="button button-confirm-outline">'+
'{{actionButtonText}}'+
'</button>' +
'</div>';
var linkFn = $compile(template);
var content = linkFn(scope);
element.append(content);
lastElement = content;
}
scope.$watch('ifController', update);
}
};
return handler;
});
You can set controller by passing a variable to if-contoller like below:
<div if-controller="{{controller}}">
This will remove previous build of element and compile a new one with a new one controller. Also you can modify this directive to use it as a wrapper to another directive you with to use (just change template).
Related
I am trying to make a read more directive.
Currently my directive on works once I refresh the page. I feel like I'm missing a concept here, do I need to initialize the directive into the controller? Is my watch wrong? Currently there is nothing in the controller related to this problem.
HTML:
<div data-ng-switch-when="false">
<div data-ng-bind-html="::sanitizeWidgetContent(widget.content)"
data-expand-height
data-ng-class="{true: 'widget-text-collapsed'}[elementHeight>500]"
class="widget-content">
</div>
<div class="fadeout-bottom" data-ng-show="elementHeight>500">
<button class="btn btn-default btn-fadeout" data-ng-click="elementHeight=false">Read More</button>
</div>
</div>
JS:
angular.module('myApp.directives')
.directive('expandHeight', function () {
return function (scope, el, attrs) {
scope.$watch(attrs.expandHeight, function(){
scope.elementHeight = el[0].offsetHeight;
});
}
});
I am building an AngularJS application where I want to dynamically load different html templates based on where it was called from. I am new to AngularJS, so I may well be wide of the mark with this approach.
html
<div ng-controller="ProjectsCtrl" ng-init="getProjects()">
<div ng-repeat="project in projects>
<button ng-click="">Create New Project</button>
<button ng-click="">Cancel</button>
<div ng-repeat="task in projects.tasks>
<button ng-click="">Create New Task</button>
<button ng-click="">Cancel</button>
</div>
</div>
</div>
<focus-pane></focus-pane>
app.js
var projectsApp = angular.module('projectsApp', []);
projectsApp.directive('focusPane', function() {
return {
restrict: 'E',
// this template loading should be dynamic
templateUrl: '_create_project.html'
};
});
concernsApp.controller('ProjectsCtrl', function ($scope, $http, ) {
...
});
So, what I want to do is be able to two things when the 'Create New Project' button is clicked:
pass in the project or task object and use this in the focusPane directive.
pass in a name of a template to load dynamically in the directive.
I'm thinking, in pseudo-code, the html should look something like this:
html
<div ng-controller="ProjectsCtrl" ng-init="getProjects()">
<div ng-repeat="project in projects>
<button ng-click="createProject(project, '_create_project.html')">Create New Project</button>
<button ng-click="">Cancel</button>
<div ng-repeat="task in projects.tasks>
<button ng-click="createProject(task, '_create_task.html')">Create New Task</button>
<button ng-click="">Cancel</button>
</div>
</div>
</div>
<focus-pane></focus-pane>
..and then I would declare these functions in the ProjectsCtrl. However, as I said, I'm just getting into AngularJS and I'm not sure this is the right path at all. In the AngularJS Docs I couldn't find much info on dynamically loading things in a directive.
Yes the comments made earlier are correct (depending upon what context your working in), but it is still possible none the less to create a directive that will load dynamic templates for you. Here is an example. http://jsfiddle.net/joshkurz/T42xQ/5/
.directive('dynamicTemplate', ['$compile', '$templateCache', function($compile, $templateCache){
return {
scope: { templateVar: '#'},
link: function(scope, element, attrs){
scope.test = 'Hello World';
scope.$watch('templateVar', function(newV,oldV){
if(newV !== undefined){
var newElement = $compile($templateCache.get(newV).trim())(scope);
element.html('');
element.append(newElement[0]);
}
});
}
}
}]);
I find sometimes when creating directives they can actually have requirements to control what data is requested. After all that is what ngView does right? its a directive too. You just have to make sure that that you have all the data before any DOM manipulation starts.
I have this this template:
<div class="modal" id="popupModal" tabindex="-1" role="dialog" aria-labelledby="createBuildingLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="createBuildingLabel">{{ title }}</h4>
</div>
<form data-ng-submit="submit()">
<div class="modal-body" data-ng-transclude>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-ng-click="visible = false">Annuleren</button>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-save"></span>Maken</button>
</div>
</form>
</div>
</div>
</div>
and here's the directive:
app.directive("modalPopup", [function () {
return {
restrict: 'E',
templateUrl: 'Utils/ModalPopup',
scope: {
title: '#',
onSubmit: '&',
visible: '='
},
transclude: true,
link: function (scope, element, attributes) {
var container = $("#popupModal");
scope.submit = function (newGroup) {
scope.onSubmit(newGroup);
}
scope.hide = function () {
container.modal('hide');
}
scope.show = function () {
container.modal('show');
}
scope.$watch('visible', function (newVal, oldVal) {
if (newVal === true) {
scope.show();
}
else {
scope.hide();
}
})
}
}
}]);
As you can see I have declared my form tag inside the directive and I also use transclude to determine how my form is going to look like. For now I have this:
<modal-popup title="Nieuwe groep aanmaken" data-on-submit="createGroup()" visible="showAddGroupForm">
<div class="row">
<div class="col-md-3">Kopieren van:</div>
<div class="col-md-8">
<select class="form-control" data-ng-model="newGroup.Year">
<option value="">Nieuw jaar</option>
<option data-ng-repeat="year in years" value="{{year.Id}}">{{year.Name}}</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-3">Naam</div>
<div class="col-md-8">
<input type="text" class="form-control" data-ng-model="newGroup.Name" />
</div>
</div>
</modal-popup>
When the submit button is pressed, I want the data to be available in my controller.
I ques the data isn't available because of the isolated scope, however I'm not sure. What do I need to do to get the data back from the directive into my controller?
PS: I know about angular-ui and angularstrap, but I'm doing this to learn about Angular.
EDIT:
Here's a Fiddle
I think the cause is a misunderstanding about how scopes work (especially with transclusion).
Let's start with this code (from the fiddle):
<div ng-controller="MyCtrl">
<my-popup on-submit="formSubmitted(model)">
<input type="text" ng-model="model.Name"/>
</my-popup>
</div>
Since <my-popup> transcludes its content, the scope above is that of MyCtrl, even in the content of the directive. By content I mean the <input>, NOT the directive template, i.e. the <div><form ... code.
Therefore it is implied that model (as used in ng-model="model.Name") is a property of the scope of MyCtrl, as is formSubmitted(). Since both are members of the same scope, you do not need to pass the model as argument; you could just do:
(in the template:)
<my-popup on-submit="formSubmitted()"><!-- no `model` argument -->
(the controller:)
function MyCtrl($scope) {
// I like declaring $scope members explicitly,
// though it can work without it (charlietfl comments)
$scope.model = {};
$scope.submittedValue = null;
$scope.formSubmitted = function() {
// another bug was here; `model` is always a member of the `$scope`
// object, not a local var
$scope.submittedValue = $scope.model.Name;
}
}
Another bug is in the directive code:
link: function(scope, element, attributes){
scope.submit = function(){
scope.onSubmit({model: model});
}
}
The variable model (not the name model:) is undefined! It is a property of the parent scope, so you would have a chance if the scope was not isolated. With the isolated scope of the directive, it may work with an awful workaround that I am not even considering to write :)
Luckily, you do not need the directive to know about things happening in the external scope. The directive has one function, to display the form and the submit button and invoke a callback when the submit button is clicked. So the following is not only enough for this example, but also conceptually correct (the directive does not care what is happenning outside it):
link: function(scope, element, attributes){
scope.submit = function(){
scope.onSubmit();
}
}
See the fiddle: http://jsfiddle.net/PRnYg/
By the way: You are using Angular v1.0.1. This is WAAAAY too old, seriously consider upgrading!!! If you do upgrade, add the closing </div> to the template: <div ng-transclude></div>.
My page will have a growing list of directives that have common functionality. What would be the best way to implement that functionality keeping best practices and performance in mind.
For example:
Page will have 100 directives and each directive will have common events:
Show hidden layer on mouseover
Hide div > view and Show div > edit on click.
......
Template:
<div class="panel">
<div class="view">
<div class="edit-controls hidden">
Edit
</div>
<h3>{{......}}</h3>
<p>{{.....}}</p>
</div>
<div class="edit hidden">
<form>
........
</form>
</div>
</div>
Option 1. Directive:
appModule.directive('elemTest', [function() {
return {
restrict: 'E',
templateUrl: '.......',
replace: true,
controller: function($scope, $element) {
},
link: function(scope, element) {
element.on('mouseover', function() {
element.find(".edit-controls").show();
});
element.on('mouseout', function() {
element.find(".edit-controls").hide();
});
element.find(".edit").on('click', function(event){
event.preventDefault();
element.children(".view").hide();
element.children(".edit").show();
});
}
}
}]);
Option 2. Directive with no link functions but handle mouseover/out/click events with jQuery script snippet on the bottom of the page:
$(".panel").live('mouseover',function() {
.......
})
$(".panel").live('mouseout',function() {
.......
})
..........
Option 3. Directive with controller and ng-click instead of directives link function?
Option 4. ?
Using Angular 1.2.0
Option 4: Directives with support for ng-mouseover, ng-mouseout (mouseleave?) and an ng-click on the edit button.
In a nutshell, make your directive have a template which supports the functions:
In the template:
...
<div ng-mouseover="showEditControls = true" ng-mouseleave="showEditControls = false">
<div ng-show="showEditControls">
<button ng-click="edit()" />
</div>
</div>
...
In the directive:
...
controller: function($scope){
$scope.edit = function()
// do whatever the editor does
}
}
Using the example mentioned here, how can I invoke the modal window using JavaScript instead of clicking a button?
I am new to AngularJS and tried searching the documentation here and here without luck.
Thanks
OK, so first of all the http://angular-ui.github.io/bootstrap/ has a <modal> directive and the $dialog service and both of those can be used to open modal windows.
The difference is that with the <modal> directive content of a modal is embedded in a hosting template (one that triggers modal window opening). The $dialog service is far more flexible and allow you to load modal's content from a separate file as well as trigger modal windows from any place in AngularJS code (this being a controller, a service or another directive).
Not sure what you mean exactly by "using JavaScript code" but assuming that you mean any place in AngularJS code the $dialog service is probably a way to go.
It is very easy to use and in its simplest form you could just write:
$dialog.dialog({}).open('modalContent.html');
To illustrate that it can be really triggered by any JavaScript code here is a version that triggers modal with a timer, 3 seconds after a controller was instantiated:
function DialogDemoCtrl($scope, $timeout, $dialog){
$timeout(function(){
$dialog.dialog({}).open('modalContent.html');
}, 3000);
}
This can be seen in action in this plunk: http://plnkr.co/edit/u9HHaRlHnko492WDtmRU?p=preview
Finally, here is the full reference documentation to the $dialog service described here:
https://github.com/angular-ui/bootstrap/blob/master/src/dialog/README.md
To make angular ui $modal work with bootstrap 3 you need to overwrite the styles
.modal {
display: block;
}
.modal-body:before,
.modal-body:after {
display: table;
content: " ";
}
.modal-header:before,
.modal-header:after {
display: table;
content: " ";
}
(The last ones are necessary if you use custom directives) and encapsulate the html with
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
Open modal windows with passing data to dialog
In case if someone interests to pass data to dialog:
app.controller('ModalCtrl', function($scope, $modal) {
$scope.name = 'theNameHasBeenPassed';
$scope.showModal = function() {
$scope.opts = {
backdrop: true,
backdropClick: true,
dialogFade: false,
keyboard: true,
templateUrl : 'modalContent.html',
controller : ModalInstanceCtrl,
resolve: {} // empty storage
};
$scope.opts.resolve.item = function() {
return angular.copy(
{name: $scope.name}
); // pass name to resolve storage
}
var modalInstance = $modal.open($scope.opts);
modalInstance.result.then(function(){
//on ok button press
},function(){
//on cancel button press
console.log("Modal Closed");
});
};
})
var ModalInstanceCtrl = function($scope, $modalInstance, $modal, item) {
$scope.item = item;
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
Demo Plunker
The AngularJS Bootstrap website hasn't been updated with the latest documentation. About 3 months ago pkozlowski-opensource authored a change to separate out $modal from $dialog commit is below:
https://github.com/angular-ui/bootstrap/commit/d7a48523e437b0a94615350a59be1588dbdd86bd
In that commit he added new documentation for $modal, which can be found below:
https://github.com/angular-ui/bootstrap/blob/d7a48523e437b0a94615350a59be1588dbdd86bd/src/modal/docs/readme.md.
Hope this helps!
Quick and Dirty Way!
It's not a good way, but for me it seems the most simplest.
Add an anchor tag which contains the modal data-target and data-toggle, have an id associated with it. (Can be added mostly anywhere in the html view)
Now,
Inside the angular controller, from where you want to trigger the modal just use
angular.element('#myModalShower').trigger('click');
This will mimic a click to the button based on the angular code and the modal will appear.
Different version similar to the one offered by Maxim Shoustin
I liked the answer but the part that bothered me was the use of <script id="..."> as a container for the modal's template.
I wanted to place the modal's template in a hidden <div> and bind the inner html with a scope variable called modal_html_template
mainly because i think it more correct (and more comfortable to process in WebStorm/PyCharm) to place the template's html inside a <div> instead of <script id="...">
this variable will be used when calling $modal({... 'template': $scope.modal_html_template, ...})
in order to bind the inner html, i created inner-html-bind which is a simple directive
check out the example plunker
<div ng-controller="ModalDemoCtrl">
<div inner-html-bind inner-html="modal_html_template" class="hidden">
<div class="modal-header">
<h3>I'm a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li ng-repeat="item in items">
<a ng-click="selected.item = item">{{ item }}</a>
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</div>
<button class="btn" ng-click="open()">Open me!</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
inner-html-bind directive:
app.directive('innerHtmlBind', function() {
return {
restrict: 'A',
scope: {
inner_html: '=innerHtml'
},
link: function(scope, element, attrs) {
scope.inner_html = element.html();
}
}
});