AngularJS - ngRepeat and ngMessages - javascript

I struggle to make ngMessages work in a ngRepeat loop. Please consider the following example :
<form name="$ctrl.demoForm.$fc" novalidate>
<div ng-repeat="tag in $ctrl.demoForm.tags track by $index">
<div>
<label>Tag:</label>
<input name="tag{{$index}}" ng-model="tag.value" type="text" ng-pattern="/^[a-z]*$/">
<button ng-click="$ctrl.removeTag($index)">Remove</button>
</div>
<ng-messages for="$ctrl.demoForm.$fc['tag'+$index].$error">
<ng-message when="pattern">Must only contain letters.</ng-message>
</ng-messages>
</div>
<button ng-click="$ctrl.addTag()">Add new tag</button>
</form>
I simply iterate over an array of tags stored in $ctrl.demoForm.tags. I'm aware that ng-repeat creates a new scope each iteration so each tag is an object like {value: 'tag value'}.
Here is the controller associated, very basic stuff:
app.controller('DemoCtrl', function() {
this.demoForm = {
tags: [
{value: 'tag 1'},
{value: 'tag 2'}
]
};
this.addTag = function(){
this.demoForm.tags.push({value: ''});
};
this.removeTag = function(index) {
if (this.demoForm.tags.length > index) {
this.demoForm.tags.splice(index, 1);
}
};
});
It works fine, I can add and remove tags normally. But, problems arise when a field has an error. If you try to remove a field above the field on error, their values get messed up.
The odd thing is that the good field is actually removed, only their values seems desynchronized when on error. I added an id attribute in the jsbin to illustrate the problem, you can find it here :
http://jsbin.com/nifipatojo/edit?html,js,output
Does anyone have an idea of what happen ?
Thanks for your help.

Related

Angular 4 nested form update

I've got the following 'triple level' nested form:
FormGroup->ArrayOfFormGroups->FormGroup
Top level (myForm):
this.fb.group({
name: '',
description: '',
questions: this.fb.array([])
});
Nested form array element for 'questions':
this.fb.group({
priority: ['1'],
params: this.fb.group({parameter: ['']})
});
Nested form group element for 'params' is a key:value object of random length.
I'm using the following ngFor to go through elements:
<tr *ngFor="let questionConfigForm of myForm.controls.questions.controls; let i=index" [formGroupName]="i">
...
<div *ngFor="let param of objectKeys(questionConfigForm.controls.params.controls)" formGroupName="params">
<input type="text" [formControlName]="param">
I've got the following behavior:
When I'm updating any of the fields on first two form levels I could instantly see changes in corresponding form controls values with {{myForm.value | json}}.
But if I input something in one of 'params' controls I couldn't see any changes in myForm values, but the form data for 'params' controls will be updated if I will make any changes in corresponding 'questions' form.
For me it looks like 'param' form control receives input data, but doesn't trigger some update event, and I don't know how to fix that, except writing my own function to react on (change) and patchValue in form..
So my question is how to make 'params' controls update myForm without that strange behavior?
UPD:
initQuestionConfig() {
return this.fb.group({
priority: ['1'],
params: this.fb.group({parameter: ['']}),
});
}
addQuestionConfig() {
const control = <FormArray>this.myForm.controls['questions'];
const newQuestionCfg = this.initQuestionConfig();
control.push(newQuestionCfg);
}
Finally the problem is solved.
The root of this issue was the way I've cleaned up already existing 'params'.
To remove all parameters from 'questions' I used the following code:
const control = <FormArray>this.myForm.controls['questions'];
control.controls[index]['controls'].params = this.fb.group([]);
And the reason of those glitches was this new 'fb.group' instance.
Now I'm removing params one by one, keeping original formGroup instance and it works as expected:
const control = <FormArray>this.myForm.controls['questions'];
const paramNames = Object.keys(control.controls[index]['controls'].params.controls);
for (let i = 0; i < paramNames.length; i++) {
control.controls[index]['controls'].params.removeControl(paramNames[i]);
}
#MilanRaval thanks for your time again :)
Try this: Give formArrayName and formGroupName like below...
<div formArrayName="testGroup">
<div *ngFor="let test of testGroup.controls; let i=index">
<div [formGroupName]="i">
<div class="well well-sm">
<label>
<input type="checkbox" formControlName="controlName" />
</div>
</div>
</div>
</div>

Avoid reflow while editing ordered input ng-repeat on Angular

I'm experiencing some problems while inputing user data into ordered fields displayed with Angular ng-repeat.
Say that you want some values to display on a list, and those values might be editable. At the same time, you are ordering that data. Due to how ng-model works and Angular reflow cycle, if the value of one input surpases another one while still editing, you'll find yourself typing on the wrong field. Look at this example:
var app = angular.module('app', []);
app.directive('myrow', Row);
app.controller('controller', Controller);
function Controller () {
this.order = '-value';
this.inputs = [
{value: 1, tag: "Peas"},
{value: 2, tag: "Apples"},
{value: 3, tag: "Potatos"}
];
}
function Row($compile, $sce){
var linker = function($scope, $element, $attrs){
var template = '<div>- <input type="number" ng-model="data.value"><span ng-bind="data.tag"></span></div>';
a = $element.html(template);
$element.html(template);
$compile($element.contents())($scope);
}
return {
restrict: 'AE',
replace: true,
scope: {
data: "="
},
link: linker
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<div ng-app="app" ng-controller="controller as ctrl">
List:
<div ng-repeat="item in ctrl.inputs | orderBy: ctrl.order">
<div myrow data="item"></div>
</div>
</div>
I've made this simplified example as the original component has thousands of lines and some dependencies. Here this problem is not reproduced exactly, yet, when you write, sometimes the input loses focus, thing that, for example, doesn't happen when not compiling on the directive (which is completly necessary in my real code). Any ideas on how to solve this? Is it possible to activate ng-model update on change instead of on user typing.
You can use ng-model-options and its updateOn property so that your model is updated only when user leaves the field.
You can see how it works here: https://docs.angularjs.org/api/ng/directive/ngModelOptions (There is a sample in the 'Triggering and debouncing model updates' section)
example:
<input ng-model-options="{ updateOn: 'blur'}" />

Add dynamic model to newly created elements via directives in angularjs

Following is my PLNKR CODE which is working fine.
Problem - I need to add dynamic scope to these element so that I can grab the contact number + type.
I google the problem before asking but as I am new to directives in angular I am confused with the results, let me know what else I need to add to grab the result.
Following kind of result I am expecting -
contact: [
{number: 56432452, type: "Cell"},
{number: 67895644, type: "Work"},
{number: 78943245, type: "Cell"},
{number: 66793456, type: "Home"},
{number: 90546675, type: "Fax"},
];
Also, I need to use the same form in EDIT mode, let me know what are the extra things that I need to keep in mind while developing this functionality for the edit case.
Following is my directive code -
<div class="form-group">
<label class="col-sm-2 control-label">Contact Number<span class="asterisk">*</span></label>
<div class="col-sm-5">
<input type="text" class="form-control">
</div>
<div class="col-sm-2">
<select class="btn">
<option>Cell</option>
<option>Work</option>
<option>Home</option>
<option>Fax</option>
</select>
</div>
<div class="col-sm-1">
<img src="http://img.informer.com/icons/png/16/3225/3225535.png" class="remCls">
</div>
</div>
As you can see currently the select and input do not contain and ngModel. Let me know how do I introduce this to obtain the above mentioned result.
I'm not sure this is what you need but I think you could define your controller as:
myApp.controller("myCtrl", function($scope){
//Create and array of contacts in your model
$scope.contacts = [];
//Add a new contact to the model
$scope.addContact = function() {
var contacts = $scope.contacts;
contacts[contacts.length] = {};
}
//Remove a contact from the model based on its index
$scope.removeContact = function(index) {
$scope.contacts.splice(index, 1);
}
});
Then on your HTML, you leverage the Angular directives ng-repeat and ng-click:
<body ng-controller="myCtrl">
<button ng-click="addContact()"> Add Contact </button>
<div class="form-group" ng-repeat="contact in contacts">
<label>Contact Number</label>
<input type="text" ng-model="contact.contact">
<select ng-model="contact.type">
<option>Cell</option>
<option>Work</option>
<option>Home</option>
<option>Fax</option>
</select>
<button ng-click="removeContact($index)"> Remove Contact </button>
</div> <!-- Close Repeater -->
</body>
Here's your PLNKR link with the changes proposed:
http://plnkr.co/edit/VWCdXSnOsY18XoCKxO0t?p=preview
First of all I would like to thank ExpertSystem for suggesting me to think in Angular way. Then I would like to thank Foxandxss and medice from angular IRC for making the things right not by code but improving my concept and approach for angular.
This is the WORKING code, I came up with for the above problem.
Actually I don't need directive and managed things easily without it.
medice: directives are fine, but when you set up click events that
modify dom, it's gonna break
medice: angularjs can't bind directives properly
Following is my controller code -
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope){
$scope.cnctnum = [];
$scope.cncttype = [];
$scope.types = [
{name: "Cell", value: 1},
{name: "Work", value: 2},
{name: "Home", value: 3},
{name: "Fax", value: 4}
];
$scope.items = [];
var i =0;
$scope.addCnt = function(){
$scope.items.push(i);
i++;
};
$scope.remCl = function(index){
$scope.cnctnum.splice(index, 1);
$scope.cncttype.splice(index, 1);
$scope.items.splice(index, 1);
};
$scope.getval = function(){
console.log($scope.cnctnum);
console.log($scope.cncttype);
};
});

Angular ng-class performance issue when too many elements in DOM

I have been working on a complex angular page which has been causing performance issue. To highlight the problem I have created a fiddle http://jsfiddle.net/4ex2xgL1/3/ here.
Essentially the performance issue is being caused by ng-class statement which has a function in it.
<span class="done-{{todo.done}}" ng-class="myfunction()">{{todo.text}}</span>
The span is in an ng-repeat. On running the fiddle one can see that ng-class gets executed several times when the page loads and on each key up it gets called as many time as number of items in the TODO list.
This is a lot simpler case, in my case I have 780 items on my page and the function ends up being evaluated aroung 3000 times!
One of the solution we saw is to break up the scope but it will cause almost a rewrite of my app.
We also tried https://github.com/Pasvaz/bindonce but it doesn't seem to be working with highly dynamic content.
Any thoughts?
I built a tree with https://github.com/JimLiu/angular-ui-tree with almost 500 items to render, with quite a lot of listeners. It takes 5 seconds to render. Bindonce won't work there.
The only solution out there is make ng-repeat do less. Keep the list small with a pagination, search or anything. Its the best shot as far as I know.
Well here are my recommendations
use ng-change on the checkbox to manipulate dom or anything rather using ng-class, it will improve your performance drastically.
<li ng-repeat="todo in todos track by todo.id">
<input type="checkbox" ng-model="todo.done" ng-change="myfunction()">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
http://jsfiddle.net/4ex2xgL1/3/
use track by in ng-repeat if you have ids, more here http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by/
dont show 780 items in a list. Use a searchbox to show some 100 or 50 or you know better
quick-ng-repeat not used yet, try testing it https://github.com/allaud/quick-ng-repeat
finally a few good http://tech.small-improvements.com/2013/09/10/angularjs-performance-with-large-lists/
Finally I found the solution and it will helps lot to improve performance in angular js.
If your model changes dynamically and if you have lots of data and then also it improve AngularJS pages rendering up to 1000% and more - no kidding !.
Fore more information you can visit : http://orangevolt.blogspot.in/2013/08/superspeed-your-angularjs-apps.html
Follow the steps:
download the library from the link:library
2.example without library:(check your console)
function MyController( $scope) {
var entries = [
{ label : 'one', value : 'first entry'},
{ label : 'two', value : 'second entry'},
{ label : 'three', value : 'third entry'}
];
$scope.label ="";
$scope.value ="";
$scope.order = 'label';
$scope.add = function() {
entries.push({
label : $scope.label,
value : $scope.value
});
};
$scope.getEntries = function() {
console && console.log( "getEntries() called");
return entries;
};
}
<script src="https://raw.githubusercontent.com/lodash/lodash/2.4.1/dist/lodash.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<form name="myform" ng-app ng-controller="MyController">
Label/Value :
<input type="text" required ng-model="label">
<input type="text" required ng-model="value">
<button
ng-disabled="!myform.$valid"
ng-click="add()"
>Add</button>
<fieldset>
<legend>
Entries sorted by
<select
ng-model="order"
ng-options="property for property in [ 'label', 'value']">
</select>
</legend>
<div ng-repeat="entry in getEntries() | orderBy:order">
{{entry.label}} = "{{entry.value}}"
</div>
</fieldset>
</form>
3.example with library:(check your console)
function MyController( $scope) {
var entries = [
{ label : 'one', value : 'first entry'},
{ label : 'two', value : 'second entry'},
{ label : 'three', value : 'third entry'}
];
$scope.label ="";
$scope.value ="";
$scope.order = 'label';
$scope.add = function() {
entries.push({
label : $scope.label,
value : $scope.value
});
// clear cache
$scope.getEntries.cache = {};
};
$scope.getEntries = _.memoize(
function() {
console && console.log( "getEntries() sorted by '" + $scope.order + " 'called");
// return entries sorted by value of $scope.order
return _.sortBy( entries, $scope.order);
},
function() {
// return the cache key for the current result to store
return $scope.order;
}
);
}
<script src="https://raw.githubusercontent.com/lodash/lodash/2.4.1/dist/lodash.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.20/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<form name="myform" ng-app ng-controller="MyController">
Label/Value :
<input type="text" required ng-model="label">
<input type="text" required ng-model="value">
<button
ng-disabled="!myform.$valid"
ng-click="add()"
>Add</button>
<fieldset>
<legend>
Entries sorted by
<select
ng-model="order"
ng-options="property for property in [ 'label', 'value']">
</select>
</legend>
<div ng-repeat="entry in getEntries()">
{{entry.label}} = "{{entry.value}}"
</div>
</fieldset>
</form>

angularjs data binding with dynamically created elements

I have this code in my application:
$scope.appendBets = function()
{
$.each($scope.phaseBets, function(i, bet)
{
var betElement = angular.element('<div ng-model="phaseBets[i]">Bet id: {{phaseBets[i].id}}</div>');
angular.element(document.querySelector('#betsHolder')).append(betElement);
$compile(betElement)($scope);
});
}
the $scope.phaseBets is loaded from $http.get.
Now the problem is that the {{phaseBets[i].id}} content not seen on the html page, I am getting this Bet id:.
I have seen this OS but it's not working for me, maybe because of the array.
Is there anything wrong with my code?
Note
The thing is that by the I'd I will create an element (different for each id) so ng-repeat not helping that mutch.
Here's how I'd do it using ng-repeat and ng-include:
$scope.items = [
{id: 1, title: 'foo', data: {body: 'baz1'}},
{id: 2, title: 'bar', data: {body: 'baz2'}}
];
<div ng-repeat="item in items">
<h2>{{item.title}}</h2>
<div ng-include src="getTemplateById(item.id)"></div>
</div>
Where the templates are defined inline like this:
<script type="text/ng-template" id="template-1.html">
Content of template-1.html
<div>{{item.data.body}}</div>
</script>
<script type="text/ng-template" id="template-2.html">
<p>Content of template-2.html</p>
</script>
and getTemplateById is:
$scope.getTemplateById = function(id) {
return 'template-' + id + '.html';
};
You can see it in action here.
I think you got it from wrong side, in angularjs controllers/data drives the view, here you are creating elements (and even worse adding them to page) in loop (expensive DOM operations)
I'd replace your code with following
<div id="betsHolder">
<div ng-repeat="bet in phaseBets track by bet.id">Bet id: {{bet.id}}</div>
</div>
as soon as you assign your array/object to $scope.phaseBets the DOM will be created
but using ng-repeat is better option,
angular.forEach($scope.phaseBets, function(bet, i)
{
var betElement = angular.element('<div ng-model="bet">Bet id: {{bet.id}}</div>');
angular.element(document.querySelector('#betsHolder')).append(betElement);
$compile(betElement)($scope);
});

Categories

Resources