One time binding not working inside custom AngularJS Directive - javascript

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.

Related

angular js: why $scope.product do not update if it is a variable and update if it is a function

I am new to angular js. and I found a problem but I do not know why. can anybody explain that for me? Great thanks.
below is my html file:
<div ng-controller="index">
<input type="text" ng-model="factor" /> {{product}}
</div>
below is my js file:
angular.module("root", [])
.controller("index", ["$scope", function($scope) {
$scope.factor = 6;
$scope.product = $scope.factor * 2;
}]);
when I change the input to 5, the product value do not update as expected to 10.
but if I changed code like below,the product value update as soon as I change the input
html:
<div ng-controller="index">
<input type="text" ng-model="factor" /> {{product()}}
</div>
js:
angular.module("root", [])
.controller("index", ["$scope", function($scope) {
$scope.factor = 6;
$scope.product = function(){
return $scope.factor * 2
};
}]);
In the first case you initialized the $scope fields. And that's it. The field product is initialized using the value of the field factor but the fields are independent.
In the second case you defined a $scope function product and its return value is dependent on the $scope field factor thus any change of $scope.factor causes changes of $scope.product return value.
When the page is rendered, the product is 12, because 6 * 2 is 12. Even though you have two-way binding for the factor, but the code to assign factor * 2 to product is not executed again.
As suggested by the comments in, what you should do is set an ng-change event and do $scope.product = $scope.factor * 2; That should rebind the page if you change the factor.
The reason the product() works is that every time you make a change on the form, functions get executed. This is very in-effecient, because if you have other fields, the product function still runs, which may not be what you want.
Try this:
HTML:
<div ng-controller="index">
<input type="text" ng-change="factorChanged()" ng-model="factor" /> {{product}}
</div>
JS:
angular.module("root", [])
.controller("index", ["$scope", function($scope) {
$scope.factorChanged = function() {
$scope.product = $scope.factor * 2;
};
$scope.factor = 6;
$scope.factorChanged();
}]);
the best way is to use $watch,please check this plunker
app
.controller("index", ["$scope", function($scope) {
$scope.factor = 6;
$scope.$watch('factor',function(newValue,oldValue){
$scope.product = newValue *2;
});
}]);

AngularJS, how to trigger dom-related javascript on ng-if change

I have a form field (input text) with an ng-if being false at the begining. At some point the ng-if value become true.
When this happen, I want to execute some javascript which manipulate the DOM. To keep it simple, let's say that I need to select the input value and focus the field.
<input type="text" ng-value="foo" ng-if="show" onshow="doSomething()"/>
<button ng-click="toggle()"></button>
The JavaScript
ctrl.foo = "bar";
ctrl.show = false;
ctrl.toggle = function(){
ctrl.show = !ctrl.show;
}
I know that it looks like a "non-angular approach", but here I think the action is not model related.
Since the ng-if directive execute the template each time show become true, you can use ng-init for that. See the following snippet and replace alert('test); by anything you want.
angular.module('test', []).controller('test', function($scope, $element) {
$scope.show = false;
$scope.toggle = function() {
$scope.show = !$scope.show;
};
$scope.init = function() {
alert($element.find('input').val());
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="test">
<input type="text" value="foo" ng-if="show" ng-init="init()" />
<button ng-click="toggle()">Toggle</button>
</div>
</div>

ngModel not updating in select

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.

Angular JS get value into another variable

I have $scope.user = {} when I do this <input type="text" ng-model="user.name">the name in the input gets stored into $scope.user how do I get that value into another variable?
var myApp = angular.module('myApp', []);
myApp.controller('WizardController', ['$scope', function($scope){
$scope.user = {};
$scope.displayName = $scope.user{'name'};
}]);
I think you meant:
$scope.displayName = $scope.user.name
or
$scope.displayName = $scope.user['name']
Also, if you want to display the value of '$scope.user.name' somewhere in your html (which is what I assume you're trying to do) you can do something like this:
<h2>{{user.name}}</h2>
EDIT
If you want it to automatically refresh $scope.displayName in your view when you update $scope.user.name you'll need to add this to your controller:
$scope.$watch('user.name', function () {
$scope.displayName = $scope.user.name;
});
However, there's probably very few good reasons to do it that way.
Fiddle: http://jsfiddle.net/HB7LU/3752/ (Updated from one in comment to include logging).
If you want to put it in another variable you just have to make a new one.
$scope.newName = $scope.user.name;
The content will be put into the newName scope. Is that what you mean?
$scope.displayName = $scope.user{'name'}; I think you want to rewrite it to:
$scope.displayName = $scope.user.name or $scope.user['name'];

Angularjs- adding/removing dynamic html elements (dropdown)

here is my code-
http://plnkr.co/edit/oTWXbLIKOxoGTd4U0goD?p=preview
why is the days dropdown does not data bind with scope.demoDays, it is always empty?
is this the correct way to add dropdown dynamically? If user adds 5 dropdown, how to get the results , will ng-model="selectedDay" create an array of selection? any suggestions?
Thank you
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
var counter = 0;
$scope.fields = [];
$scope.days =['Day','Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
$scope.addField = function() {
$scope.fields.push({name:"test " + counter++});
};
});
app.directive('demoDisplay', function($compile){
return {
scope:{
demoDisplay:"=", //import referenced model to our directives scope
demoDays:"="
},
link:function (scope, elem, attr, ctrl)
{
scope.$watch('demoDisplay', function(){ // watch for when model changes
elem.html("") //remove all elements
angular.forEach(scope.demoDisplay, function(d){ //iterate list
var s = scope.$new(); //create a new scope
angular.extend(s,d); //copy data onto it
console.log(scope.demoDays);
var template = '<label class="item item-input"><div class="style-select"><select ng-model="selectedDay" ng-options="day for day in scope.demoDays"></select><br></div></label>';
elem.append($compile(template)(s)); // compile template & append
});
}, true) //look deep into object
}
}
})
html
<button ng-click="addField()">Add Field</button>
<div demo-display="fields" demo-days="days"></div>
There is no need for $watch in your link function - you have already established two-way binding by specifying = on your scope property. And you can use a plain template, without having to compile.
templateUrl: 'template.html',
where template.html is:
<label class="item item-input">
<div class="style-select">
<select ng-model="demoDisplay.selection" ng-options="day for day in demoDays"></select>
<br>
</div>
</label>
Notice that the select is bound to demoDisplay.selection, which will be created on each field and be accessible on the parent scope via two-way binding. Also, note that within ng-options, I changed scope.demoDays to just demoDays. In a directive's template you only need to use the property's name to access a scope value.
You can use the directive inside ng-repeat to create additional fields when the button is clicked:
<div ng-repeat="field in data.fields">
<div demo-display="field" demo-days="days"></div>
</div>
Here is a working plunker: http://plnkr.co/edit/pOY0l18W7wEbfSU7DKw2?p=preview
Any easy fix to get it working.
In your var template you have scope.demoDays.
Simply change this to demoDays. You are already in this scope so using it again isn't necessary.

Categories

Resources