AngularJS form in custom directive not updating after data binding - angularjs

This is my code :
Template :
<div ng-repeat="framework in frameworks" id="{{framework.id}}"
role="tabpanel" class="tab-pane show active" >
<div class="progress" >
<div ng-click="display_framework_formulaire(framework)"
class="progress-bar" role="progressbar"
style="width: {{framework.level}}%"
aria-valuenow="{{framework.level}}"
aria-valuemin="0" aria-valuemax="100">
<div data-toggle="tooltip" data-placement="top"
data-title="{{framework.version}}">
</div>
{{framework.nom}}
</div>
</div>
</div>
This is the function in my controller :
$scope.display_framework_formulaire = function(framework)
{
$scope.frameworkCourant = framework;
$scope.vueCourante = 'VUE_FORMULAIRE_FRAMEWORK'
}
This is the directive in template:
<formulaire-framework ng-show="vueCourante == 'VUE_FORMULAIRE_FRAMEWORK'"
categories="categories" framework="frameworkCourant" >
</formulaire-framework>
And the directive :
app.directive("formulaireFramework", function() {
return {
restrict: "E",
templateUrl: 'formulaireFramework.html',
scope: {
framework : '=framework',
categories : '=categories'
},
}
})
So it's working after loading homepage.
But when i do :
restfulService.getFrameworksByCategorieValue(categorie_value)
.then(function(frameworks){
$scope.frameworks = [];
angular.forEach(frameworks, function(framework, key) {
$scope.frameworks.push(framework);
});
});
Which update the list of frameworks, when i click on a framework, the value of element of formulare is this of last action and not the value of the framework current.
PS : my form :
<form name="frameworkForm" id="frameworkForm" method="POST" class="spacer"
ng-submit="submit()" ng-controller="frameworkFormulaireController" >
<div class="input-group">
<select ng-model="framework.categorie.id">
<option ng-repeat="categorie in categories"
ng-value="categorie.id"
ng-selected="categorie.id == framework.categorie.id" >
{{categorie.label}}
</option>
</select>
</div>
<div class="input-group" >
<input type="text" class="form-control" ng-value="id"
ng-model="framework.id">
</div>
<div class="input-group">
<span class="input-group-addon">Nom</span>
<input type="text" class="form-control" ng-value="nom"
ng-model="framework.nom">
</div>
<div class="input-group">
<span class="input-group-addon">Version</span>
<input type="text" class="form-control" ng-value="version"
ng-model="framework.version">
</div>
<div class="input-group">
<span class="input-group-addon">Niveau</span>
<input type="text" class="form-control" ng-value="level"
ng-model="framework.level">
</div>
<input type="submit" class="btn btn-success pull-right" >
</form>

Remove the ng-controller directive from the form:
<form name="frameworkForm" id="frameworkForm" method="POST" class="spacer"
ng-submit="submit()" ̶n̶g̶-̶c̶o̶n̶t̶r̶o̶l̶l̶e̶r̶=̶"̶f̶r̶a̶m̶e̶w̶o̶r̶k̶F̶o̶r̶m̶u̶l̶a̶i̶r̶e̶C̶o̶n̶t̶r̶o̶l̶l̶e̶r̶"̶ >
<!-- input controls -->
<!-- input controls -->
<!-- input controls -->
<!-- input controls -->
</form>
Add the controller to the directive:
app.directive("formulaireFramework", function() {
return {
restrict: "E",
templateUrl: 'formulaireFramework.html',
controller: 'frameworkFormulaireController',
scope: {
framework : '=framework',
categories : '=categories'
},
}
})
The ng-controller directive adds a new child scope which is unneccessary and can have prototypal inheritance variable hiding problems.
For more information, see What are the nuances of scope prototypal / prototypical inheritance in AngularJS?.

Related

Angular validation directive not working properly

I am using custom angular directive for showing validation. Directive code is as below
angularFormsApp.directive('showErrors',['$timeout', function ($timeout) {
return {
restrict: 'A',
require: '^form',
link: function (scope, el, attrs, formCtrl) {
// find the text box element, which has the 'name' attribute
var inputEl = el[0].querySelector("[name]");
// convert the native text box element to an angular element
var inputNgEl = angular.element(inputEl);
// get the name on the text box so we know the property to check
// on the form controller
var inputName = inputNgEl.attr('name');
var helpText = angular.element(el[0].querySelector(".help-block"));
// only apply the has-error class after the user leaves the text box
inputNgEl.bind('blur', function () {
el.toggleClass('has-error', formCtrl[inputName].$invalid);
helpText.toggleClass('hide', formCtrl[inputName].$valid);
});
scope.$on('show-errors-event', function () {
el.toggleClass('has-error', formCtrl[inputName].$invalid);
});
scope.$on('hide-errors-event', function () {
$timeout(function () {
el.removeClass('has-error');
}, 0, false);
});
}
}
}]);
and Html is as below
<div class="container" id="login-form">
<img src="assets/img/logo-big.png">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-heading">
<h2>Login Form</h2>
</div>
<div class="panel-body">
<form name="loginForm" class="form-horizontal" novalidate>
<div class="form-group mb-md" show-errors>
<div class="col-xs-12">
<div class="input-group">
<span class="input-group-addon">
<i class="ti ti-user"></i>
</span>
<input type="text" class="form-control" placeholder="Username" autocomplete="Off" ng-required="true" name="username" autofocus ng-model="loginUser.username">
</div>
<span class="help-block" ng-if="loginForm.username.$error.required">Username is required</span>
</div>
</div>
<div class="form-group mb-md" show-errors>
<div class="col-xs-12" >
<div class="input-group">
<span class="input-group-addon">
<i class="ti ti-key"></i>
</span>
<input type="password" class="form-control" placeholder="Password"
name="password"
ng-model="loginUser.password" autocomplete="Off"
ng-required="true">
</div>
<span class="help-block" ng-if="loginForm.password.$error.required">Password is required</span>
</div>
</div>
<div class="form-group mb-n">
<div class="col-xs-12">
Forgot password?
<div class="checkbox-inline icheck pull-right p-n">
<label for="">
<input type="checkbox"></input>
Remember me
</label>
</div>
</div>
</div>
</form>
</div>
<div class="panel-footer">
<div class="clearfix">
Register
Login
</div>
</div>
</div>
</div>
</div>
</div>
Controller code :
var loginController = function ($scope, $window, $routeParams, $uibModal, $location, $filter, $rootScope, DataService, SharedProperties) {
$rootScope.bodylayout = 'focused-form animated-content';
$scope.loginUser = {
username: "",
password:""
}
$scope.load = function () {
//getAppointmentInfo();
};
$scope.SubmitLoginForm = function () {
$scope.$broadcast('show-errors-event');
if ($scope.loginForm.$invalid)
return;
}
}
angularFormsApp.controller("loginController", ["$scope", "$window", "$routeParams", "$uibModal", "$location", "$filter", "$rootScope", "DataService", "SharedProperties", loginController]);
Now When I open form below input control validation span is displaying by default . When I click on Login button then its showing in red and working fine.
problem is it shouldn't show by default when Page is opend.. Please see image below
Instead of this
loginForm.password.$error.required
try this
(loginForm.$submitted || loginForm.username.$dirty) && loginForm.password.$error.required
Take a look at the ng-messages directive. Its fairly elegant. Example:
<form name="myForm">
<input type="text" ng-model="field" name="myField" required minlength="5" />
<div ng-messages="myForm.myField.$error">
<div ng-message="required">You did not enter a field</div>
<div ng-message="minlength">The value entered is too short</div>
</div>
</form>
You can then combine it with any form validation. Just place the error messages from the validators onto the elements $error object and they are automatically rendered in your UI.
I ended up here as part of my search for an issue. In my case I was using a directive with a changing minimum value like this:
ngOnChanges(changes: SimpleChanges) {
if (changes.minDate && this.control) {
this.control.updateValueAndValidity({ onlySelf: true });
}
}
This means that the form will not be updated, I removed onlySelf and it worked correctly.
this.control.updateValueAndValidity();
Just leaving this as a breadcrumb in case someone else does something similar.

How do I build a directive in angular that adds a wrapper around an input and shows message if input is invalid?

I'm a total beginner in Angular, so maybe I am thinking about it totally wrong.
What I'm trying to do is create a directive that wraps an input in some boilerplate html. It should bind the input's ng-model to the parent scope (the myForm controller's scope) but should have access to the validation state of the input.
Here's my setup:
parent_form.html
<form name="myForm">
<div fieldContainer label="'Field 1'" model="'field1'">
<input type="text" ng-model="field1" required/>
</div>
<div fieldContainer label="'Field 2'" model="'field2'">
<input type="text" ng-model="field2" required/>
</div>
</form>
field_container.html
<div class="row">
<div class="col-md-5">{{label}}</div>
<div class="col-md-5" ng-transclude></div>
<div class="col-md-2" ng-show="valid">REQUIRED!</div>
</div>
parent_form.js
angular.module('myApp')
.controller('myForm', function ($scope) {
$scope.field1 = '';
$scope.field2 = '';
})
.directive('fieldContainer', function () {
return {
restrict: 'A',
templateUrl: 'field_container.html',
transclude: true,
scope: {
label: '=label',
model : '=model'
}
}
});
I got it working by:
Adding a name to the field (so that Angular's Form validation takes over)
Setting ng-show on the validation message to be: $parent.myForm[model].$valid
So apparently the transcluded input still uses the parent (myForm) scope and the directive has access to the parent's scope via scope.$parent.
Not sure if this is the cleanest way to do it...
Final code looks like:
parent_form.html
<form name="myForm">
<div fieldContainer label="'Field 1'" model="'field1'">
<input type="text" name="field1" ng-model="field1" required/>
</div>
<div fieldContainer label="'Field 2'" model="'field2'">
<input type="text" name="field2" ng-model="field2" required/>
</div>
</form>
field_container.html
<div class="row">
<div class="col-md-5">{{label}}</div>
<div class="col-md-5" ng-transclude></div>
<div class="col-md-2" ng-show="$parent.myForm[model].$valid">REQUIRED!</div>
</div>

Using ng-repeat in directive causing form validation not working properly

In html,
<form name="textform" novalidate>
<cms-radio name="color" require="true" option="[{title:'Red', value:'red'},{title:'Orange', value:'orange'},{title:'Green', value:'green'}]" ng-model="color"></cms-radio>
</form>
In JS,
angular.module('cmsRadio',[]).directive('cmsRadio', function(){
'use strict';
return {
restrict: 'E',
scope: {
name:'#',
require:'=',
option:"=",
bindedModel: "=ngModel"
},
replace:true,
templateUrl: 'radio.html'
};
});
In radio.html
<div class="form-group" ng-form="{{name}}" ng-class="{'has-error':{{name}}.$invalid && {{name}}.$dirty}" >
<div class="radio" ng-repeat='item in option'>
<label><input type="radio" name="{{name}}" ng-model="bindedModel" value="{{item.value}}" ng-required="require">{{item.title}}</label>
</div>
<span class="has-error" ng-show='{{name}}.$dirty && {{name}}.$invalid' ng-message='{{name}}.$error'>
<p class="control-label" ng-messsage='require'>{{name}} is required.</p>
</span>
</div>
When I click on the first radio button, it shows the error as follow.
The error disappeared only if I clicked on the three radio buttons. How to prevent the error appeared when only one of the radio button is clicked instead of three? Anyone could help?
EDIT: MY SOLUTION
In html,
<cms-radio label="Color" name="color" require="true" option="[{'answers':[{'title':'Red', 'value':'red'},{'title':'Orange', 'value':'orange'},{'title':'Green', 'value':'green'}],'selectedAnswer':null}]" id="color" class=""></cms-radio>
In JS,
angular.module('cmsRadio',[]).directive('cmsRadio', function(){
'use strict';
return {
restrict: 'E',
scope: {
name:'#',
require:'=',
option:"="
},
replace:true,
templateUrl: 'radio.html'
};
});
In radio.html
<div class="form-group" ng-form="{{name}}" ng-class="{'has-error':{{name}}.$invalid && {{name}}.$dirty}" >
<div ng-repeat="item in option">
<div class="radio" ng-repeat="answer in item.answers">
<label><input type="radio" name="{{name}}" ng-model="item.selectedAnswer" value="{{answer.value}}" ng-required="require">{{answer.title}}</label>
</div>
</div>
<span class="has-error" ng-show='{{name}}.$dirty && {{name}}.$invalid' ng-message='{{name}}.$error'>
<p class="control-label" ng-messsage='require'>{{name}} is required.</p>
</span>
</div>
I would not be interpolating the field names in the ng-show and ng-class directives.
Rather than duplicating scope.name for both the form AND the input names, try giving the form a fixed name (e.g. 'radioList') e.g.:
<div class="form-group" ng-form="radioList" ng-class="{'has-error':radioList[name].$invalid && radioList[name].$dirty}" >
<div class="radio" ng-repeat='item in option'>
<label><input type="radio" name="{{name}}" ng-model="bindedModel" value="{{item.value}}" ng-required="require">{{item.title}}</label>
</div>
<span class="has-error" ng-show='radioList[name].$dirty && radioList[name].$invalid' ng-message='radioList[name].$error'>
<p class="control-label" ng-messsage='require'>{{name}} is required.</p>
</span>
</div>
UPDATE
The answer above was misguided. The issue had nothing to do with the interpolation of name. It was simply that the ng-repeat was creating a child scope and the ng-model did not have a '.' in it, and therefore each child scope was getting its own copy of bindedmodel.
If the directive used the controllerAs option this would not be a problem. However, the simplest solution here where we are using scope directly is to compensate for the child scope by using $parent as below:
<div class="form-group" ng-class="{'has-error':{{name}}.$invalid && {{name}}.$dirty}" >
<div class="radio" ng-repeat='item in option'>
<label><input type="radio" name="{{name}}" ng-model="$parent.bindedModel" value="{{item.value}}" ng-required="require && !bindedModel">{{item.title}}</label>
</div>
<span class="has-error" ng-show="{{name}}.$invalid && {{name}}.$dirty" ng-messages='{{name}}.$error'>
<p class="control-label" ng-messsage='require'>{{name}} is required.</p>
</span>
</div>
Updated plunkr

Bootstrap form validation with Angular directive

I am able to get the form-control class from Bootstrap to behave well within a single HTML file. But when the form-control is defined from within a directive, the flags like $dirty are not being recognized. The class ng-dirty does appear.
HTML using directive:
<form class="form-horizontal" role="form" name="formExpense">
<div frm-item ng-repeat="f in formdata2"></div>
</form>
Directive:
app.directive('frmItem', function() {
return {
restrict: 'A',
templateUrl: 'form.html'
};
});
Template:
<div class="form-group" ng-class="{'has-error':formExpense.{{f.name}}.$dirty}">
<div class="col-md-6 text-right">
<label for="{{f.name}}-entry">{{f.name}}</label>
</div>
<div class="col-md-6">
<input type="text" name="{{f.name}}" ng-model="f.amount"
class="form-control" placeholder="per month">
</div>
</div>
$scope:
$scope.formdata2 = [{'name': 'housing'},{'name': 'medical'}];
Plunk: http://plnkr.co/JcanBWWTphGdL18v6NxZ

Pass ng-model and place-holder value into directive

I have a segment of code needs to be reuse a lot, there for I want to just create a directive for it.
<div class="btn-group">
<div class="input-group">
<div class="has-feedback">
<input type="text" class="form-control" placeholder="BLAH BLAH" ng-model="model">
<span class="times form-control-feedback" ng-click="model=''" ng-show="model.length > 0"></span>
</div>
</div>
</div>
I want to use this code as template in directive.
Create a directive used as follow:
<div search-Field ng-model="model" placeholder="STRING"></div>
to replace to old html, ng-model and placeholder will be as variables.
angular.module('searchField', [])
.directive('searchField', [function () {
return {
scope: {
placeholder: '#',
ngModel: '='
},
templateUrl: 'Partials/_SearchInputGroup.html'
}
}]);
Is it the way of doing it?
That looks fine.
Here is a sample for you -http://plnkr.co/edit/LCWHRj6xc9bxwrgpaAb4
corrected few typos and binded placeholder and ngModel data in the directive.
Template:
<div class="btn-group">
<div class="input-group">
<div class="has-feedback">
<input type="text" class="form-control" placeholder="{{placeholder}}" ng-model="ngModel">
<span class="times form-control-feedback" ng-click="model=''" ng-show="ngModel.length > 0">Show</span>
</div>
</div>
</div>

Resources