angularjs directive link function not binding data from Controller - angularjs

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>

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>

Disable and empty model value and then restore it

I have working code:
<input type="checkbox" ng-model="disabled" >
<input type="text" ng-model="text" ng-disabled="disabled">
When disabled == true the input is disabled. I need to hide actual model value (set empty) as well. And after uncheck the checkbox actual model value should appears. How to do it without changing model value?
I would do something like this:
<input type="checkbox" ng-model="disabled" ng-change="change()">
<input type="text" ng-model="text" ng-disabled="disabled">
...
$scope.change = function() {
if ($scope.disabled) {
$scope.textBackup = $scope.text;
$scope.text = '';
} else {
$scope.text = $scope.textBackup;
}
};
Note: I posted before reading #AbdulMateenMohammed comment... My answer is an implementation of his suggestion...
This is not the best option but if you want to deal only with the view:
<input type="checkbox" ng-model="disabled">
<div ng-show="disabled">
<input type="text" ng-init="text2 = ''" ng-model="text2" ng-disabled="true">
</div>
<div ng-hide="disabled">
<input type="text" ng-model="text">
</div>
Again, this is not the best option!!! MarcosS option is the recommended.
#Makarov Sergey, I think even when you have a complex view or data source the basic idea is to use a temporary variable because you need to have two values b/w which you swap around.
angular
.module('demo', [])
.controller('DefaultController', DefaultController);
function DefaultController() {
var originalText = '';
var vm = this;
vm.text = 'Hello, World!';
vm.onValueChanged = onValueChanged;
function onValueChanged(text) {
if (vm.disabled) {
originalText = text;
vm.text = '';
} else {
vm.text = originalText;
}
}
}
span {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div>
<em>I </em><span>♥ </span><em>AngularJS</em>
</div>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<input type="checkbox" ng-model="ctrl.disabled" ng-change="ctrl.onValueChanged(ctrl.text)"/>
<input type="text" ng-model="ctrl.text" ng-disabled="ctrl.disabled"/>
</div>
</div>
Tip: For the objects that aren't displayed in the view but present in the controller shouldn't be assigned on the scope because using scope would add some JavaScript events to do the data-binding which is unnecessary overhead such as the dirty check event for the two-way data-binding so that's why in the sample code snippet I used var originalText = ''; instead of vm.originalText = '';
Found the solution that works for me atm
function directive() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
let oldViewValue = '';
scope.$watch(() => attrs.hideValue, (newV) => {
if (newV == 'true') {
oldViewValue = ngModelCtrl.$viewValue;
ngModelCtrl.$viewValue = '';
} else {
ngModelCtrl.$viewValue = oldViewValue;
}
ngModelCtrl.$render();
});
}
}
}
And use:
<input type="checkbox" ng-model="disabled" >
<input type="text" ng-model="text" ng-disabled="disabled" hide-value="disabled">

How to use one controller for multiple inputs with the same logic?

I have the following scenario, one form with multiple inputs and i need to calculate every input the same way but return the values to different fields
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorFOR" placeholder="" ng-change="findModifier()" ng-model="atrb.for">
<p>{{mod.for}}</p>
</div>
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorDES" placeholder="" ng-change="findModifier()" ng-model="atrb.des">
<p>{{mod.des}}</p>
</div>
the controller:
app.controller('atributosCtrl', function($scope){
findModifier = function() {
if ($scope.atrb > 1 && $scope.atrb <10)
{
if ($scope.atrb % 2 == 0)
{
$scope.mod = (($scope.atrb / 2) - 5);
}
}
};
$scope.$watch('atrb', findModifier); });
I want to change the value of mod.for or mod.des without having to write a controller for each input. but i don't how to pass the name of the model from the input that i'm modifying
I don't know what exactly you want, but I made some changes on your code to make it working. Please tell me what you want in comments here and I'll can help you.
Your HTML modified:
<body ng-controller="atributosCtrl">
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorFOR" placeholder="" ng-change="findModifier('for')" ng-model="atrb.for">
<p>{{mod.for}}</p>
</div>
<div class="row">
<input type="number" min="1" class="form-control" id="InputValorDES" placeholder="" ng-change="findModifier('des')" ng-model="atrb.des">
<p>{{mod.des}}</p>
</div>
</body>
Your JS modified:
app.controller('atributosCtrl', function($scope){
$scope.atrb = {
for: null,
des: null
};
$scope.mod = {
for: null,
des: null
};
$scope.findModifier = function(type) {
$scope.mod[type] = null;
if ($scope.atrb[type] > 1 && $scope.atrb[type] <10)
{
if ($scope.atrb[type] % 2 === 0)
{
$scope.mod[type] = (($scope.atrb[type] / 2) - 5);
}
}
}
});
Plunker:
https://plnkr.co/edit/aCNJQyfYXZ5vU1rc381S
I think you are expecting somethin like this. You can write a custom directive with a link function like below
(function () {
"use strict";
angular.module("app").directive("notifypropertychanged", notifypropertychanged);
function notifypropertychanged() {
var directive = {
require: "ngModel",
link: function ($scope, element, attrs, ngModel) {
$scope.$watch(attrs["notifypropertychanged"], function (newVal, oldVal) {
var initialValue = attrs["oldvalue"];
});
}
};
return directive;
}
})();
Apply this directive on your input
<input type="number" min="1" class="form-control" notifypropertychanged="atrb.des" oldvalue=" {{::atrb.des}} " id="InputValorDES" placeholder="" ng-model="atrb.des">
whenever the value change it will hit on custom watch
I hope this helps

AngularJS validation directive

We have a validation directive that we use to validate the controls on Blur and viewContentLoaded event.
We persist the form values in local storage to remember the details when we navigate away from the form and come back
The problem is that, even though it remembers the details it doesn't re-validate the Firstname and Lastname when we load that view again.
Following is our original code:
<div>
<form name="form" class="form-horizontal">
<label class="control-label">First name</label>
<div class="controls">
<input id="firstName" name="FirstName" ng-model="order.FirstName" type="text" validate="alphabeticOnly" maxLength="30" required/>
<span class="help-block" ng-show="form.FirstName.$dirty && form.FirstName.$invalid">Please enter valid Firstname</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Last name</label>
<div class="controls">
<input id="lastName" name="LastName" ng-model="order.LastName" type="text" validate="alphabeticOnly" maxLength="30" required/>
<span class="help-block" ng-show="form.LastName.$dirty && form.LastName.$invalid">Please enter valid Lastname</span>
</div>
</div>
</form>
Confirm
The next function just saves the order and redirects to another page.
We have a $watch on $scope.order that saves the data in local storage to remember.
Directive:
.directive('validate', ['validationService', function(validationService) {
function validate(elm) {
var fn = elm.attr("validate");
var value = elm.val();
return validationService[fn](value);
}
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
function triggerValidations(){
checkValidity();
ctrl.$parsers.unshift(function(viewValue) {
checkValidity();
return viewValue;
});
ctrl.$formatters.unshift(function(viewValue) {
checkValidity();
return viewValue;
});
}
function checkValidity(){
if (elm.val().length > 0)
{
var isValid = validate(elm);
ctrl.$setValidity('validate', isValid);
console.log(" here i am - CheckValidity", attrs.id, elm.val(), isValid );
//scope.$apply();
}
}
$rootScope.$on('$viewContentLoaded', triggerValidations);
elm.bind('blur', function(event) {
scope.$apply(function() {
ctrl.$setValidity('validate', validate(elm));
});
});
}
};
}])
If we add $scope.apply it gives an error "$digest already in progress"
At the end, we want to validate the form when someone lands onto the page.

How to get data from ngform in angularjs?

HTML:
<div ng-controller="TestController" >
<form name="test_form" ng-submit="submit()">
<input type="text" name="some_name" ng-model="form_data.some_name" required>
<ng-form ng-repeat="key in keys" name="keyForm">
<input type="text" name="data_input" ng-model="form_data.data_input" required>
</ng-form>
<a ng-click="addKey()">NEW KEY</a>
</form>
</div>
JS:
app.controller('TestController', function TestController($scope){
$scope.keys = [];
$scope.addKey = function() {
$scope.keys.push({});
}
$scope.submit = function() {
console.log($scope);
}
});
In submit function I can get the value of "some_name" input:
$scope.submit = function() {
console.log($scope.form_data.some_name);
}
But I can't get the values of "data_input" inputs (they are inside ngform tag). How to do that?
(ngform tag is using for ability to validate each new added input separately)
Each input inside the ng-repeat needs its own unique ng-model property -- they all can't use form_data.data_input. Here is one way to solve your problem:
<ng-form ng-repeat="key in keys" name="keyForm">
<input type="text" name="data_input" ng-model="key.data" required>
</ng-form>
$scope.addKey = function () {
$scope.keys.push({ data: ''});
}
Fiddle.
See also https://stackoverflow.com/a/14379763/215945

Resources