AngularJS custom directive, cant access ngModel on DOM load - angularjs

I'm using Laravel5 and creating a custom directive for angular novalidation forms. Now I can't access ngmodel placed data. My goal is to first show person's name, and then save his name, but name is not showing at DOM load...
Main problem I found is on $watch, but I don't know what to change in it.
Here's my codepen
AngularJS code:
(function() {
'use strict';
angular.module('app', [])
.controller('ctrl', mainController)
.directive('myInput', myInput);
function mainController() {
var vm = this;
vm.data = {
email: 'test#tesst.com',
name: 'Nickelson'
};
}
function myInput() {
function tempFunc(element, attrs) {
var templateWithVars = '<input type="inputType" ng-model="fieldModelName" name="fieldModelName" ng-required="required"/>';
var template = templateWithVars
.replace("inputType", attrs.type)
.replace("exampleName", attrs.example)
.replace(/fieldModelName/g, getFieldModelName(attrs));
return template;
}
function getFieldModelName(attrs) {
var objectAndField = attrs.ngModel;
var names = objectAndField.split('.');
var fieldModelName = names.pop();
return fieldModelName;
}
return {
replace: false,
scope: {},
template: tempFunc,
require: ['^form', "ngModel"],
link: function (scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1]; // This part is null
var model = getFieldModelName(attrs);
scope.$watch(model, function (newVal, oldVal) {
ngModel.$setViewValue(scope[model]);
});
}
}
}
})();
HTML:
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-controller="ctrl as vm">
<br />
email: {{ vm.data.email }}
<br />
name: {{ vm.data.name }} // Nothing showing in here!!!
<br />
<h2>Edit</h2>
<form novalidation name="vm.form">
<my-input ng-model="vm.data.name"
type="text"
example="vm.data.name"> // My Input by default is empty
</my-input>
<br />
<br />
<input type="submit" ng-show="vm.form.$valid" value="Save Name"/>
</form>
</body>
</html>

You're assigning the ng-model to your directive in a funky way. It's making it such that it does work, but the way it initializes actually overwrites the original ng-model with the blank input.
If you change your $watch function to :
if (scope[model] !== undefined) ngModel.$setViewValue(scope[model]);
It'll only update the ngModel if it's not undefined, which it is initially.
Here's an updated codepen.
Note that you'll actually have to make changes to the input before ngModel is defined (even if you just type a space and backspace, it'll be fine).

I fix my problem. I don't exacly needed place ngmodel from require: ["ngModel"], but just add it in scope (scope: { ngModel: '=' }), and after that I add it strate to tempFunc, like:
var templateWithVars = 'input type="inputType" ng-model="ngModel" name="fieldModelName" ng-required="required"/>';
And It kind of fix it...

Related

Creating custom field validation in angular

I have the following code/plunkr and for some reason the form.name.$invalid is always true (see the span just after the input box). Angular's documentation doesn't say much about how the $invalid value is set. I have a hunch it has something to do with in the javascript I have ctrl.$error.taken = true/false and just having an object on the $error object set's $invalid to true. Can someone who knows how angular works behind the scenes help me out?
I want the "Name must be alpha-numeric" error to display if the name doesn't match the regex, but I want the "Name is already taken" error to display if the name is one from the list. I also what the text "error" to display if the field is one of those two errors. All of these things are working except the word "error" is always displayed. I'm trying to follow angular's standards of using $invalid.
View:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-forms-async-validation-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="form-example1">
<form name="form" class="css-form" novalidate>
<div>
Username:
<input type="text" ng-model="name" name="name" username />
<span ng-show="form.name.$invalid">error</span>
<br />{{name}}<br />
<span ng-show="form.name.$error.badContent">Name must be alpha-numeric</span>
<span ng-show="form.name.$error.taken">Name is already taken!</span>
</div>
</form>
</body>
</html>
Script:
(function(angular) {
'use strict';
var app = angular.module('form-example1', []);
var NAME_REGEXP = /^([a-zA-Z0-9])*$/;
app.directive('username', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var names = ['Jim', 'John', 'Jill', 'Jackie'];
ctrl.$validators.username = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
return true; // consider empty model valid
}
ctrl.$error.taken = names.indexOf(modelValue) > -1;
ctrl.$error.badContent = !NAME_REGEXP.test(modelValue);
return !ctrl.$error.taken && !ctrl.$error.badContent;
};
}
};
});
})(window.angular);
Plunkr:
https://plnkr.co/edit/LBRR14PpjAQigafOVTgQ?p=preview
Use separate directives one to validate alphanumeric, and one for the username already taken issue.
$ctrl.validators.alphaNumeric exposes $error.alphaNumeric on the FormController. You don't have to manipulate the value on $error.xxx, it is set automatically based on the validator's return value.
Please check the plunkr.
Also refer to https://code.angularjs.org/1.5.8/docs/api/ng/type/ngModel.NgModelController
Though I have not verified this, but I believe you now should have ng-invalid-alpha-numeric error class, owing to the fact that now form.$error.alphaNumeric is set.
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.model = {};
});
app.directive('validateAlphaNumeric', function () {
var REGEX = /^([a-zA-Z0-9])*$/;
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
ctrl.$validators.alphaNumeric = function (modelValue, viewValue) {
if (REGEX.test(viewValue)) {
return true
}
return false;
};
}
};
});
app.directive('validateUserNotTaken', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
var names = ['Jim', 'John', 'Jill', 'Jackie'];
ctrl.$validators.userNotTaken = function (modelValue, viewValue) {
if (names.indexOf(modelValue) == -1) {
return true
}
return false;
};
}
};
});
HTML
<body ng-controller="MainCtrl">
<form name="myForm">
<input type="text" validate-alpha-numeric validate-user-not-taken ng-model="model.value1">
<p ng-if="myForm.$error.alphaNumeric">Should be alphanumeric</p>
<p ng-if="myForm.$error.userNotTaken">User Exists!</p>
<p ng-if="myForm.$error.alphaNumeric || myForm.$error.userNotTaken">Error</p>
</form>
</body>
Working plunkr: https://plnkr.co/edit/MZ7DMW?p=preview

Angular $watch newValue is undefined after the first click on a radio button

I have a radio button inside a directive and I have a number of these directive on the page. All of these radio buttons have the same name. When I click on any of them a newValue in a $watch is undefined. Also ng-change event fires only ones (I guess for the same reason). My question is how can I know the current status of the radio button inside a directive on each click (that I will pass to the parent directive)?
var app = angular.module("app", []);
app.directive("choiceRb", function() {
return {
template: '<input type="radio" id="{{elid}}" name="selector" ng-change="onChange()" ng-model="checked" >',
scope: {
elid: "#"
},
link: function ($scope, $element, attr, cntr) {
$scope.checked = $element[0].querySelector("input[type=radio]").checked;
$scope.$watch('checked', function (newval, oldval) {
console.log(newval);
}, true);
$scope.onChange = function () {
console.log("onChange");
}
}
};
});
app.controller("myC", ["$scope", function ($scope) {
$scope.model = {};
$scope.model.elid = "1";
$scope.model.elid1 = "2";
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<div ng-app="app" ng-controller="myC">
<div style="width:300px; height:300px; border:1px solid red;position:relative;">
<choice-rb elid="{{model.elid}}"></choice-rb>
<choice-rb elid="{{model.elid}}"></choice-rb>
</div>
</div>
It doesn't look like you have any value attribute on your radio input element. Most likely you would want to be passing a value as an attribute on the choice-rb elements in your parent. So you would need to make some changes to the directive and the template which uses it.
add the elval attribute to choiceRb directive and ng-value attribute to the input type="radio" in its template.
app.directive("choiceRb", function() {
return {
template: '<input type="radio" id="{{elid}}" ng-value="{{elval}}" name="selector" ng-change="onChange()" ng-model="elmodel" >',
scope: {
elid: "#",
elval: "#",
elmodel: "="
},
...
add the elval attribute to the choice-rb elements in main template
<div ng-app="app" ng-controller="myC">
<div style="width:300px; height:300px; border:1px solid red;position:relative;">
<choice-rb elid="selector-{{model.elid}}" elval="{{model.elid}}" elmodel="model.value"></choice-rb>
<choice-rb elid="selector-{{model.elid1}}" elval="{{model.elid1}}" elmodel="model.value"></choice-rb>
</div>
</div>
Notice that I have also changed the elid attributes, prefixing with selector-, this is because input id's must start with a letter and the model.elid and model.elid1 values you have defined are both numbers (which are perfectly valid for the input's value attribute.)
Edit: Added the elmodel attribute to use for shared ng-model.

How to access ng-model value in directive?

I created a directive for google map auto-complete. everything is working fine, but the problem is when I need to access the value of input and re-set it. it doesn't work. Here is code:
<div controller='mainCtr'>
<span click='reset(destination)'>Reset</span>
<div class='floatleft' style='width:30%;margin-right:40px;'>
<smart-Googlemaps locationgoogle='destination.From'></smart-Googlemaps>
<label>From</label>
</div>
</div>
In the directive:
angular.module('ecom').directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '='
},
templateUrl: 'components/directives/autocomplete/googlemap-search.html',
link: function($scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($(elm).find("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
// $scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
// console.log(place);
$scope.locationgoogle = {};
$scope.locationgoogle.formatted_address = place.formatted_address;
$scope.locationgoogle.loglat = place.geometry.location;
$scope.locationgoogle.locationText = $scope.locationText;
$scope.$apply();
});
}
}
})
Here is html for directive:
<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model='locationText'/>
The directive works fine, I create a isolated scope(locationgoogle) to pass the information I need to parent controller(mainCtr), now in the mainCtr I have a function calld reset(), after I click this,I need to clean up the input make it empty. How Can I do it?
One way to access the value of the model in your directive from a parent controller is to put that on the isolate scope too and use the two-way binding flag = like you've done with the locationgoogle property. Try this:
DEMO
html
<body ng-controller="MainCtrl">
<button ng-click="reset()">Reset</button>
<smart-googlemaps location-text="locationText"></smart-googlemaps>
</body>
js
app.controller('MainCtrl', function($scope) {
// need to define model in parent and pass to directive
$scope.locationText = {
value: ''
};
$scope.reset = function(){
$scope.locationText.value = '';
}
});
app.directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '=',
locationText: '='
},
// ng-model="locationText.value"
template: '<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model="locationText.value"/>',
link: function($scope, elm, attrs){
// implement directive googlemaps logic, set text value etc.
$scope.locationText.value = 'foo';
}
}
})

Integrate jquery masked input as angularjs directive

I wish to use masking in form inputs. I have created a directive uiMask which takes predefined masking formats like DoB or zip. In order to initiate masking, I apply the masking in directive's link function. And to update the model I manually trigger the digest cycle using $apply on keyup. Is this approach correct?
angular.module('formApp', [])
.controller("DemoFormController",['$scope',function($scope){
}])
.directive('uiMask', [
function () {
return {
require:'ngModel',
scope: {
type : "#uiMask"
},
controller: function($scope){
$scope.dob = "99/99/9999";
$scope.zip = "99999";
},
link:function ($scope, element, attrs, controller) {
var $element = $(element[0]);
$element.mask($scope.$eval($scope.type));
/* Add a parser that extracts the masked value into the model but only if the mask is valid
*/
controller.$parsers.push(function (value) {
var isValid = value.length && value.indexOf("_") == -1;
return isValid ? value : undefined;
});
/* When keyup, update the view value
*/
element.bind('keyup', function () {
$scope.$apply(function () {
controller.$setViewValue(element.val());
});
});
}
};
}
]);
<!doctype html>
<html ng-app="formApp">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.maskedinput/1.3.1/jquery.maskedinput.js"></script>
</head>
<body>
<div ng-controller="DemoFormController" class="container">
{{employee | json}}
<input type="text" class="form-control" id="dob" name="dob" placeholder="Date of Birth" ui-mask="dob" ng-model="employee.dob">
<input type="text" class="form-control" id="zip" placeholder="Zip" ui-mask="zip" ng-model="employee.zip" >
</div>
</body>
</html>
I did something similar with putting a jQuery "plugin" (uniform.js) into an angular directive. I think what might help is if you attached a $watch to the element. Here is what I did for the uniform directive:
(function () {
'use strict';
angular.module('pfmApp').directive('uniform', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
$(element).addClass('uniform').uniform();
//$(element).uniform.update();
scope.$watch(function() { return ngModel.$modelValue }, function() {
$timeout(jQuery.uniform.update, 0);
});
}
}
});
}());
I hope this helps or perhaps points you in the right direction.

AngularJS and contentEditable two way binding doesn't work as expected

Why in the following example the initial rendered value is {{ person.name }} rather than David? How would you fix this?
Live example here
HTML:
<body ng-controller="MyCtrl">
<div contenteditable="true" ng-model="person.name">{{ person.name }}</div>
<pre ng-bind="person.name"></pre>
</body>
JS:
app.controller('MyCtrl', function($scope) {
$scope.person = {name: 'David'};
});
app.directive('contenteditable', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('blur', function() {
scope.$apply(function() {
ctrl.$setViewValue(element.html());
});
});
// model -> view
ctrl.$render = function() {
element.html(ctrl.$viewValue);
};
// load init value from DOM
ctrl.$setViewValue(element.html());
}
};
});
The problem is that you are updating the view value when the interpolation is not finished yet.
So removing
// load init value from DOM
ctrl.$setViewValue(element.html());
or replacing it with
ctrl.$render();
will resolve the issue.
Short answer
You're initializing the model from the DOM using this line:
ctrl.$setViewValue(element.html());
You obviously don't need to initialize it from the DOM, since you're setting the value in the controller. Just remove this initialization line.
Long answer (and probably to the different question)
This is actually a known issue: https://github.com/angular/angular.js/issues/528
See an official docs example here
Html:
<!doctype html>
<html ng-app="customControl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form name="myForm">
<div contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</body>
</html>
JavaScript:
angular.module('customControl', []).
directive('contenteditable', function() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
Plunkr
Here is my understanding of Custom directives.
The code below is basic overview of two way binding.
you can see it working here as well.
http://plnkr.co/edit/8dhZw5W1JyPFUiY7sXjo
<!doctype html>
<html ng-app="customCtrl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script>
angular.module("customCtrl", []) //[] for setter
.directive("contenteditable", function () {
return {
restrict: "A", //A for Attribute, E for Element, C for Class & M for comment
require: "ngModel", //requiring ngModel to bind 2 ways.
link: linkFunc
}
//----------------------------------------------------------------------//
function linkFunc(scope, element, attributes,ngModelController) {
// From Html to View Model
// Attaching an event handler to trigger the View Model Update.
// Using scope.$apply to update View Model with a function as an
// argument that takes Value from the Html Page and update it on View Model
element.on("keyup blur change", function () {
scope.$apply(updateViewModel)
})
function updateViewModel() {
var htmlValue = element.text()
ngModelController.$setViewValue(htmlValue)
}
// from View Model to Html
// render method of Model Controller takes a function defining how
// to update the Html. Function gets the current value in the View Model
// with $viewValue property of Model Controller and I used element text method
// to update the Html just as we do in normal jQuery.
ngModelController.$render = updateHtml
function updateHtml() {
var viewModelValue = ngModelController.$viewValue
// if viewModelValue is change internally, and if it is
// undefined, it won't update the html. That's why "" is used.
viewModelValue = viewModelValue ? viewModelValue : ""
element.text(viewModelValue)
}
// General Notes:- ngModelController is a connection between backend View Model and the
// front end Html. So we can use $viewValue and $setViewValue property to view backend
// value and set backend value. For taking and setting Frontend Html Value, Element would suffice.
}
})
</script>
</head>
<body>
<form name="myForm">
<label>Enter some text!!</label>
<div contenteditable
name="myWidget" ng-model="userContent"
style="border: 1px solid lightgrey"></div>
<hr>
<textarea placeholder="Enter some text!!" ng-model="userContent"></textarea>
</form>
</body>
</html>
Hope, it helps someone out there.!!
Check this angularjs directive
https://github.com/clofus/angular-inputnlabel
http://clofus.com/viewarticles/109
You may run into issues using #Vanaun's code if a scope.$apply is already in progress. In this case I use $timeout instead which resolves the issue:
Html:
<!doctype html>
<html ng-app="customControl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form name="myForm">
<div contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</body>
</html>
JavaScript:
angular.module('customControl', []).
directive('contenteditable', function($timeout) {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
$timeout(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
Working Example: Plunkr

Resources