I have some problems with angular binding and I'm not very experienced with it. I will post all questions here as they are related.
Here is some angularjs code snipped that triggers 10 digest() cycle reached. I found some similar posts and the cause is that a change is performed recursively in digest(), but I cannot find the cause in my example.
Here is code:
<work-timekeepings-day timekeepings="dailyTimekeepingCtrl.timekeepingList | timekeepingDay : dailyTimekeepingCtrl.selectedDay" day="dailyTimekeepingCtrl.selectedDay"></work-timekeepings-day>
Component:
var workTimekeepingsDay = TimekeepingsApp.component('workTimekeepingsDay', {
templateUrl : 'angular/components/work-timekeepings-day.html',
controllerAs: '$workTkDayCtrl',
bindings : {
timekeepings : '=',
day: '='
}
});
HTML template:
<div class="row lightgreen-row padding-5 border-rounded" ng-repeat-start="workTk in $workTkDayCtrl.timekeepings">
<div class="col-md-4"> <b> {{ workTk.user.firstName + ' ' + workTk.user.lastName }} </b> </div> </div> ...
Filter function:
var timekeepingDayFilter = TimekeepingsApp.filter('timekeepingDay', function() {
return function(timekeepings, filterDay) {
var result=[];
angular.forEach(timekeepings, function(timekeeping) {
var timekeepingDay = moment(timekeeping.workDay);
if (timekeepingDay.isSame(filterDay, 'day')) {
result.push(timekeeping);
}
});
return result;
}
});
If I apply filter function inside HTML template it doesn't trigger the error, but the two-way binding with 'day' variable seems to not work properly. If I update 'dailyTimekeepingCtrl.selectedDay' in another component, bound in the same way, the change is not reflected in workTimekeepingsDay component.
Here is the filter applied in component template:
<work-timekeepings-day timekeepings="dailyTimekeepingCtrl.timekeepingList" day="dailyTimekeepingCtrl.selectedDay"></work-timekeepings-day>
<div class="row lightgreen-row padding-5 border-rounded" ng-repeat-start="workTk in $workTkDayCtrl.timekeepings | timekeepingDay : day">
<div class="col-md-4"> <b> {{ workTk.user.firstName + ' ' + workTk.user.lastName }} </b> </div> </div> ..
Q1: Why is the 'digest() aborted' error occur if I am not updating the base array? How can I pass directly the filtered array to component in timekeepings variable?
Q2: Why is day variable not updated in component if dailyTimekeepingCtrl.selectedDay is updated?
I solved this by using lodash memoize function to use result from cash. Although I'd have preferred to not use an external library, but to change the algorithm, I am still happy with this.
Related
I am updating and modifying a project using Angular JS 1.2.25.
I have my controller where I have a value called vm.stageValue which is then called in template with an ng-if, so when the vm.stageValue increments it shows different containers. But whhen I define a value on the vm object that I want to interpolate on the template, eg a string that will be used and will not change on the template, I cannot seem to get it display.
This has makes me think I have not set up my controller correctly using the vm method.
It seems weird that I can use the ng-if and call function from the controller using ng-click on the template but I cannot interpolate a string or send it to another child component
Code is below, thank you in advance. Any help would be hugely appreciated
Controller
angular
.module('formModule')
.controller('NewBusinessFormCtrl', [
function() {
let vm = this;
// Methods used in controller
vm.methods = {
incrementStageValue: incrementStageValue,
decrementStageValue: decrementStageValue,
canIncrement: canIncrement,
canDecrement: canDecrement
};
//Initial stage values
vm.stageValue = 1;
vm.maxStageValue = 7;
// This is the string that I want to interpolate below
vm.contactFormCategory = 'New Business';
}
]);
Template of Controller
<div class="new_busines_cf" ng-controller="NewBusinessFormCtrl as vm">
<div class="form_wrapper">
<div ng-if="vm.stageValue == 1">
<input-text
class="form_input"
ng-model="ngModel"
input-text-label="This is the label">
</input-text>
// I want to send the vm.contactFormCategory into the component
// Value is sending but the component display 'vm.contactFormCategory'
// Not the value set in the controller
<form-headline
form-headline-sup-title="vm.contactFormCategory"
form-headline-text="This is a form headline text">
</form-headline>
</div>
// Trying to interpolate value here into template, but nothing display
{{vm.contactFormCategory}}
<div ng-if="vm.stageValue == 2">
<input-text
class="form_input"
ng-model="ngModel"
input-text-label="This is the label of stage 2">
</input-text>
<form-headline
form-headline-sup-title="vm.contactFormCategory"
form-headline-text="This is a form headline text">
</form-headline>
</div>
<button ng-click="vm.methods.incrementStageValue()">Increment Value</button>
<button ng-click="vm.methods.decrementStageValue()">decrement Value</button>
</div>
</div>
** Form Headline **
angular
.module('formModule')
.directive('formHeadline', function() {
return {
restrict: 'E',
templateUrl: '/partials/form/form-headline.component.html',
scope: {
formHeadlineText: '#',
formHeadlineSupTitle: '#'
},
link: function () {
}
};
});
Change your ng-if to
<div ng-if="vm.stageValue === '2'">
I am using Angular JS 1.5.6 components to build dynamically a form. The hierarchy is the following : index.html calls component my-view which calls component my-form which calls unitary components like inputs and button.
The issue is that the data binding is not working because any modification in input components is not taken into account into my-view component.
Besides I have a weird behavior, each time I update input value, a call is made to view component function.
I have plunkered this, the submit button triggers console.log (so need to open firebug to see it in action).
Here is my index.html
<body ng-app="MyApp">
<my-view></my-view>
</body>
Here is myView.html
<div class="container">
<h2>With component myform</h2>
<my-form form-elements="
[{type:'input', label:'First name', placeholder:'Enter first name',model:$ctrl.userData.firstName, id:'firstName'},
{type:'input', label:'Last name', placeholder:'Enter last name',model:$ctrl.userData.lastName, id:'lastName'},
{type:'button', label:'Submit', click:$ctrl.click()}]"></my-form>
</div>
<div class="container">
<h2>Check data binding</h2>
<label>{{$ctrl.userData.firstName}}</label>
<label>{{$ctrl.userData.lastName}}</label>
</div>
Here is myView.js
(function() {
'use strict';
angular.module('MyApp').component('myView', {
templateUrl: 'myView.html',
controller: MyViewController,
bindings: {
viewFormElements: '<'
}
});
function MyViewController() {
this.userData = {
firstName: 'François',
lastName: 'Xavier'
};
function click() {
console.log("Hello " + this.userData.firstName + " " + this.userData.lastName);
}
this.click = click;
}
})();
I manage to solve my issue with 2 way binding and by putting form-element in an object instead of putting it directly in the view ($ctrl.formElements). It is on plunker.
myView.html
<div class="container">
<h2>With component myform</h2>
<my-form form-elements=$ctrl.formElements></my-form>
</div>
<div class="container">
<h2>Check data binding</h2>
<label>{{$ctrl.formElements}}</label><br />
</div>'
I'm new to angular, trying to bind an an element's content into the controller's Scope to be able to use it within another function:
here is the scenario am working around:
I want the content of the <span> element {{y.facetName}} in
<span ng-model="columnFacetname">{{y.facetName}}</span>
to be sent to the controller an be put in the object $scope.columnFacetname in the controller
Here is a snippet of what I'm working on:
<div ng-repeat="y in x.facetArr|limitTo: limit track by $index ">
<div class="list_items panel-body ">
<button class="ButtonforAccordion" ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)">
<span>{{$index+1}}</span>
<span ng-model="columnFacetname">{{y.facetName}}</span>
<span>{{y.facetValue}}</span>
</button>
</div>
</div>
angular.module('mainModule').controller('MainCtrl', function($scope, $http) {
$scope.columnFacetname = "";
$scope.ListClicktnColumnFilter = "";
$scope.ListClicktnColumnFilterfunc = function() {
$scope.ListClicktnColumnFilter = "\":\'" + $scope.columnFacetname + "\'";
};
}
the problem is that the $scope.ListClicktnColumnFilter doesn't show the $scope.columnFacetname within it, meaning that the $scope.columnFacetname is not well-binded.
In your ng-click instead of calling two different function
ng-click="ListClicktnColumnFilterfunc(); onServerSideButtonItemsRequested(ListClicktnColumnFilter, myOrderBy)"
you can declare like this
ng-click="columnFacetname = y.facetName; onServerSideButtonItemsRequested(columnFacetname , myOrderBy)"
You are trying to pass that model to another function by assigning it to ListClicktnColumnFilter in your controller
By doing in this way, you can achieve the same thing.
I have done one plunker with sample array,
http://embed.plnkr.co/YIwRLWXEOeK8NmYmT6VK/preview
Hope this helps!
In one case I have a problem with running a function on the Controller from the template. The value becomes a string containing the function signature, not the value that should be returned from the function.
When I use {{ getSomeObject(d) }} in my template markup it works fine, and it prints the object values, meaning that the function got called on the Controller.
I have tried with and without the {{ }}.
Pseudo code:
<div class"xyz" data-lav-fact="getSomeObject(d)"> <!-- Does not work here -->
{{ getSomeObject(d) }} <!-- Works here -->
</div>
And of course the function is added to the scope in the Controller:
$scope.getSomeObject = function(data) {
return { key: "test" };
};
This works in other parts of the application and I don't know what wrong in this case.
Does anyone know what typically can be wrong here?
Since you are trying to set an attribute with a $scope function, you'll need to {{ interpolate }} and use ngAttr attribute bindings. Here is a simple example that shows this in action. Examine the difference between the elements logged out. As you dig, you'll see your { key: 'test' } value being set
<div id="without" data-lav-fact="getSomeObject()">without</div>
<div id="with" ng-attr-data-lav-fact="{{ getSomeObject() }}">with</div>
app.controller('ctrl', ['$scope', function($scope) {
$scope.getSomeObject = function() {
return { key: 'test' };
}
var w = angular.element(document.getElementById('with'));
var wo = angular.element(document.getElementById('without'));
console.log(w[0].attributes); // has value
console.log(wo[0].attributes); // does not have value
}]);
JSFiddle Link
I have a bit of a strange scenario that is a little different to the other childscope and two way binding issues I have seen on Stackoverflow.
I have a field generation directive that receives a configuration object and some data and dynamically creates the correct type of field on screen and populates the data.
directive.js
.directive('myField', function () {
var stringTemplate = "scripts/directives/templates/my-string.tpl.html";
var textTemplate = "scripts/directives/templates/my-text.tpl.html";
var selectTemplate = "scripts/directives/templates/my-select.tpl.html";
var linker = function ($scope, elem, attrs) {
// Function to dynamically select the correct template
$scope.getTemplateUrl = function () {
var template = '';
if ($scope.options) {
if ($scope.options.optionList) {
template = selectTemplate;
} else {
switch ($scope.options.type) {
case 'String':
template = stringTemplate;
break;
case 'Text':
template = textTemplate;
break;
}
}
return template;
}
};
return {
restrict: 'E',
replace: true,
scope: {
options: '=',
data: '=',
fieldName: '#',
fieldWidth: '#',
labelWidth: '#',
},
link: linker,
template: '<ng-include src="getTemplateUrl()"/>'
}
});
I then have the corresponding template... I'm showing just the string template in this case.
my-string.tpl.html
<div class="form-group col-md-12">
<label for="{{fieldName}}" class="{{labelWidth}}">
{{options.label}}
</label>
<div class="{{fieldWidth}}">
<input type="text" class="form-control input-sm" id="{{fieldName}}" placeholder="{{options.watermark}}" ng-model="data" tooltip="{{options.tipText}}" ng-disabled="options.editable === false">
</div>
</div>
An example of how this might then be used would be
controller.js
$scope.person.firstName = "John";
$scope.person.lastName = "Doe";
$scope.options.person.firstName.type = "String";
index.html
<div class="row">
<my-field options="options.person.firstName" data="person.firstName" field-name="firstName" label-width="small" field-width="medium"></my-field>
The problem is the usual one, my-field directive has an isolated scope with a "data" property that is two-way bound to the controller. Because I am then using ng-include to dynamically load the correct template I am creating a further child scope that due to prototypical inheritance still populates correctly as it doesn't have its own data property so reaches to the parent. However when I modify the field, a shadow property is created on my child scope called data that doesn't propagate upwards the way that two way binding should.
I hope you are still with me
controller > my-field
ng-include causes the following scopes to exist
controller > my-field > ng-include
From reading around I understand that what I need to do to rectify this is pass an object rather than a primitive, however as there is effectively an intermediate layer between my controller and my final directive this is not straightforward.
I thought about changing the isolate scope in my-field to look like this
scope: {
....
data: {value: '=data'}
....
}
and then updating the template to refer to the object
my-string.tpl.html
<div class="form-group col-md-12">
<label for="{{fieldName}}" class="{{labelWidth}}">
{{options.label}}
</label>
<div class="{{fieldWidth}}">
<input type="text" class="form-control input-sm" id="{{fieldName}}" placeholder="{{options.watermark}}" **ng-model="data.value"** tooltip="{{options.tipText}}" ng-disabled="options.editable === false">
</div>
</div>
but this kills angular.
I have successfully got it to work by reaching back to the controller scope for binding by using
ng-model="$parent.$parent.data"
but I am not really happy with this as a solution as A it is ugly and B it involves knowing the depth of scope you are at which could vary.
Really stumped with this. Any help would be appreciated.