I have a simple issue with AngularJS: the properties outside of the array update normally, but the array elements (that are bound to those properties) don't, here is a plunker to show that: http://plnkr.co/edit/gNtGhiTf228G8rP0O2iB?p=preview
var vm = this;
vm.topLine = {
netWrittenPremiumLastFye: 1000000,
netWrittenPremiumYtd: 123000
}
vm.grossColumnChartData = [
['Previous YE', vm.topLine.netWrittenPremiumLastFye],
['YTD', vm.topLine.netWrittenPremiumYtd]
];
We can use $scope.$watch('something', function(oldValue, newValue) {...}) but that's not a good choice in my situation since i'll have to do that many times, repeatedly.
I want to take advantage of angular 2-way binding as explained above.
Any helps or suggestions are appreciated, thanks!
I think the only way you can do this without using $watch is by updating the array directly in the html binding.
{{ grossColumnChartData = [
['Previous YE', vm.topLine.netWrittenPremiumLastFye],
['YTD', vm.topLine.netWrittenPremiumYtd]
] }}
<br/>
grossColumnChartData = {{grossColumnChartData | json}}
See this:
http://plnkr.co/edit/uthXcvoWSV2XNRFJzkck?p=preview
I have updated my plnkr and i have my answer now: http://plnkr.co/edit/gNtGhiTf228G8rP0O2iB?p=preview
<div ng-repeat="item in vm.grossColumnChartData">
<label>Input {{$index+1}}:</label>
<input ng-model="item.data" type="text"/>
</div>
I was confused between data binding and reference.
I used ng-repeat to update the array elements.
Related
I'm trying to make a minimal but fancy AngularJS tutorial example, and I am running into an issue where after updating the entire tree for a model (inside the scope of an ng-change update), a template that is driven by a top-level ng-repeat is not re-rendered at all.
However, if I add the code $scope.data = {} at a strategic place, it starts working; but then the display flashes instead of being nice and smooth. And it's not a great example of how AngularJS automatic data binding works.
What am I missing; and what would be the right fix?
Exact code - select a country from the dropdown -
This jsFiddle does not work: http://jsfiddle.net/f9zxt36g/
This jsFiddle works but flickers: http://jsfiddle.net/y090my10/
var app = angular.module('factbook', []);
app.controller('loadfact', function($scope, $http) {
$scope.country = 'europe/uk';
$scope.safe = function safe(name) { // Makes a safe CSS class name
return name.replace(/[_\W]+/g, '_').toLowerCase();
};
$scope.trunc = function trunc(text) { // Truncates text to 500 chars
return (text.length < 500) ? text : text.substr(0, 500) + "...";
};
$scope.update = function() { // Handles country selection
// $scope.data = {}; // uncomment to force rednering; an angular bug?
$http.get('https://rawgit.com/opendatajson/factbook.json/master/' +
$scope.country + '.json').then(function(response) {
$scope.data = response.data;
});
};
$scope.countries = [
{id: 'europe/uk', name: 'UK'},
{id: 'africa/eg', name: 'Egypt'},
{id: 'east-n-southeast-asia/ch', name: 'China'}
];
$scope.update();
});
The template is driven by ng-repeat:
<div ng-app="factbook" ng-controller="loadfact">
<select ng-model="country" ng-change="update()"
ng-options="item.id as item.name for item in countries">
</select>
<div ng-repeat="(heading, section) in data"
ng-init="depth = 1"
ng-include="'recurse.template'"></div>
<!-- A template for nested sections with heading and body parts -->
<script type="text/ng-template" id="recurse.template">
<div ng-if="section.text"
class="level{{depth}} section fact ng-class:safe(heading);">
<div class="level{{depth}} heading factname">{{heading}}</div>
<div class="level{{depth}} body factvalue">{{trunc(section.text)}}</div>
</div>
<div ng-if="!section.text"
class="level{{depth}} section ng-class:safe(heading);">
<div class="level{{depth}} heading">{{heading}}</div>
<div ng-repeat="(heading, body) in section"
ng-init="depth = depth+1; section = body;"
ng-include="'recurse.template'"
class="level{{depth-1}} body"></div>
</div>
</script>
</div>
What am I missing?
You changed reference of section property by executing section = body; inside of ng-if directives $scope. What happened in details (https://docs.angularjs.org/api/ng/directive/ngIf):
ng-repeat on data created $scope for ng-repeat with properties heading and section;
Template from ng-include $compile'd with $scope from 1st step;
According to documentation ng-if created own $scope using inheritance and duplicated heading and section;
ng-repeat inside of template executed section = body and changed reference to which will point section property inside ngIf.$scope;
As section is inherited property, you directed are displaying section property from another $scope, different from initial $scope of parent of ngIf.
This is easily traced - just add:
...
<script type="text/ng-template" id="recurse.template">
{{section.Background.text}}
...
and you will notice that section.Background.text actually appoints to proper value and changed accordingly while section.text under ngIf.$scope is not changed ever.
Whatever you update $scope.data reference, ng-if does not cares as it's own section still referencing to previous object that was not cleared by garbage collector.
Reccomdendation:
Do not use recursion in templates. Serialize your response and create flat object that will be displayed without need of recursion. As your template desired to display static titles and dynamic texts. That's why you have lagging rendering - you did not used one-way-binding for such static things like section titles. Some performance tips.
P.S. Just do recursion not in template but at business logic place when you manage your data. ECMAScript is very sensitive to references and best practice is to keep templates simple - no assignments, no mutating, no business logic in templates. Also Angular goes wild with $watcher's when you updating every of your section so many times without end.
Thanks to Apperion and anoop for their analysis. I have narrowed down the problem, and the upshot is that there seems to be a buggy interaction between ng-repeat and ng-init which prevents updates from being applied when a repeated variable is copied in ng-init. Here is a minimized example that shows the problem without using any recursion or includes or shadowing. https://jsfiddle.net/7sqk02m6/
<div ng-app="app" ng-controller="c">
<select ng-model="choice" ng-change="update()">
<option value="">Choose X or Y</option>
<option value="X">X</option>
<option value="Y">Y</option>
</select>
<div ng-repeat="(key, val) in data" ng-init="copy = val">
<span>{{key}}:</span> <span>val is {{val}}</span> <span>copy is {{copy}}</span>
</div>
</div>
The controller code just switches the data between "X" and "Y" and empty versions:
var app = angular.module('app', []);
app.controller('c', function($scope) {
$scope.choice = '';
$scope.update = function() {
$scope.data = {
X: { first: 'X1', second: 'X2' },
Y: { first: 'Y1', second: 'Y2' },
"": {}
}[$scope.choice];
};
$scope.update();
});
Notice that {{copy}} and {{val}} should behave the same inside the loop, because copy is just a copy of val. They are just strings like 'X1'. And indeed, the first time you select 'X', it works great - the copies are made, they follow the looping variable and change values through the loop. The val and the copy are the same.
first: val is X1 copy is X1
second: val is X2 copy is X2
But when you update to the 'Y' version of the data, the {{val}} variables update to the Y version but the {{copy}} values do not update: they stay as X versions.
first: val is Y1 copy is X1
second: val is Y2 copy is X2
Similarly, if you clear everything and start with 'Y', then update to 'X', the copies get stuck as the Y versions.
The upshot is: ng-init seems to fail to set up watchers correctly somehow when looped variables are copied in this situation. I could not follow Angular internals well enough to understand where the bug is. But avoiding ng-init solves the problem. A version of the original example that works well with no flicker is here: http://jsfiddle.net/cjtuyw5q/
If you want to control what keys are being tracked by ng-repeat you can use a trackby statement: https://docs.angularjs.org/api/ng/directive/ngRepeat
<div ng-repeat="model in collection track by model.id">
{{model.name}}
</div>
modifying other properties won't fire the refresh, which can be very positive for performance, or painful if you do a search/filter across all the properties of an object.
I have a list of checkboxes as following :
<div flex="50" ng-repeat="formationType in formationTypeList">
<md-checkbox class="md-warn md-align-top-left"
value="{{formationType.codeFormation}}"
name="formationSelection[]"
ng-checked="formationSelection.indexOf(formationType) > -1"
ng-click="toggleFormationTypeSelection(formationType)">
{{ formationType.nom }}
</md-checkbox>
</div>
This is the format of formationSelection after I send my form :
formationSelection = [
{
codeFormation: 1,
nom: "ENSA"
},
{
codeFormation: 2,
nom: "CPGE"
}
]
In another scenario I want when I open my form to check the checkboxes which are defined in an array as following :
$scope.formationSelection = res.candidatureProjetProfessionnel.formations;
the object res.candidatureProjetProfessionnel.formations contains this :
formationSelection = [
{
codeFormation: 1,
nom: "ENSA"
},
{
codeFormation: 2,
nom: "CPGE"
}
]
And when I inspect $scope.formationSelection it contains the data I got from res.candidatureProjetProfessionnel.formations :
But I don't know why my checkboxes are not checked even though the $scope.formationSelection is not empty.
How can I solve this ?
i'm not sure what the md-checkbox directive is so i'm just going to use a normal checkbox input. Generally speaking, setting a default value for inputs in angular involves 2 things:
Make sure your inputs have ng-model to store the value of the checkbox and for 2 way data binding (so that you can set it from the controller as well)
In your controller set the variable declared in the ng-model to whatever default value you want.
So in you html:
<input type="checkbox" class="md-warn md-align-top-left" ng-
model="formationSelection[$index]" ng-true-value="{{formationType}}"
name="formationSelection[]">
Make sure you use ng-true-value to declare the value of each checkbox when checked. The ng-model is set to formationSelection[$index] which basically means each checkbox is an item inside the formationSelection array, this way the array will be the collection of the values of all checked inputs.
Now $scope.formationSelection = res.candidatureProjetProfessionnel.formations should work
Here's a working plunker:
http://plnkr.co/edit/sGm39DRWH9EOReiiSrIl?p=preview
You have to use ng-model as shown below.It should be an object like $scope.data = {};.This is just an example where hope you can get the point and work on your scenario. Actually you're having individual check boxes as shown below but values are being set through the loop.So you can apply this concept for your use case as well.Hope this will help to you.
Html
<md-checkbox ng-model="data.cb1" aria-label="Checkbox 1">
Checkbox 1: {{ data.cb1 }}
</md-checkbox>
JS
$scope.data = {};
$scope.data.cb1 = true;
Play with it on Codepen
I think that your method isFormation(formationType) on the directive ng-checked it's not return the result.
In your controller create a function
$scope.isFormation(_type){
return $scope.formationSelection.filter(function(f){return f.nom === _type;}).length > 0;
}
I have some data in the form of array, which I am converting into an object using _.groupBy(data, ""filter"). After grouping, it looks like this
$scope.data = {"Key1" : [{}, {}], "Key2" : [{}] }
I am looping this object using ngRepeat, and have a directive inside the ngRepeat like below.
<div ng-repeat = "(key, value) in data">
<my-directive id={{$index}} group = "data[key]"></my-directive>
</div>
The problem I am facing is, on refreshing and calling the service again, the updated array of keys is not displayed on the UI. However, if any new key is added in the data object, only then UI is updating.
Any suggestions please.
Hi I am a newbie to angular js and I am hoping someone can help me out with the following problem.
I have a numeric field called numAdults and I need to show a set of field (such as name, address, telephone etc ) numAdult times to get those information for each of those person.
Here is the jsfiddle for the problem jsfiddle link
Here is also an overview of code of the controller
function bookingController($scope){
$scope.numAdults = 1;
$scope.personLoop = function(){
console.log('personLoop called')
return new Array($scope.numAdults);
//return new Array(2);
}
the html
<label for="book_num_adults">Number of adults:</label>
<input id="book_num_adults" type="text" ng-model="numAdults">
<div class="row" ng-repeat="t in personLoop()" style="border:2px solid red;margin-top:10px">
<h4>Person {{$index+1}}</h4>
<input placeholder="name"><br>
<input placeholder="address"><br>
<input placeholder="telephone"><br>
</div>
Can you also help me with how to transform this as an module ( not just a controller based )
Thank you in advance!
Your Fiddle was riddled with errors...
http://jsfiddle.net/bRgTR/5/
Under Frameworks & Extensions, you need to change the 2nd dropdown from "onLoad" to one of the "No wrap" options
Your controller definition is mangled. It's supposed to be: .controller('name', ['depname', function (depname) { })]); -- you had your closing array misplaced.
You should really use semi-colons.
You don't create an array of 5 items in JavaScript like this: var a = new Array(5), that creates an array that contains a 5. Instead, you should do var a = []; a.length = 5;
I had asked a question about
How to associate objects as models using ng-options in angularjs.
And I got an awesome answer very fast. My followup questions is that the response uses <select mutiple> to handle the child object array.
You can see a working example of what I want, working with <select> at http://plnkr.co/edit/FQQxrSE89iY1BnfumK0A?p=preview
How can I use <input type='checkbox'> (instead of <select>) to handle that object array i.e. ng:model="shirt.colors" while repeating the items from colors object array.
The reason, this appears so complicated to me is that I have to manage an array of objects instead of array of values... for example, if you look in the fiddle, there are color objects and shirt object that has multiple colors.
If the color object changes, it should change the corresponding color objects in shirt objects.
Thank you in advance.
You just need some intermediate value in your scope, and bind checkboxes to it. In your controller - watch for it changes, and manually reconstruct shirt.colors, according to it value.
<div ng-repeat='shirt in shirts'>
<h3>Shirt.</h3>
<label>Size: <input ng-model='shirt.size'></label><br/>
<label>Colors:</label>
<label ng-repeat="color in colors">
{{color.label}} <input ng-model="selection[$parent.$index][$index]" type="checkbox"/>
</label>
</label>
</div>
And in your controller:
$scope.selection = [[],[]];
$scope.$watch('selection', function () {
console.log('change', $scope.selection);
angular.forEach($scope.selection, function (shirtSelection, index) {
$scope.shirts[index].colors = [];
angular.forEach(shirtSelection, function (value, index2) {
if (value) $scope.shirts[index].colors.push($scope.colors[index2]);
});
});
}, true);
You can test it here: http://plnkr.co/edit/lh9hTa9wM5fkh3nT09RJ?p=preview