Stop two way databinding on model - angularjs

I'm fairly new to Angular so if there is some incorrect thinking here, please let me know.
I'm trying to create two separate scope variables based on the same data set. I assumed that I would just be able to set them to different variables (as shown below) and it would work. I've found, however, that no matter what they are named or how they are defined (even in a directive!) that changing one changes them all.
So...what I expect/would like to see is that if I change the input in the top repeat it will only change the model for that repeat. Currently it changes all three.
Where am I going wrong here? I assume this has something to do with the two way data-binding. Thanks in advance!
Plnkr
HTML:
<h4>data</h4>
<div ng-repeat="person in data">
{{person.name}}
<input ng-model="person.name" />
</div>
{{data[0].name}}
<br>
<br>
<h4>testData</h4>
<div ng-repeat="person in testData">
{{person.name}}
<input ng-model="person.name" />
</div>
{{testData[0].name}}
<h4>Directive</h4>
<div tester data="data"></div>
Directive HTML:
<div ng-repeat="person in data">
{{person.name}}
<input ng-model="person.name" />
</div>
{{data[0].name}}
JS:
var app = angular.module('test', []);
(function () {
var testController = function ($scope) {
var data = [
{name:'Jordan', age:30},
{name:'Sean', age:32},
{name:'Seth', age:26}
];
$scope.data = data;
$scope.testData = data;
}
testController.$inject = ['$scope', '$http'];
app.controller('testController', testController);
}())
app.directive('tester', function(){
return {
restrict: 'A',
templateUrl: 'directive.html',
//If percent = true then that table should have a "percent change" th
scope:{
data: '=data'
}
}
})

I'm trying to create two separate scope variables based on the same
data set. I assumed that I would just be able to set them to different
variables (as shown below) and it would work
Actually both those javascript variables are pointing to the same data structure in memory. So when you modify this structure it reflects to both of them. Think of those data and testData variables as pointers to the same data.
You could copy this data structure in order to create 2 different instances of it in memory so that changes to one do not reflect to changes of the other:
$scope.data = data;
$scope.testData = angular.copy(data);
and if you wanted to reflect this in your directive, go ahead and clone the instance you are passing to it as well:
<div tester data="angular.copy(data)"></div>

Here both data and testData pointing to the same reference that why they are replicating same value.There are 2 solutions we can apply if there is function references, date object, and any undefined values which need to be there in the object after copy then appropriate option is $scope.testData = agular.copy(data);
Another options $scope.testData = JSON.parse(JSON.stringify(data)); but following keys won't be copyed.
functions.
Date object
properties with the value undefined

Related

ngModel with Arrays

I am trying to organize my (elsewhere defined) variables in an array, but this breaks the two-way-binding. I don't understand why I can bind to a variable directly, but not indirectly. I guess this is some stupid mistake.
Example here (jsfiddle) or below:
Html:
<div ng-controller="MyCtrl">
<input ng-model="test1"></input>
<input ng-model="test2[0]"></input>
<p>{{test1}}</p>
</div>
Javascript:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.test1 = 'text goes here';
$scope.test2 = [$scope.test1];
}
As you can see the first input is bound to the variable and updates it correctly, while the second one takes the initial value, but isn't bound.
It is working actually. See https://jsfiddle.net/ryekxkpL/2/
The $scope.test2[0] is a copy of $scope.test1, so it's the same as if you had $scope.test2 = ['text goes here']; Changing it won't effect $scope.test1.

multiple inputs based on array

My angular experience is basically about 3 days part time, so there's probably something simple I'm missing here.
I'm trying to create a dynamic list of multiple inputs based on an array, which I then want to reference from elsewhere in the app. What I've tried is loading a template from a custom directive, then $compile-ing it.
<input data-ng-repeat="term in query" data-ng-model="term">
My controller contains $scope.query = [""] which successfully creates the first empty input box. But the input box doesn't seem to update $scope.query[0] when I modify it. This means that when I try to create another empty input box with $scope.query.push(""); (from a keypress listener looking for the "/" key) I get a "duplicates not allowed" error.
I've tried manually listening to the inputs and updating scope.$query based on their value, but that doesn't feel very "angular", and results in weird behaviour.
What do I need to do to link these values. Am I along the right lines or way off?
I made a simple jsfiddle showing how to use an angular model (service) to store the data. Modifying the text inputs will also modify the model. In order to reference them somewhere else in your app, you can include TestModel in your other controllers.
http://jsfiddle.net/o63ubdnL/
html:
<body ng-app="TestApp">
<div ng-controller="TestController">
<div ng-repeat="item in queries track by $index">
<input type="text" ng-model="queries[$index]" />
</div>
<br/><br/>
<button ng-click="getVal()">Get Values</button>
</div>
</body>
javascript:
var app = angular.module('TestApp',[]);
app.controller('TestController', function($scope, TestModel)
{
$scope.queries = TestModel.get();
$scope.getVal = function()
{
console.log(TestModel.get());
alert(TestModel.get());
}
});
app.service('TestModel', function()
{
var queries = ['box1','box2','box3'];
return {
get: function()
{
return queries;
}
}
});

Angular.js. Two-way data binding breaks when using services

The problem is that SecondName attribute is not updating when I input text in the field.
please look at this code at jsfiddle: http://jsfiddle.net/HEdJF/253/
HTML:
<div ng-app="myApp">
<div ng-controller="FirstCtrl">
<div>
<input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
<br>FirstName is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
</div>
<hr>
<div ng-controller="SecondCtrl as Second">
SecondName: {{Second.Data.SecondName}}<!-- How do I automatically updated it here? -->
</div>
</div>
</div>
JS:
var myApp = angular.module('myApp', []);
myApp.service('Data', function(){
var obj;
return obj = { FirstName: '54',
SecondName: '22',
f: function(){
obj.FirstName = '1';
obj.SecondName = obj.FirstName;
}
};
});
myApp.controller('FirstCtrl', function( $scope, Data ){
Data.f();
$scope.Data = Data;
});
myApp.controller('SecondCtrl', function( Data ){
Second = this;
Second.Data = Data;
});
It's not going to work like you think it should. This line:
obj.SecondName = obj.FirstName;
creates a new property SecondName equal by value to the FirstName. However since both properties are primitive types (String) there is no connection between them. In other words obj.SecondName does not reference obj.FirstName.
You have two options.
Option 1. (bad) Set up additional watcher on FirstName change, and once that happens update SecondName respectively
$scope.$watch('Data.FirstName', function() { Data.SecondName = Data.FirstName; });
http://jsfiddle.net/HEdJF/254/
Option 2. Don't introduce additional watchers and change your architecture. For example, use FirstName in the second controller too, since they are supposed to be equal.
http://jsfiddle.net/HEdJF/255/
This is a scoping issue because of your nested scopes. Take a look at this website for a clear explanation: http://toddmotto.com/digging-into-angulars-controller-as-syntax/. There's a few different solutions to solve your problem under the Nested Scopes section.
The problem with your code is that the First Controller is just changing the value of Data.FirstName object, hence the changes are not reflecting on your second controller because the value of SecondName does not change after it is initialized in the first controller. So you have to set your Data.SecondName
in your data as well.
Alternatively, you can do this.
<div>
<input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
<br>FirstName is : <strong>{{Data.SecondName=Data.FirstName}}</strong><!-- Successfully updates here -->
</div>
You can also use directives to achieve this functionality, but I guess you are just looking for the above solution.
Cheers!

Preserving Scope in ng-repeat ( not wanting child scope )

I might be missing something conceptually but I understand that ng-repeat creates child scopes but for my scenario this is undesirable. Here is the scenario. I have a 3way bind to a firebase dataset. The object is an object with n sub objects. In my current code structure I use ng-repeat to iterate and render these objects with a custom directive. The issue is that these objects are meant to be "live" ( meaning that they are 3-way bound. The highest level object is bound with angularfire $bind ).
So the simple scenario in my case would be where the ng-repeat created scope was not isolated from the scope that it was created from.
I am looking for ideas on how to do this? Or suggestions on other approaches.
This won't be a complete answer, but I can help with the angularFire portion, and probably an angular guru can fill in the blanks for you (see //todo).
First of all, don't try to share scope. Simple pass the variables you want into the child scope. Since you'll want a 3-way binding, you can use & to call a method on the parent scope.
For example, to set up this pattern:
<div ng-repeat="(key,widget) in widgets">
<data-widget bound-widget="getBoundWidget(key)"/>
</div>
You could set up your directive like this:
.directive('dataWidget', function() {
return {
scope: {
boundWidget: '&boundWidget'
},
/* your directive here */
//todo presumably you want to use controller: ... here
}
});
Where &boundWidget invokes a method in the parent $scope like so:
.controller('ParentController', function($scope, $firebase) {
$scope.widgets = $firebase(/*...*/);
$scope.getBoundWidget = function(key) {
var ref = $scope.widgets.$child( key );
// todo, reference $scope.boundWidget in the directive??
ref.$bind(/*** ??? ***/);
};
});
Now you just need someone to fill in the //todo parts!
You still have access to the parent scope in the repeat. You just have to use $parent.
Controller
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.someParentScopeVariable = 'Blah'
$scope.data = [];
$scope.data.push({name:"fred"});
$scope.data.push({name:"frank"});
$scope.data.push({name:"flo"});
$scope.data.push({name:"francis"});
}]);
HTML
<body ng-controller="MainCtrl">
<table>
<tr ng-repeat="item in data | filter: search">
<td>
<input type="text" ng-model="$parent.someParentScopeVariable"/>
<input type="text" ng-model="item.name">
</td>
</tr>
</table>
</body>
Plunker

AngularJS - Using a Service as a Model, ng-repeat not updating

I'm creating an ajax search page which will consist of a search input box, series of filter drop-downs and then a UL where the results are displayed.
As the filters part of the search will be in a separate place on the page, I thought it would be a good idea to create a Service which deals with coordinating the inputs and the ajax requests to a search server-side. This can then be called by a couple of separate Controllers (one for searchbox and results, and one for filters).
The main thing I'm struggling with is getting results to refresh when the ajax is called. If I put the ajax directly in the SearchCtrl Controller, it works fine, but when I move the ajax out to a Service it stops updating the results when the find method on the Service is called.
I'm sure it's something simple I've missed, but I can't seem to see it.
Markup:
<div ng-app="jobs">
<div data-ng-controller="SearchCtrl">
<div class="search">
<h2>Search</h2>
<div class="box"><input type="text" id="search" maxlength="75" data-ng-model="search_term" data-ng-change="doSearch()" placeholder="Type to start your search..." /></div>
</div>
<div class="search-summary">
<p><span class="field">You searched for:</span> {{ search_term }}</p>
</div>
<div class="results">
<ul>
<li data-ng-repeat="item in searchService.results">
<h3>{{ item.title }}</h3>
</li>
</ul>
</div>
</div>
</div>
AngularJS:
var app = angular.module('jobs', []);
app.factory('searchService', function($http) {
var results = [];
function find(term) {
$http.get('/json/search').success(function(data) {
results = data.results;
});
}
//public API
return {
results: results,
find: find
};
});
app.controller("SearchCtrl", ['$scope', '$http', 'searchService', function($scope, $http, searchService) {
$scope.search_term = '';
$scope.searchService = searchService;
$scope.doSearch = function(){
$scope.searchService.find($scope.search_term);
};
$scope.searchService.find();
}]);
Here is a rough JSFiddle, I've commented out the ajax and I'm just updating the results variable manually as an example. For brevity I've not included the filter drop-downs.
http://jsfiddle.net/XTQSu/1/
I'm very new to AngularJS, so if I'm going about it in totally the wrong way, please tell me so :)
In your HTML, you need to reference a property defined on your controller's $scope. One way to do that is to bind $scope.searchService.results to searchService.results once in your controller:
$scope.searchService.results = searchService.results;
Now this line will work:
<li data-ng-repeat="item in searchService.results">
In your service, use angular.copy() rather than assigning a new array reference to results, otherwise your controller's $scope will lose its data-binding:
var new_results = [{ 'title': 'title 3' },
{ 'title': 'title 4' }];
angular.copy(new_results, results);
Fiddle. In the fiddle, I commented out the initial call to find(), so you can see an update happen when you type something into the search box.
The problem is that you're never updating your results within your scope. There are many approaches to do this, but based on your current code, you could first modify your find function to return the results:
function find(term) {
$http.get('/json/search').success(function(data) {
var results = data.results;
});
//also notice that you're not using the variable 'term'
//to perform a query in your webservice
return results;
}
You're using a module pattern in your 'public API' so your searchService returns the find function and an array of results, but you'd want to make the function find to be the responsible for actually returning the results.
Setting that aside, whenever you call doSearch() in your scope, you'd want to update the current results for those returned by your searchService
$scope.doSearch = function(){
$scope.searchService.results = searchService.find($scope.search_term);
};
I updated your jsfiddle with my ideas, is not functional but i added some commments and logs to help you debug this issue. http://jsfiddle.net/XTQSu/3/

Resources