I have a directive rendering inputs based on a configuration sent by the server. Everything is working great except for the 'select' input.
No matter what I try, the ng-model does not update.
I have trimmed my code a lot to isolate the problem :
Javascript :
var myApp = angular.module('example', []);
myApp.factory('DynamicData', [function() {
return {
data: {
backup_frequency: 604800
}
};
}])
.directive('dynamicInput',
['$compile', 'DynamicData', function($compile, DynamicData) {
/**
* Render the input
*/
var render = function render() {
var input = angular.element('<select class="form-control" ng-model="inner.backup_frequency" ng-options="option.value as option.title for option in options"></select>');
return input;
};
var getInput = function ()
{
var input = render();
return input ? input[0].outerHTML : '';
};
var getTemplate = function(){
var template = '<div class="form-group">' +
'Select input ' +
'<div class="col-md-7">' + getInput() + '</div>' +
'</div>';
return template;
};
return {
restrict : 'E',
scope: {
content:'=content',
},
link : function(scope, element, attrs) {
var template = getTemplate();
scope.options = [
{title: "Daily", value: 86400},
{title: "Weekly", value: 604800},
{title: "Monthly", value: 2678400},
];
scope.inner = DynamicData.data;
console.info('inner data', scope.inner);
element.html(template);
element.replaceWith($compile(element.contents())(scope));
}
};
}])
.controller('FormCtrl', ['DynamicData', '$scope', function (DynamicData, $scope){
$scope.app = {};
$scope.save = function save() {
$scope.value = DynamicData.data.backup_frequency;
console.info('DynamicData', DynamicData.data);
};
}]);
HTML :
<head>
<script data-require="angular.js#1.3.8" data-semver="1.3.8" src="https://code.angularjs.org/1.3.8/angular.js"></script>
<link href="style.css" rel="stylesheet" />
<script src="script.js"></script>
</head>
<body>
<h1>Dynamic Input :</h1>
<div data-ng-controller="FormCtrl">
<dynamic-input class="form-group" content="app"></dynamic-input>
<span data-ng-bind="value"></span><br/>
<button class="btn btn-primary" ng-click="save()">Save</button>
</div>
</body>
</html>
A working plunker is available : http://plnkr.co/edit/mNBTJzZXjX6mLyPz6NCI?p=preview
Do you have any ideas why the ng-model in the select is not updated ?
EDIT : What I want to achieve is updating the variable "inner.backup_frequency" (the reference to my data object returned by my factory DynamicData). As you can see in the plunker, whenever I change the option in the select, the variable contained in the ng-model is not updated.
Thank you for your time.
I had a similar issue and find if you directly bind ng-model to a scope variable as below, the scope variable selection somehow will not be updated.
<select ng-model="selection" ng-options="option for option in options">
Instead, define a view model in your scope and bind ng-model to a field of view model the view model field will be updated.
<select ng-model="viewmodel.selection" ng-options="option for option in options">
In your controller, you should do this:
app.controller('SomeCtrl', ['$scope'
function($scope)
{
$scope.viewmodel = {};
});
I've fixed it. What you needed to do was not to replace the element's content with the compiled content, but rather replace it with the raw HTML and only then compile it.
element.html(template);
$compile(element.contents())(scope);
Working Plunker.
EDIT: I've edited your code to work without the directive:
Plunker without directive.
EDIT 2: Also a version where it works with the directive, but without the compile:
Plunker.
Related
I created a directive to add some content whenever the button "add" is clicked. But I don't know how to get all values in these new inputs.
HTML Code
angular.module('myApp', [])
.controller('myController', function() {
})
.directive('addContent', function($document, $compile) {
return {
restrict: 'A',
replace: false,
link: function(scope, element, attr) {
element.on('click', function() {
var newcontent = '<input type="text" ng-model="myModel"><br>';
angular.element($document[0].querySelector('.newcontent')).append($compile(newcontent)(scope));
})
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="newcontent" ng-app="myApp" ng-controller="myController">
<button type="button" add-content>Add</button><br><br>
</div>
So, how can I set a different ng-model value to each one new input that is created and, how can I get this values in my controller?
You could go with something like this:
The Idea:
A base name can be defined from the html where the directive is being applied.
An incremental number is used in the directive when creating new inputs (the view controller (programmer) where this model is used must be aware of that). Actually, you could use any other strategy you'd prefer better in this case. I've used this one for simplicity and stating my point.
The code (see below snippet):
In the directive
Create a counter for incrementing as new inputs are added: var count = 0;
Take the base name specified in the html with var model = scope.$eval(attr['addContent']);
Modify the newcontent variable to use that base name and the incremental counter like this: var newcontent = '<input type="text" ng-model="' + model + (count++) + '"><br>';
The controller
For organization, create a variable for holding the base name: $scope.buttonModel = 'buttonModelReference';
Access the value of those new models like this: $scope[$scope.buttonModel + $scope.index] where $scope.index is the index of the input (where 0 is the first input created)
The view
Use the modified directive like this add-content="buttonModel" where buttonModel is the variable defined in the controller.
Plus code (for demonstration purposes only)
The showModel function shows the value of one (dynamic created) input passing as reference the index of the input (0 zero is the index of the first input created)
The Snippet
angular.module('myApp', [])
.controller('myController', function($scope) {
$scope.index;
$scope.buttonModel = 'buttonModelReference';
$scope.showModel = function() {
console.log($scope[$scope.buttonModel + $scope.index]);
}
})
.directive('addContent', function($document, $compile) {
var count = 0;
return {
restrict: 'A',
replace: false,
link: function(scope, element, attr) {
element.on('click', function() {
var model = scope.$eval(attr['addContent']);
var newcontent = '<input type="text" ng-model="' + model + (count++) + '"><br>';
angular.element($document[0].querySelector('.newcontent')).append($compile(newcontent)(scope));
})
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="newcontent" ng-app="myApp" ng-controller="myController">
<button type="button" ng-click="showModel()">showModel</button> <input ng-model="index" type="number" placeholder="select the model index starting from 0" /> <br><br>
<button type="button" add-content="buttonModel">Add</button><br><br>
</div>
Once user select value from dropdown i have ng-change function invoked onSizeChange and setting values of $scope.maxMb $scope.maxBytes $scope.FileSizeString, So my question how can i use these values in directive once value is selected from dropdown. i tried to bind these values to isolated scope but no luck. Basically i need fileSize and fileValue after size selection that i have added as an attribute to directive in html so these values should bind to isolated scope but that is happening.How can i resolve this problem ?
directive.js
angular.module("App").directive('progressBarCustom', function() {
return {
restrict: 'E',
scope: {
message: "=",
fileSize: "=",
fileValue: "="
},
templateUrl: '/view/partials/progressbar.html',
controller: "StCtrl",
link: function(scope, el, attrs) {
console.log("file size", scope.fileSize);
//these values should assign to directive template once user select value from dropdown
//start
scope.maxMb = scope.fileSize;
scope.maxBytes = 1000 * 1000 * scope.maxMb;
scope.max = scope.maxBytes;
scope.FileSizeString = scope.fileValue;
// end
el.bind('click', function(event) {
scope.$parent.startRecording();
scope.$parent.stopLogs();
scope.$parent.onSizeChange();
console.log('EVENT', event);
});
};
}
});
ctrl.js
$scope.onSizeChange = function() {
$scope.maxMb = $scope.selectedFileSize.size;
$scope.maxBytes = 3000;
$scope.max = $scope.maxBytes;
$scope.FileSizeString = $scope.selectedFileSize.value;
console.log('FileSize', $scope.maxMb);
}
main.html
<div class="col-md-3">
<select class="form-control" ng-model="selectedFileSize" ng-options="item as item.value for item in FileSizeOptions" ng-change="onSizeChange()"><option value="">Select</option></select>
</div>
<progress-bar-custom ng-show="progressBarFlag" message="event" fileSize="selectedFileSize.size" fileValue="selectedFileSize.value"></progress-bar-custom>
template.html
<uib-progressbar type="success" class="progress-striped" max="max" animate="true" value="dynamic"><span>{{downloadPercentage}}%</span></uib-progressbar>
<p class="pull-right bytes-progress-0"><small>Recorded <strong>{{currentBytes}}</strong> of <strong>{{FileSizeString}}</strong></small></p>
Change fileSize to file-size and fileValue to file-value
<progress-bar-custom ng-show="progressBarFlag" message="event" file-size="selectedFileSize.size" file-value="selectedFileSize.value"></progress-bar-custom>
Update after discussion with OP
Pass just selectedFileSize object in the directive instead of sending it as two properties. And you can get values from selectedFileSize.size and selectedFileSize.value inside directive.
And then watch selectedFileSize object in the directive
I created a simple directive wrapper around the HTML file input to make angular binding work. Here's my directive:
angular.module('myApp').directive('inputFile', InputFileDirective);
function InputFileDirective() {
var bindings = {
selectLabel: '#',
};
return {
restrict: 'E',
require: ['inputFile', 'ngModel'],
scope: true,
controllerAs: 'inputFileCtrl',
bindToController: bindings,
controller: function () {
},
template: `<input class="ng-hide" id="input-file-id" type="file" />
<label for="input-file-id" class="md-button md-raised md-primary">{{ inputFileCtrl.getButtonLabel() }}</label>`,
link: link
};
function link(scope, element, attrs, controllers) {
if (angular.isDefined(attrs.multiple)) {
element.find('input').attr('multiple', 'multiple');
}
var inputFileCtrl = controllers[0];
var ngModelCtrl = controllers[1];
inputFileCtrl.getButtonLabel = function () {
if (ngModelCtrl.$viewValue == undefined || ngModelCtrl.$viewValue.length == 0) {
return inputFileCtrl.selectLabel;
}
else {
return ngModelCtrl.$viewValue.length + (ngModelCtrl.$viewValue.length == 1 ? " file" : " files") + " selected";
}
};
element.on('change', function (evt) {
ngModelCtrl.$setViewValue(element.find('input')[0].files);
ngModelCtrl.$render();
});
}
};
And here's the HTML
<body ng-app="myApp" ng-controller="MyController as ctrl">
<form name="ctrl.myForm">
<input-file select-label="Select Attachment" ng-model="ctrl.attachment1"></input-file>
<input-file select-label="Select Attachment" ng-model="ctrl.attachment2"></input-file>
</form>
</body>
It's pretty simple and it works - if only one is on the page. As soon as I add a second one, I notice that only the first one ever updates. If I select a file with the second one, the label updates on the first one. My suspicions are that the require ['inputFile'] is pulling in the controller for the first directive instance into the link function or something (which shouldn't happen). Even now as I type this, that doesn't really make sense to me. So what's going on here and how do I fix it?
Here's a codepen for you guys to play with and try to figure it out: http://codepen.io/astynax777/pen/PzzBRv
Your problem is not with your angular... is with you html.
You are assigning the same id twice.
Change your template to this:
template: `<label class="md-button md-raised md-primary">{{ inputFileCtrl.getButtonLabel() }}<input class="ng-hide" type="file" /></label>`
HTML :
<div ng-app="myApp" ng-controller="someController as Ctrl">
<div class="clickme" ng-repeat="elems in Ctrl.elem" ng-click="Ctrl.click(elems.title)">
{{elems.title}}
<span>click me</span>
<div id="container">
<test-Input title="elems.title" data="elems.id" ng-if="Ctrl.myId==" >/test-Input>
</div>
</div>
JS :
var Elems = [
{
title : "First",
id : 1
},
{
title : "Second",
id : 2
},
{
title : "Third",
id : 3
}
];
var myApp = angular.module('myApp', []);
myApp.controller('someController', function($scope) {
var self = this;
self.elem = Elems;
self.myId = false;
self.click = function(data){
self.myId = data;
};
});
myApp.directive('testInput',function(){
return {
restrict: 'E',
scope: {
myTitle: '=title',
myId: '=data'
},
template: '<div>{{myTitle}}</div>',
controller: function($scope) {
}
};
});
I'm new to angular js. when I click the "click me" div then I want to make ng-if = true result. then show (not ng-show it will renders every elements) the directive. is there any ways to do it angular way?
Here is a fiddle: http://jsfiddle.net/4L6qbpoy/5/
You can use something like:
<test-Input title="elems.title" data="elems.id" ng-if="elems.isVisible"></test-Input>
and toggle that on click
Check out this jsfiddle
You need to have the ng-if evaluate to true within the ng-repeat. So you need a condition that evaluates the unique value for each object in the array.
ng-if="Ctrl.myId==elems.title"
To understand, each instance of ng-repeat falls under the controller's scope. That means the ng-repeated elements are pushed to the boundaries and are only evaluated within your template. Within those bounds, you have access to a tiny local scope which includes $index, $first, $odd, etc..
You can read more here: https://docs.angularjs.org/api/ng/directive/ngRepeat
I'm trying to wrap my head around the reason that the one-time bound value (obj.value) inside the directive in this code example is being updated?
Updating the first field will update the bound value inside the directive only once, as expected. Afterwards, inside the directive, when clicking "edit", it will also update the one-time bound value AND also update the parent scope. Updating the first field again will not change the value inside the directive.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
</head>
<body ng-app="myApp" ng-controller="myCtrl" ng-model-options="{updateOn: 'blur'}">
Enter value here first, then press edit:<br>
<input type="text" ng-model="t.value"><br>
<br>
Press edit, change the value and press copy:
<my-directive obj="t"></my-directive><br><br>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myDirective', function() {
var directive = {};
directive.restrict = 'E';
directive.template = '<div ng-switch="edit">\
<div ng-switch-default>[{{ ::obj.value }}]<button ng-click="toggle()">edit</button></div>\
<div ng-switch-when="true">\
<input type="text" ng-model="clone.value">\
<button ng-click="copy()">copy</button>\
</div>\
</div>';
directive.scope = {
obj: '='
};
directive.controller = function($scope) {
$scope.edit = false;
$scope.toggle = function() {
$scope.edit = true;
$scope.clone = angular.copy($scope.obj);
}
$scope.copy = function() {
$scope.obj = angular.copy($scope.clone);
$scope.edit = false;
}
}
return directive;
});
myApp.controller('myCtrl', function(){
});
</script>
</body>
http://plnkr.co/edit/tbC3Ji6122gdqt4XbZpI?p=preview
In 1.3 they added a new syntax for helping with one-way binding, "::". So you just need to change your directive implementation to obj="::t".
Here's an update to your plnkr: http://plnkr.co/edit/7lsiX1ItPiQoVpJcQ6iW?p=preview
Here's a nice article that explains a bit more
It is because of ng-switch. Every time it's expression is recalculated the directive is 'redrawn'. And every time is does that the one time expression is also recalculated.
If you change your template to:
directive.template = '{{::obj | json}}<div ng-switch="edit">
etc...
you will see it won't change because it is outside of the ng-switch.