how to $watch multiple variables in angularjs? - angularjs

Here is a angularjs directive to show multiple form validation errors.
directive code -
app.directive('validationErrors', function($compile) {
return({
link : function($scope, el, attr) {
$scope.fld = attr.id;
$scope.individualValidationErrors = [];
var model = ((attr.ngModel).split('.'))[0];
$scope.validationErrors = {};
$scope.validationErrors[model] = {};
$scope.validationErrors[model][$scope.fld] = "";
var html = $compile(
'<div id="error-{{fld}}" style="color:red;">'+
'<ul>' +
'<li ng-repeat="error in individualValidationErrors[fld]">'+
'{{error}}' +
'</li>' +
'</ul>' +
'</div>'
)($scope);
$('input[id="'+$scope.fld+'"]').after(html);
$scope.$watch('validationErrors',
function(newV) {
$scope.fld = attr.id;
$scope.individualValidationErrors = [];
console.log(newV);
console.log($scope.validationErrors);
if ($scope.fld != undefined) {
$scope.individualValidationErrors[$scope.fld] = $scope.validationErrors[model][$scope.fld];
//console.log($scope.individualValidationErrors);
}
},
true
);
}
});
});
Html code -
<form ng-submit="registration()">
<input validation-errors="validationErrors" maxlength="50" type="text" id="first_name" ng-model="User.first_name">
<input validation-errors="validationErrors" maxlength="50" type="text" id="last_name" ng-model="User.last_name">
<input validation-errors="validationErrors" maxlength="50" type="text" id="email" ng-model="User.email">
<input validation-errors="validationErrors" type="password" id="password" ng-model="User.password">
<input class="btn btn-info" type="submit" id="registration-sbmit" value="Submit">
</form>
the error of the last field of form overwrites all fields in the form and so it is not showing individual error for field..
$scope.validationErrors variable is set in controller which i want to $watch in directive.

I think your primary issue here is that you are overwriting $scope.validationErrors in your link function. The link function will be run for every directive on the page. They are also sharing the same $scope object. So you need to conditionally create $scope.validationErrors and conditionally add top-level keys to it:
if(!$scope.validationErrors)
$scope.validationErrors = {};
if(!$scope.validationErrors[model])
$scope.validationErrors[model] = {};
$scope.validationErrors[model][$scope.fld] = "";
That should at least clear up your issue where you are only getting the last item in $scope.validationErrors.

You can do it like this:
$scope.$watch('[myproperty1,myproperty2,myproperty3]',function(nv,ov){
//do some stuff
});

Related

How to include input fields from custom directives in angular forms?

I need to create a simple form with validations like this - https://jsfiddle.net/rohansh20/k7omkz7p/2/
<div ng-app="module1" ng-controller="ctrl1 as vm">
<form novalidate name="vm.form1" class="css-form">
<label>Name:
<input type="text" name="Name" ng-model="vm.user.name" required />
</label>
<br />
<label>E-mail:
<input type="email" name="Email" ng-model="vm.user.email" required />
</label>
<br />
<input type="submit" ng-click="vm.save(vm.form1, vm.user)" value="Save" />
</form>
<div>
{{vm.result}}
</div>
<pre>form = {{vm.form1 | json}}</pre>
</div>
angular.module('module1', []);
angular.module('module1').controller('ctrl1', function() {
this.save = function(form, user) {
if(form.$invalid) {
this.result = 'Please correct the data entered';
return;
}
this.result = 'User ' + user.name + ' with email ' + user.email + ' saved successfully';
};
});
But I need to dynamically generate the input fields. So I have made a directive that transforms into any type of input field - https://jsfiddle.net/rohansh20/hdxj0np6/3/
<div ng-app="module1" ng-controller="ctrl1 as vm">
<form novalidate name="vm.form1" class="css-form">
<custom-input name="Name" type="text" model="vm.user.name" required="true">
</custom-input>
<br />
<custom-input name="Email" type="email" model="vm.user.email" required="true">
</custom-input>
<br />
<input type="submit" ng-click="vm.save(vm.form1, vm.user)" value="Save" />
</form>
<div>
{{vm.result}}
</div>
<pre>form = {{vm.form1 | json}}</pre>
</div>
var app = angular.module('module1', []);
app.controller('ctrl1', function() {
this.save = function(form, user) {
if(form.$invalid) {
this.result = 'Please correct the data entered';
return;
}
this.result = 'User ' + user.name + ' with email ' + user.email + ' saved successfully';
};
});
app.directive('customInput', function($compile) {
return {
restrict: 'E',
link: function(scope, element, attributes) {
var labelElement = angular.element('<label/>'),
name = attributes.name,
type = attributes.type,
ngModelString = attributes.model,
required = attributes.required,
inputElement = angular.element('<input/>');
inputElement.attr('ng-model', ngModelString);
inputElement.attr('name', name);
inputElement.attr('type', type);
if (required) {
inputElement.attr('required', 'required');
}
labelElement.append(name + ': ');
labelElement.append(inputElement);
$compile(labelElement)(scope);
element.replaceWith(labelElement);
}
}
});
The fiddles are simplified versions of what I'm trying to make.
The problem is that these fields, even though compiled and rendered perfectly(which can be seen by inspecting the HTML), are not getting included as part of the parent form control. This can be seen in the displayed form control object in both the fiddles. Because of this, the form validity cannot be determined and both forms behave differently on submitting invalid input.
I need the form control in the second fiddle to have correct values for its properties and to have the child controls and their properties like in the first fiddle. Is this even possible using a custom directive? What do I need to change to make this work?
Note - The directive would involve complex operations to dynamically create HTML, so it has to be done in the link function of a directive. A directive template with multiple ngIfs would just not work.
In order not to lose bindings from your parent form, include it in your custom form directive
In your directive
(function () {
'use strict';
angular
.module('myApp')
.directive('customInput', customInput)
customInput.$inject = ['$compile'];
/* #ngInject */
function customInput ($compile) {
var directive = {
templateUrl: 'app/custom-input.html',
restrict: 'E',
transclude: true,
require: "?^form",
compile: compile,
controller:CustomInputController,
controllerAs:'vm',
scope:{
inputType:'=',
inputValue:'='
}
};
return directive;
function CustomInputController($scope){
var vm = this;
}
function compile(element, attributes){
return {
pre: preLink,
post: postLink
}
}
function preLink (scope,element,attrs, form, transclude){
}
function postLink (scope,element,attrs, form, transclude){
scope.currentForm = form;
$compile(element.contents())(scope);
}
}
})();
In your directive html template
<input type="inputType" ng-model="inputValue">
When you call your directive
<br-input type="text"
input-value="vm.user.email"
inputname="name"
input-required ="true">
</br-input>

angularjs directive link function not binding data from Controller

I have a directive that makes use of jquery events on the element parameter of the link function, this directive has an input that is binding to a value that is obtained from the main controller of the page, passed through nested directives in a isolated scope , but when changing the value in the input is not reflected in the original object from controller.
The object has the following structure:
Invoice 1:
- Product 1
- Product 2
Invoice 2:
- Product 3
- Product 4
When I change the amount of the invoice, the value is updated in the main controller, but when I change the amount of the product the change is not reflected.
This is my directive, what you should do is that when the user clicks on the value an input appears to be able to edit the value of the model:
eFieldTemplate.html
<div>
<div ng-if="IsMouseIn">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
<div ng-if="IsMouseOut" ng-click="OnMouseClick()">
{{value}}
</div>
<div ng-if="MouseClick">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
eFieldDirective.js
angular.module("appDirectives").directive("eField", function () {
return {
restrict: "E",
templateUrl: "eFieldTemplate.html",
scope: {
value: "="
},
controller: function ($scope) {
$scope.IsMouseOut = true;
$scope.IsMouseIn = false;
$scope.MouseClick = false;
$scope.OnMouseEnter = function () {
if (!$scope.MouseClick) {
$scope.IsMouseOut = false;
$scope.IsMouseIn = true;
$scope.MouseClick = false;
}
}
$scope.OnMouseLeave = function () {
if (!$scope.MouseClick) {
$scope.IsMouseOut = true;
$scope.IsMouseIn = false;
$scope.MouseClick = false;
}
}
$scope.OnMouseClick = function () {
$scope.IsMouseOut = false;
$scope.IsMouseIn = false;
$scope.MouseClick = true;
}
$scope.EndEdit = function () {
$scope.IsMouseOut = true;
$scope.IsMouseIn = false;
$scope.MouseClick = false;
}
},
link: function (scope, el, attrs) {
el.on("mouseenter", function () {
scope.OnMouseEnter();
scope.$apply();
});
el.on("mousemove", function () {
scope.OnMouseEnter();
scope.$apply();
});
el.on("mouseleave", function () {
scope.OnMouseLeave();
scope.$apply();
});
el.on("click", function () {
scope.OnMouseClick();
if (el[0].querySelector('input'))
el[0].querySelector('input').select();
scope.$apply();
});
}
};
});
Any Suggestions?
I give the example here: Plunker
UPDATED
I found a solution using ngIf, and is to reference a variable from the parent scope using $ parent.value. Eg.
<Input type="text" ng-model="$parent.value" class="form-control input-sm" />
Or also referring to another object eg.
<input type="text" ng-model="value">
<div ng-if="IsMouseIn">
<input type="text" ng-model="value">
</div>
Here is the reference link: what is the difference between ng-if and ng-show/ng-hide
using ng-if makes it create/destroy new html nodes and it seems to be unable to cope with that. change to ng-show and it will work. i also added a body mouse capture so it ends the edit.
<div>
<div ng-show="IsMouseIn">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
<div ng-show="IsMouseOut" ng-click="OnMouseClick()">
{{value}}
</div>
<div ng-show="MouseClick">
<input type="text" ng-model="value" class="form-control input-sm" />
</div>
view plunker
If you want to use ng-if not ng-show still, define $scope.values and $scope.config and use like this. To avoid the ng-if problem you should define an object.
<div>
<div ng-if="config.IsMouseIn">
<input type="text" ng-model="values.value" class="form-control input-sm" />
</div>
<div ng-if="config.IsMouseOut" ng-click="OnMouseClick()">
{{values.value}}
</div>
<div ng-if="config.MouseClick">
<input type="text" ng-model="values.value" class="form-control input-sm" />
</div>

AngularJS input not updating with ng-model

I have a form with three input fields:
<form name="myForm" novalidate ng-controller="MainController as vm">
<div>
<input type="text" ng-model="vm.id" name="idInput" required>
<input type="email" ng-model="vm.email" name="emailInput" required>
<input type="text" ng-model="vm.output" name="output">
</div>
</form>
vm.output is a variable defined in my controller that contains some strings plus vm.id and vm.email:
vm.output = 'myurl.com?id=' + vm.id + '&email=' + vm.email;
I want to generate an output URL based on the user input in the id and email fields. However, the output-field does not update when I enter some input into the two other fields. It just says myurl.com?id=undefined&email=undefined,
I can get it working if I use
ng-value="'myurl.com?id=' + vm.id + '&email=' + vm.email"
However, I'm using ng-clip which gets the content to copy by using ng-model so I need to use that.
Also, here's my controller:
angular
.module("app")
.controller("MainController",[MainController);
function MainController(){
var vm = this;
vm.output = 'myurl.com?id=' + vm.id + '&email=' + vm.email;
}
Any suggestions?
You could accomplish this a few different ways. One way is to set up ng-change events on each of the inputs you want to watch:
<div>
<input type="text" ng-model="vm.id" ng-change="updateOutput()" name="idInput" required />
<input type="email" ng-model="vm.email" ng-change="updateOutput()" name="emailInput" required />
<input type="text" ng-model="vm.output" name="output" />
</div>
Then, you have to build the update method on the controller scope:
app.controller = app.controller('MainController', function($scope) {
$scope.vm = {
output: '',
email: '',
id: ''
};
$scope.updateOutput = function() {
$scope.vm.output = 'myurl.com?id=' + $scope.vm.id + '&email=' + $scope.vm.email;
}
});
Here is a working plunker.
I would go with custom directive that would set model value properly:
app.directive('concatModel', function($parse) {
var pattern = function(data) {
return 'myurl.com?id=' + data.id + '&email=' + data.email;
};
return {
require: 'ngModel',
scope: {
data: '=concatModel'
},
link: function(scope, element, attrs, controller) {
scope.$watchCollection('data', function(newVal) {
controller.$setViewValue(pattern(newVal));
controller.$render();
});
}
};
});
and use it like this:
<div>
<input type="text" ng-model="vm.id" name="idInput" required="" />
<input type="email" ng-model="vm.email" name="emailInput" required="" />
<input type="text" concat-model="{id: vm.id, email: vm.email}" ng-model="vm.output" name="output" />
</div>
Demo: http://plnkr.co/edit/sFW16LLZK3TezNAvYk5F?p=info

How to dynamically change input value

I'm trying to show some editable results to the users, so I show them through an input field. This is a basic example of what I'm doing:
<div class="form-group">
<label>First number</label>
<input type="text" ng-model="first" ng-required="true" class="form-control">
</div>
<div class="form-group">
<label>Second number</label>
<input type="text" ng-model="second" ng-required="true" class="form-control">
</div>
<div class="form-group">
<label>The sum is: {{first + second }}</label>
<input type="text" ng-model="result" ng-required="true" class="form-control">
</div>
In the result's div, I used a label to test if the result is being obtained correctly, and it is. But if I edit the first or second values, the input's result doesn't update.
This is the controller used (yeah, the form is in a modal):
var ModalInstanceCtrl = function ($scope, $modalInstance) {
$scope.result = $scope.first + $scope.second;
$scope.confirm = function () {
$modalInstance.close(result);
};
$scope.cancelNewBet = function () {
$modalInstance.dismiss('cancel');
};
};
I thought the value was supposed to get automatically updated once I define how it is obtained. But clearly it misses something to change the result through script...
Thanks in advance.
What do you want to happen when the user edits the results input? Do you want the data binding to break (I assume not and ignore this possibility)? Do you want one of the inputs to adjust to the proper value?
If you only want to display the output, do this, in your controller:
$scope.result = function() { return $scope.first + $scope.second; }
And in your view:
{{ result() }}
But if you want the user to be able to edit the result and (let's say) have second be assigned (result - first), then you'd want something like this in your view (by the way, note the type="number"):
<input type="number" ng-change="adjustResult()" ng-model="first">
<input type="number" ng-change="adjustResult()" ng-model="second">
<input type="number" ng-change="adjustInput()" ng-model="result">
And in your controller:
$scope.adjustResult = function() {
$scope.result = $scope.first + $scope.second;
};
$scope.adjustResult(); // initialize result
$scope.adjustInput = function() {
$scope.second = $scope.result - $scope.first;
}

Angular-UI multiple datepickers insider form controller

I am creating a form with multiple angular-ui datepickers and some input data.
For the datepickers I have created a controller and a parent form controller like the sample given below. The form controller has the model which includes the datepicker dates.
JS:
var app = angular.module('app', ['ui.bootstrap']);
app.controller('dateCntrl', function($scope,$timeout){
$scope.open = function() {
$timeout(function() {
$scope.opened = true;
});
};
});
app.controller('formCntrl', function($scope, $http){
$scope.model = {name:'', startDate:'', endDate:''};
});
HTML:
<form ng-controller="formCntrl">
<input type="text" id="name" placeholder="Name" ng-model="model.name" />
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="model.startDate" id="startDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="model.endDate" id="endDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
</form>
Am I going the right way in having a separate controller for the datepicker. This will act as a common controller for all the date inputs
If yes, is it possible to have a generic way of binding the data in the datepicker controller back to the model dates(model.startDate,model.endDate in this case) in the parent controller.
Is there a alternative way to go about this.
Thanks and regards.
Should have read more about the scope inheritance
The parent scope values can be accessed using $parent
<form ng-controller="formCntrl">
<input type="text" id="name" placeholder="Name" ng-model="model.name" />
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="$parent.model.startDate" id="startDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
<div ng-controller="dateCntrl">
<input datepicker-popup="dd-MMMM-yyyy" ng-model="$parent.model.endDate" id="endDate" type="text" />
<button class="btn" ng-click="open()"><i class="icon-calendar"></i></button>
</div>
</form>
I took all the code from the verbose example here: http://angular-ui.github.io/bootstrap/#/datepicker & wrapped it into my own directive. This way I can just drop unlimited datepickers into my page, and specify the model for each one to bind to. I do not have to manage passing repetitive settings, or setting up unique variables for tracking the "open" status anymore, I just put 1 line of code:
<div my-date-picker my-date-picker-model="myDate1"></div>
<div my-date-picker my-date-picker-model="myDate2"></div>
<div my-date-picker my-date-picker-model="myDate3"></div>
The user can then toggle each date picker open/closed, and the values will be updated into myDate1, myDate2, & myDate3 appropriately. The open/closed status is now encapsulated within the directive, and out of mind.
To implement the directive, I copied the 'JS' tab's code into it's controller, and I copied the 'Markup' tab's code into it's template. At the end I added 1 bit of code to update the value to the parent scope:
$scope.$watch('dt', function(newVal, oldVal) {
$scope.myDatePickerModel = newVal;
});
At the start of the controller, I changed $scope.today to initialize the value from the parent scope, instead of using the system clock:
$scope.init = function() {
$scope.dt = $scope.hxDatePickerModel;
};
$scope.init();
The directive uses an isolate scope, and 2-way binding on the attribute which defines the parent scope's model:
scope: {
myDatePickerModel: '='
}
Here's the full code of the directive:
app.directive('myDatePicker', function() {
function link(scope, element, attrs) {
}
function controller($scope) {
$scope.init = function() {
$scope.dt = $scope.myDatePickerModel;
};
$scope.init();
$scope.clear = function () {
$scope.dt = null;
};
// Disable weekend selection
$scope.disabled = function(date, mode) {
return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
};
$scope.toggleMin = function() {
$scope.minDate = $scope.minDate ? null : new Date();
};
$scope.toggleMin();
$scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.opened = true;
};
$scope.dateOptions = {
formatYear: 'yy',
startingDay: 1
};
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
$scope.format = $scope.formats[0];
$scope.$watch('dt', function(newVal, oldVal) {
$scope.myDatePickerModel = newVal;
});
}
return {
restrict: 'A',
templateUrl: 'datepicker.html',
link: link,
controller: controller,
scope: {
myDatePickerModel: '='
}
}
});
And here is the full code of datepicker.html, the template for this directive:
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
other solution , declare the datepicker's open() method in $rootScope and it is available entire application .
Html :
<div ng-app="myApp">
<div ng-controller="DemoController">
<div>
<input type="text" name="salesEndDate" id = "salesEndDate" datepicker-popup="dd-MM-yyyy" ng-model="salesEndDate" datepicker-options="dateOptions"/>
<button id="salesEndDateCal" ng-click="datePickerOpen('salesEndDate')"><i class="icon-calendar"></i></button>
</div>
<div>
<input type="text" name="salesStartDate" id = "salesStartDate" datepicker-popup="dd-MM-yyyy" ng-model="salesStartDate" datepicker-options="dateOptions"/>
<button id="salesEndDateCal" ng-click="datePickerOpen('salesStartDate')"><i class="icon-calendar"></i></button>
</div>
</div>
</div>
Javascript :
var myApp = angular.module('myApp',['ui.bootstrap','ui.bootstrap.datepicker']);
function DemoController($scope,$timeout,$rootScope) {
$rootScope.datePickerOpen = function(id) {
$timeout(function() {
$("#"+id).focus();
});
};
}
jsfiddle link http://jsfiddle.net/angles_k/s7yZm/21/

Resources