AngularJS - Directive ngModel doesn't get updated - angularjs

I have a good understanding of AngularJS Directives, but I'm thinking I'm missing something here with my directives.
What's wrong : On the initial page load, the ngModel gets triggered correctly and I successfully receive the data and it's modeled in the input.
Everything I do after... doesn't work. If I modify the text in the input, the model is not updated.
Please note that if I don't use a directive (straight HTML), it works fine.
I have a directive like so :
.directive('defaultFormGroup', function () {
return {
restrict: 'A',
templateUrl: 'form-group.html',
replace: true,
scope: {
uniqueId: '#',
labelTitle: '#',
placeholderText: '#',
ngModel: '=',
bsLabelClasses: '#',
bsFormControlClasses: '#'
}
};
})
The directive's template :
<div class="form-group">
<label class="{{bsLabelClasses}} control-label text-left" for="{{uniqueId}}">{{labelTitle}}</label>
<div class="{{bsFormControlClasses}}">
<input type="text" class="form-control" id="{{uniqueId}}" placeholder="{{placeholderText}}" data-ng-model="ngModel">
</div>
</div>
And here's how I call the directive in the template:
<div data-default-form-group
data-label-title="Name"
data-placeholder-text="Name"
data-unique-id="name"
data-bs-label-classes="col-sm-2 col-md-2"
data-bs-form-control-classes="col-sm-3 col-md-3"
data-ng-model="site.name">
</div>
I also have the same issue with radio buttons :
.directive('radioSwitch', function () {
return {
restrict: 'A',
templateUrl: 'radio-switch.html',
replace: true,
scope: {
ngModel: '=',
radioName: '#',
firstId: '#',
firstValue: '#',
firstLabel: '#',
secondId: '#',
secondValue: '#',
secondLabel: '#',
}
};
})
Template:
<input type="radio" name="{{radioName}}" value="{{firstValue}}" id="{{firstId}}" data-ng-model="ngModel">
<label for="{{firstID}}">{{firstLabel}}</label>
<input type="radio" name="{{radioName}}" value="{{secondValue}}" id="{{secondId}}" data-ng-model="ngModel">
<label for="{{secondID}}">{{secondLabel}}</label>
HTML:
<div data-radio-switch
data-ng-model="site.existingCustomer"
data-first-label="Yes"
data-second-label="No"
data-radio-name="existingCustomer"
data-first-value="True"
data-first-id="true"
data-second-value="False"
data-second-id="false">
</div>
What am I missing?
UPDATE
I was missing something in my directive... I want to choose the inputType (text, email, password, etc...)
If it's a textarea, the HTML is different... but it seems this line doesn't work data-ng-if=" inputType !== textarea "> ... if I remove it, it works
Anyone know why?
Here's a plunker : http://plnkr.co/edit/ITElF2epihSALeTutQAz

There doesn't seem to be something wrong with the directive itself: look at the example below with your directive, you can see that model which is the ng-model attribute of the directive gets updated. So, please try to reproduce your problem so we can help you (either embedded snippet, or jsfiddle, or just more code)
angular.module('test', [])
.controller('ctrl', function($scope) {
$scope.model = 'foo';
})
.directive('defaultFormGroup', function () {
return {
restrict: 'A',
template: '<div class="form-group"> <label class="{{bsLabelClasses}} control-label text-left" for="{{uniqueId}}">{{labelTitle}}</label> <div class="{{bsFormControlClasses}}"> <input type="text" class="form-control" id="{{uniqueId}}" placeholder="{{placeholderText}}" data-ng-model="ngModel"> </div> </div>',
replace: true,
scope: {
uniqueId: '#',
labelTitle: '#',
placeholderText: '#',
ngModel: '=',
bsLabelClasses: '#',
bsFormControlClasses: '#'
}
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl">
<div>model: {{model}}</div>
<div default-form-group ng-model="model"></div>
</div>
Update: it seems that the issue comes from the ng-if, as reproduced in the following snippet. It is fixed by using nh-show instead, as the ng-if may loose the binding from your directive. I am not clear about the exact cause though.
angular.module('test', [])
.controller('ctrl', function($scope) {
$scope.model = 'foo';
})
.directive('defaultFormGroup', function () {
return {
restrict: 'A',
template: '<div ng-if="true"><input type="text" data-ng-model="ngModel" /></div>',
replace: true,
scope: {
ngModel: '='
}
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl">
<div>model: {{model}}</div>
<div default-form-group ng-model="model"></div>
</div>

Related

Angular Custom Directive .match is not a function

I am trying to create a simple angular radio-group directive. the component will be passed an array of strings, an id, and an attribute to bind the selected option to. When I run my code I get the following error
angular.js:13642TypeError: a.match is not a function
here is what I have:
app.js:
angular.module('atp', [])
.directive('appform', require('./application-directive'))
.directive('radioGroup', require('./radio-group/radio-group-directive'))
.directive('yesNoRadioGroup', require('./radio-group/yes-no-radio-group-directive'));
radio-group-directive.js:
function RadioGroupDirective(){
return{
restrict:'E',
transclude:true,
template:require('./radio-group-template.html'),
replace:true,
scope:{
id:'=',
selected:'=',
options:'='
}
}
}
module.exports = RadioGroupDirective;
radio-group-template.html:
<div>
<label for="{{radioGroupId}}"><ng-transclude></ng-transclude></label>
<fieldset id="{{radioGroupId}}">
<div ng-repeat="option in optionsArray track by $index">
<label for="{{radioGroupId}}-{{$index}}">{{option}}</label>
<input id="{{radioGroupId}}-{{$index}}" type="radio" value="option" ng-model="selectedOption">
</div>
</fieldset>
</div>
yes-no-radio-group-directive.js:
function YesNoRadioGroupDirective() {
return {
restrict: 'E',
transclude:true,
template: require('./radio-group-template.html'),
replace: true,
scope: {
RadioGroupId: '=',
selectedOption: '=',
optionsArray: ['Yes', 'No']
}
}
}
module.exports = YesNoRadioGroupDirective;
usage:
<form>
<yes-no-radio-group radio-group-id="test" selected-option="Controller.Form.SelectedYesNo">Select Yes or No:</yes-no-radio-group>
<radio-group radio-group-id="test-radio" options-array="['Hello World','Some Other Option']" selected="Controller.Form.SelectedYesNo">Select one of the options:</radio-group>
</form>
Why are you doing this? You don't need require at first instance and secondly if you are requiring a html, u need templateUrl instead of template
template:require('./radio-group-template.html'),

angularjs dynamic templateurl values

I am trying to create a "name" directive that can host both first and last names.
the code i currently have is:
index.html
<div data-dm-name label="First Name:" info="first_name"></div>
<div data-dm-name label="Last Name:" info="last_name"></div>
directive:
angular
.module('app.names.directive',[])
.directive('dmName', [ directive ])
function directive() {
return {
restrict: 'E',
scope: {
label: '=',
info: '='
},
templateUrl: '/assets/components/name_template.html'
};
}
name_template.html
<div class="wizard-form-group">
<div class="input-group">
<label class="wizard-form-label" for="info">
{{ label }}
</label>
<span class="input-field"
data-ng-class="{
error: form.info.$dirty &&
form.info.$invalid,
focused: form.info.$focused
}"
>
<input
type="text"
name="info"
id="info"
data-ng-model="info"
data-ng-required="true"
data-ng-focus="form.info.$focused = true"
data-ng-blur="form.info.$focused = false"
/>
</span>
</div>
</div>
My problem is that I don't seem to be able to pass in the values for label and info into the template file. What am I doing wrong?
I have just started out using angular so hopefully this has a simple solution.
Thanks in advance
in your directive function add a link function
function directive() {
return {
restrict: 'EA',
scope: {
label: '=',
info: '='
},
templateUrl: '/assets/components/name_template.html',
link : function($scope, element, attrs){
if(attrs.label){
$scope.label = attrs.label
}
if(attrs.info){
$scope.info = attrs.info
}
}
};
}
Your directive is restricted to element, but you're using it as attribute. So you're directive isn't acting upon the element.
You should modify the DDO to:
function directive() {
return {
restrict: 'A', // attribute allowed
scope: {
label: '=',
info: '='
},
templateUrl: '/assets/components/name_template.html'
};
}

Create custom input directive in angular

I would like to create a custom input that looks like that:
<my-input ng-model="aScopeProperty" placeholder="Enter text"
data-title="This is my title"></my-input>
my-input should receive any property that regular input can get (like placeholder and etc...).
the output should be like this (myInputTemplate.html):
<div class="my-input">
{{title}}
<input type="text" ng-model="text" />
</div>
I created a directive:
myApp.directive('myInput', function(){
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/myInput/myInputTemplate.html',
replace: true,
scope: {
text: '=ngModel',
title: '=title'
},
}
});
the ng-model is bindded ok now,
my question is:
How can I pass the attributes (like placeholder and etc) from my-input to the inside input?
I think that I approached it the wrong way, maybe I need to do it like that:
<input my-input ng-model="aScopeProperty" placeholder="Enter text"
data-title="This is my title"></input>
and to wrap the input with:
<div class="my-input">
{{title}}
<here will be the original input>
</div>
directive call should be like
<my-input ng-model="aScopeProperty" placeholder="'Enter text'" title="'This is my title'"></my-input>
note the placeholder="'Enter text'" Enter text with in quotes ('), this indicate these values are string so angular will not search for scope variable.
and in the directive
myApp.directive('myInput', function(){
return {
restrict: 'E',
require: 'ngModel',
templateUrl: '/myInput/myInputTemplate.html',
replace: true,
scope: {
text: '=ngModel',
title: '=title',
placeholder : '=placeholder'
},
}
});
and the template
<div class="my-input">
{{title}}
<input type="text" ng-model="text" placeholder="{{ placeholder }}" />
</div>
here is the demo Plunker
You can use ng-attr as the following:
<input type="text" ng-model="text" ng-attr-placeholder="{{placeholder}}"/>
And send placeholder as attribute in your scope as the following:
scope: {
text: '=ngModel',
title: '=title',
placeholder : '=placeholder'
}
I recommend to read about ng-attr-attrName, and this useful answer.
Dynamic attributes
Read my question, and accepted answer.
The second approach succeeded!
final code:
<input my-input ng-model="aScopeProperty" placeholder="Enter text"
data-title="This is my title">
The Directive:
app.directive('myInput', function () {
return {
restrict: 'A',
scope: {
title: '=title'
},
link: function ($scope, $element) {
var wrap = angular.element('<div class="my-input-wrapper" />');
$element.addClass('form-control').removeAttr('my-input');
wrap.insertBefore($element);
$element.appendTo(wrap);
if ($scope.title) {
var title = angular.element('<span class="my-title">' + $scope.title + '</span>');
title.appendTo(wrap);
}
},
}
});
I even created my first Plunker for it, unfortunately, the Plunker don't works because it doesn't recognize: insertBefore and appendTo
http://plnkr.co/edit/XnFM75vOBg4ifHUQzGOt?p=preview

Angularjs: validation not working when control is based on directive

Being rather new to Angularjs, I am creating textbox-label combinations in Angularjs using directives. It's working very well, but I can't get validation to work. Here is a stripped-down example.
The Html:
<form name="form" novalidate ng-app="myapp">
<input type="text" name="myfield" ng-model="myfield" required />{{myfield}}
<span ng-show="form.myfield.$error.required">ERROR MSG WORKING</span>
<br>
<div mydirective FIELD="myfield2" />
</form>
The Javascript:
var myapp = angular.module('myapp', []);
myapp.directive('mydirective', function () {
return {
restrict: 'A',
scope: { ngModel: '=' },
template: '<input type="text" name="FIELD" ng-model="FIELD" />{{FIELD}}
<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};
});
The hard coded input - myfield - works, the other - myfield2 - doesn't (the binding does, just not the required-error message).
How do I tell the ng-show attribute to sort of "replace" FIELD in form.FIELD.$error.required by myfield2?
Here is a jsFiddle.
The problem is that your directive creates a new scope for the directive, this new scope does not have access to the form object in the parent scope.
I came up with two solutions, though I suspect there is a more elegant "Angular" way to do this:
Passing down the form object
Your view becomes:
<div mydirective FIELD="myfield2" form="form" />
And the scope definition object:
return {
restrict: 'A',
scope: {
ngModel: '=',
form: '='
},
template: '<input type="text" name="FIELD" ng-model="FIELD" required/>{{FIELD}}<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};
I've updated the fiddle with this code: http://jsfiddle.net/pTapw/4/
Using a controller
return {
restrict: 'A',
controller: function($scope){
$scope.form = $scope.$parent.form;
},
scope: {
ngModel: '='
},
template: '<input type="text" name="FIELD" ng-model="FIELD" required/>{{FIELD}}<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};

Angular directive: How to make assign value to parent scope?

I have a controller AppCtrl as
scope.transaction = {}
The index looks like
<div class="control-group">
<label class="control-label">Date</label>
<div class="controls">
<div class="control-group input-append date form_datetime">
<date-time-picker data-ng-model="transaction.date"></date-time-picker>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">Amount</label>
<div class="controls">
<div class="input-append">
<input type="text" name="transactionAmount" ng-model="transaction.amount" required>
</div>
and my custom directive looks like
angular.module('customDirectives', []).directive('dateTimePicker', function() {
return {
restrict: 'E',
replace: true,
scope: {
transaction['date']: '=' # COMPILATION ERROR HERE
},
template: '<div class="control-group input-append date form_datetime">'+
'<input type="text" readonly data-date-format="yyyy-mm-dd hh:ii" name="transactionDate" ng-model="transaction.date" data-date-time required>'+
'<span class="add-on"><em class="icon-remove"></em></span>'+
'<span class="add-on"><em class="icon-th"></em></span>'+
'</div>',
link: function(scope, element, attrs, ngModel) {
var input = element.find('input');
element.datetimepicker({
format: "yyyy-mm-ddThh:ii:ssZ",
showMeridian: true,
autoclose: true,
todayBtn: true,
pickerPosition: 'bottom-left'
});
element.bind('blur keyup change', function(){
console.log('binding element');
scope.$apply(date);
});
function date() {
console.log('setting date',input.val());
scope.ngModel = input.val();
}
date(); // initialize
}
}
});
I want to assign the date value from my directive to $scope.transaction.date but it is failing as compilation error, how can I achieve this?
scope: {
transaction['date']: '=' # COMPILATION ERROR HERE
},
Should be
scope: {
transactionDate: '='
},
And
<date-time-picker data-ng-model="transaction.date"></date-time-picker>
Should be
<date-time-picker transaction-date="transaction.date"></date-time-picker>
Then within your directive you can call scope.transactionDate = myValue;
within scope.$apply();
EDIT: If you want to use ng-model within your directive then you can use
....
restrict: 'E',
require: '?ngModel',
....
And
controller.$setViewValue(value); //this will in directive code where you want set the value of the ng-model bound variable.
In Html
<date-time-picker data-ng-model="transaction.date"></date-time-picker>

Resources