I'm having a few problems editing a copy of a copy.
When you first edit a record it is assigned to a $scope.original and a copy is taken for editing and stored in $scope.copy which can be changed and saved back to $scope.original which in-turn updates $scope.something correctly.
The problem is while editing the first record if you then take a copy of one of the values for further editing, it doesn't get updated when the $scope.saveSomething() function is called.
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.Something = [{
name: "Aye",
desc: new Date()
}, {
name: "Bee",
desc: new Date()
}, {
name: "See",
desc: new Date()
}];
//=================== First copy
$scope.edit = function(what) {
$scope.original = what;
$scope.copy = angular.copy(what);
}
$scope.save = function(copy) {
angular.copy($scope.copy, $scope.original);
$scope.cancel();
}
$scope.cancel = function() {
$scope.copy = null;
}
//=================== Second copy
$scope.editName = function(what) {
$scope.originalName = what;
$scope.copyName = angular.copy(what);
}
$scope.saveName = function() {
angular.copy($scope.copyName, $scope.originalName);
$scope.cancelName();
}
$scope.cancelName = function() {
$scope.copyName = null;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<div ng-repeat="s in Something">
<pre>{{s | json}}</pre>
<a ng-click='edit(s)'>edit</a>
<br/>
<br/>
</div>
<input type='text' ng-model='copy.name' />
<input type='text' ng-model='copy.desc' />
<br/>
<button ng-click='save(copy)' ng-disabled="!copy">save</button>
<button ng-click='cancel()' ng-disabled="!copy">cancel</button>
<a ng-click='editName(copy.name)'>edit name</a>
<br>
<br>
<input type='text' ng-model='copyName' />
<br>
<button ng-click='saveName()' ng-disabled="!originalName">saveName</button>
<button ng-click='cancelName()' ng-disabled="!originalName">cancelName</button>
</div>
</div>
I'm fairly new to Angular, and have been scratching my head on this one for a while now, any ideas why?
Edit
Updated the code to give a better example, the first version suggested that you might know which value of the first edit's values you were editing, and the solution scarlz posted ( http://jsfiddle.net/Karl33to/w23ppp9r/ ) just sets that value directly in the second save function, but I need to be able to do the second edit on any of the values that the first edit loads.
Have also created a fiddle if that's easier for you to run / fork http://jsfiddle.net/w23ppp9r/2/
Your problem arises from your use of angular.copy. In $scope.saveName, your destination $scope.originalName is a string, which will result in angular throwing an error.
There is actually no reason to use angular.copy at all if you're working with primitives. Instead, you could use the following here:
$scope.editName = function(what) {
$scope.originalName = what;
$scope.copyName = what;
};
$scope.saveName = function() {
$scope.copy.name = $scope.copyName;
$scope.cancelName();
}
I've managed to come up with a simple solution, which seems to work.
Instead of passing the primitive to the second edit function, if I pass in the key and a copy of the object instead, I can then update the first copy correctly.
Here's a working fiddle http://jsfiddle.net/w23ppp9r/3/
... and the relevant bit of code:
//=================== Second copy
$scope.editSomething = function(key, obj) {
$scope.originalKey = key;
$scope.originalObj = obj;
$scope.copyVal = obj[key];
};
$scope.saveSomething = function(newVal) {
$scope.originalObj[$scope.originalKey] = newVal;
$scope.cancelEdit();
}
$scope.cancelEdit = function() {
$scope.originalKey = null;
$scope.originalObj = null;
$scope.copyVal = null;
}
Is there a better answer?
Related
I need to reflect some changes to controller B (inside some event) when I make change at controller A. For that I am using a service.
When I am changing service value from FirstCtrl, ng-change is not firing at SecondCtrl. Is there anything I have missed or need to change?
Please note that I am using angular 1.5.6. and don't want to use watch or even scope.
Below is my code.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
debugger
self.changeM = function() {
debugger
Data.FirstName = self.FirstName;
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.FirstName = Data;
self.changeM = function() {
alert(1);
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="FirstCtrl as c">
<input type="text" ng-model="c.FirstName" data-ng-change="c.changeM()">
<br>Input is : <strong>{{c.FirstName}}</strong>
<div ng-controller="SecondCtrl as c1">
Input should also be here: {{c1.FirstName}}
<input type="text" ng-model="c1.FirstName" data-ng-change="c1.changeM()">
</div>
</div>
<hr>
</div>
As you dont want to use $scope trying modifying the code in order to use $emit and $on feature in angular js to communicate between two controllers. You can refer this link.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
debugger
self.changeM = function() {
debugger
//Data.FirstName = self.FirstName;
Data.$on('emitData',function(event,args){
Data.FirstName=args.message
document.write(Data.FirstName)
})
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.FirstName = Data;
self.changeM = function() {
Data.$emit('emitData',{
message:Data.FirstName
})
};
}
]);
The only way then is to directly copy the reference of the data object within the controller. Note that you don't need ng-change to update the value then.
If you want something else, either wrap the FirstName in a sub object of Data and do the same i did :
Data = {foo:'FirstName'};
Or use $watch since it's the whole purpose of that function.
Here is a working code with copying the Data object in the controller.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
self.Data=Data;
debugger
self.changeM = function() {
debugger
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.Data = Data;
self.changeM = function() {
alert(1);
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="FirstCtrl as c">
<input type="text" ng-model="c.Data.FirstName" data-ng-change="c.changeM()">
<br>Input is : <strong>{{c.Data.FirstName}}</strong>
<div ng-controller="SecondCtrl as c1">
Input should also be here: {{c1.Data.FirstName}}
<input type="text" ng-model="c1.Data.FirstName" data-ng-change="c1.changeM()">
</div>
</div>
<hr>
</div>
The only way I know to solve the problem is using watch, unfortunately. (I am new to angular.)
From the ngChange document (https://docs.angularjs.org/api/ng/directive/ngChange):
The ngChange expression is only evaluated when a change in the input value causes a new value to be committed to the model.
It will not be evaluated:
if the value returned from the $parsers transformation pipeline has not changed
if the input has continued to be invalid since the model will stay null
**if the model is changed programmatically and not by a change to the input value**
I have a simple form with a checkbox which clicked deletes a property from an object.
Here is the controller:
app.controller('PropController', function ($scope) {
var str = '{"meta":{"aprop":"lprop"},"props":{"gprop":12,"lprop":9,"wrop":5}}';
$scope.filecontent = JSON.parse(str);
$scope.delprop = false;
$scope.propobj = $scope.filecontent.props;
$scope.proptodel = $scope.filecontent.meta.prop;
var mainvalue = $scope.propobj[$scope.proptodel];
$scope.$watch('delprop', function () {
if ($scope.delprop == true) {
delete $scope.propobj[$scope.proptodel];
} else {
$scope.propobj[$scope.proptodel] = mainvalue;
}
});
And the view:
<div ng-app="SomeProperties" ng-controller="PropController">
<div ng-if="proptodel">
there is a property to delete: {{proptodel}}
<form><input type="checkbox" ng-model="delprop"></form>
filecontent: {{filecontent}}
</div>
<div ng-if="!proptodel">
there is NO property to delete
</div>
</div>
The app on jsfiddle.
The problem appears when the form is in the ng-if, it stops behaving. As you can try it in the jsfiddle, when I delete ng-if="proptodel" from the div containing the form, it working normally. What is the explanation of this?
You need to put the delprop into in object to make ng-model work properly. That means your markup should have:
<form><input type="checkbox" ng-model="obj.delprop"></form>
And your Javascript code should look like:
$scope.obj = {
delprop: false
};
$scope.propobj = $scope.filecontent.props;
$scope.proptodel = $scope.filecontent.meta.prop;
var mainvalue = $scope.propobj[$scope.proptodel];
$scope.$watch('obj.delprop', function () {
if ($scope.obj.delprop == true) {
delete $scope.propobj[$scope.proptodel];
} else {
$scope.propobj[$scope.proptodel] = mainvalue;
}
});
Of course you should find a proper name for the object as obj is really bad and generic ;-)
I am writing a generic method to get some data from a service and populate in the dynamic property name passed in the function. the value does gets assigned to the text box using angular.element assignment but does not gets populated in the model. following is the code.
<div class="input-group">
<input class="form-control" id="ImgRollover" name="ImgRollover" ng-model="contentEntity.imgRollover" placeholder="" readonly="readonly" type="text">
<div class="input-group-btn">
<button class="btn" type="button" ng-click="pickImage('contentEntity.imgRollover')">
</button>
</div>
here is my controller method which internally uses a service which sends back a promise
$scope.pickImage = function (attrModel) {
ImageSelector.selectImage().then(
function (value) {
//angular.element("#" + attrModel).val(value);
$scope[attrModel] = value;
});
};
attrModel is a property name in the scope object contentEntity but the name of the property is known ONLY dynamically via the method parameter.
<button class="btn" type="button" ng-click="pickImage('contentEntity', 'imgRollover')"></button>
$scope.pickImage = function (attrModel1, attrModel2) {
ImageSelector.selectImage().then(function (value) {
$scope.[attrModel1][attrModel2] = value;
});
};
should work
I know this has already been well answered but I wanted to make a dynamic property creator.
It splits attrModel by '.' and then edits $scope and adds and/or returns each property if it either exists already or not, we preserve the last key outside of the while loop so that the value just has to be appended to it.
$scope.pickImage = function (attrModel) {
ImageSelector.selectImage().then(
function (value) {
var parent = $scope,
current,
attribute,
attributes = attrModel.split('.');
while(attributes.length > 1 &&
(attribute = attributes.shift()) &&
(current = parent[attribute] || (parent[attribute] = {}))) {
parent = current;
}
current[attributes[0]] = value;
});
};
Of course, if you want to do it the angular way you'd have to use a service in order to do that, it could look like this
jsfiddle here
angular.module('myApp').service('ObjectWalker', function () {
var getNodeData = function (object, path) {
var parent = object,
current,
attribute,
attributes = path.split('.');
while(attributes.length > 1 &&
(attribute = attributes.shift()) &&
(current = parent[attribute] || (parent[attribute] = {}))) {
parent = current;
}
return [current, attributes[0]];
};
return {
set: function(object, path, value) {
var nodeData = getNodeData(object, path);
nodeData[0][nodeData[1]] = value;
},
get: function(object, path) {
var nodeData = getNodeData(object, path);
return nodeData[0][nodeData[1]];
}
};
})
There is already an answer but, just like to post something for dynamic properties...
<!DOCTYPE html>
<html>
<head>
<script src="https://code.angularjs.org/1.2.23/angular.js"></script>
<script type="text/javascript">
var value = 0;
function mainCtrl($scope) {
value++;
$scope.pickImage = function (attrModel) {
value++;
alert(attrModel)
$scope[attrModel] = value;
};
$scope.getValue = function(attrModel) {
return $scope[attrModel];
}
}
</script>
</head>
<body ng-app ng-controller="mainCtrl">
<input type="text" ng-model="test.obj" />
<br/>
<button ng-click="pickImage(test.obj)">test</button>
<br/>
display the value afoter button click,
note there is no single quote
<br/>
value: {{ getValue(test.obj) }}
</body>
</html>
I've created a fiddle here: http://jsfiddle.net/nicktest2222/W4VaA/
I just want to be able to hit the reset button and put my original values back. Does anyone know the best way to go about doing this?
Thanks in advance
function TodoCtrl($scope) {
$scope.data = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
$scope.orig = [$scope.data];
$scope.reset = function() {
$scope.data = $scope.orig;
};
}
The problem is in JS clone mechanics. All you need to do is to create a deep copy of your model:
function TodoCtrl($scope) {
$scope.data = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}
];
$scope.orig = angular.copy($scope.data);
$scope.reset = function() {
$scope.data = angular.copy($scope.orig);
};
}
Here is the updated fiddle.
The simplest option is to use angular.copy to clone the original data, and then again to reset the data in $scope.
function TodoCtrl($scope) {
$scope.data = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}
];
var originalData = angular.copy($scope.data);
$scope.reset = function() {
angular.copy(originalData, $scope.data);
};
}
Edit: angular.copy when provided two values will empty the target object or array before copying the source values in to it. This can be really useful when dealing with child scopes.
madhead works initially, but afterwards the data is pointing to $scope.orig and reset would not work anymore. Would need to do a copy in the reset for it to work also.
Editted madhead's work:
http://jsfiddle.net/W4VaA/2/
function TodoCtrl($scope) {
$scope.data = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}
];
$scope.orig = angular.copy($scope.data);
$scope.reset = function() {
$scope.data = angular.copy($scope.orig);
};
}
Thank God for new angular versions. but for those like me, that still have to maintain old angular js code you have to write some doggy code like this.
$scope.[moduleName] = { [variableName]: '' };
$scope.[formName].[variableName].$touched = false;
$scope.[formName].[variableName].$$untouched= false;
you can also write a function to handle lots of input elements like this. but it used jquery and bootstrap 3
HTML
<ng-form class="form" name="formName" novalidate>
<div class="row">
<div class="col-md-6"">
<div class="form-group">
<label class="control-label">
input1 <span class="symbol required"></span>
</label>
<select id="input1" name="input1" class="form-control" required ng-model="model.input1">
<option value="">Select Optionn</option>
<option ng-repeat="option in options" value="{{option.id}}">{{option.Description}}</option>
</select>
</div>
</div>
</div>
</ng-form>
Controller.js
$scope.resetForm = function () {
$scope.model = { input1: '' }; // reset form value
let form = $(".form"),
frmElm = $scope.formName; // referees to name="" for ng-form element
form.find('.form-control').each(function (item) {
let element = $(this),
id = element.attr("id");
if (frmElm[id]) {
var scopeElement = frmElm[id];
scopeElement.$touched = false;
scopeElement.$untouched = false;
}
})
};
I'm writing a small application in JS and I decided to use Knockout.
Everything work well except from a single value that is not printed correctly and I don't understand why.
This is the html view where error appends (viaggio.arrivo is not visualized, and in place of correct value appears a function code like this "function c(){if(0 <arguments.length){if ..." and so on)
<input data-bind="value: viaggio.arrivo" />
And this is the javascript View Model.
Code is pretty long so I put it in a jsFiddle.
function ViewModel() {
function Viaggiatore(nome, cognome, eta, citta) {
var self = this;
self.nome = nome; self.cognome = cognome;
self.eta = ko.observable(eta);
self.citta = ko.observable(citta);
}
function Viaggio(viaggiatore, partenza, arrivo, mete) {
var self = this;
self.viaggiatore = ko.computed(viaggiatore);
self.partenza = ko.computed(partenza);
self.arrivo = ko.observable(arrivo);
self.mete = ko.computed(mete);
}
self.viaggiatore = new Viaggiatore("Mario", "Rossi", 35, "Como");
self.viaggio = new Viaggio(
function(){ return self.viaggiatore.nome+" "+self.viaggiatore.cognome; },
function(){ return self.viaggiatore.citta; },
"Roma",
function(){ return "mete" ;}
);
}
ko.applyBindings(new ViewModel());
I think you need brackets on one of your parameters, like so:
<p data-bind="text: viaggio.partenza()"></p>
Check out the updated fiddle: http://jsfiddle.net/mGDwy/2/