AngularJS custom form component / directive using ng-model - angularjs

Angular custom form component / directive and $dirty property
When using regular input, such as
<form name="myForm">
<input type="text" ng-model="foobar">
</form>
after typing in the input box myForm.$dirty is true.
I'd like to create a simple directive such as
angular.module('myModule', [])
.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
fooBar: '='
},
template: '<div><button ng-click="fooBar=foo"></button><button ng-click="fooBar=bar"></button></div>'
};
});
Sample usage would be
<form name="myForm">
<my-directive foo-bar="myObj.foobarValue"></my-directive>
</form>
and after user clicks on any of the two buttons, myForm$dirty is set to true.
How is this accomplished?

Implementing custom form controls (using ngModel)
Use the ngModel controller and the object form of the require property in the DDO:
angular.module('myModule', [])
.directive('myDirective', function() {
return {
restrict: 'E',
require: { ngModelCtrl: 'ngModel' },
scope: {
ngModel: '<'
},
bindToController: true,
controllerAs: '$ctrl',
template:
`<div>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('foo')">
Set foo
</button>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('bar')">
Set bar
</button>
</div>`,
controller: function ctrl() {}
};
});
Usage:
<form name="myForm">
<input type="text" ng-model="foobar">
<my-directive ng-model="foobar"></my-directive>
</form>
By instantiating and using the ng-model controller, the directive will automatically set the form controls as necessary.
The DEMO
angular.module('myModule', [])
.directive('myDirective', function() {
return {
restrict: 'E',
require: { ngModelCtrl: 'ngModel' },
scope: {
ngModel: '<'
},
bindToController: true,
controllerAs: '$ctrl',
template:
`<div>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('foo')">
Set foo
</button>
<button ng-click="$ctrl.ngModelCtrl.$setViewValue('bar')">
Set bar
</button>
</div>`,
controller: function ctrl() {}
};
});
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myModule">
<h2>ngModel DEMO</h2>
<form name="myForm">
<input type="text" ng-model="foobar">
<my-directive ng-model="foobar"></my-directive>
</form>
<br>myForm.$dirty = {{myForm.$dirty}}
<br>myForm.$pristine = {{myForm.$pristine}}
<br><button ng-click="myForm.$setDirty()">Set dirty</button>
<br><button ng-click="myForm.$setPristine()">Set pristine</button>
</body>
I recommend isolate scope with ngModel as an input. Output should be done with the $setViewValue method.
For more information, see
AngularJS Developer Guide - Implementing custom form controls (using ngModel)
AngularJS Developer Guide - Component-based application architecture

Related

Angular modal directive - issue when having more than 1 in a page

I made a modal directive for my angular app.
modal-directive.js
'use strict';
backyApp.directive('appModal', function() {
return {
restrict: 'A',
transclude: true,
link: function($scope, elem, attr){
$scope.modalClass = attr.appModal;
},
scope: '#',
templateUrl: './components/modal/modal.html'
};
});
and the template looks like this: (modal.html)
<!-- Modal -->
<div class="app-modal" ng-class="modalClass">
<div ng-transclude></div>
</div>
Now, let's pretend we have 2 modals in a page:
<div app-modal="firstModal">
<div class="form-group">
<input type="text" />
<input type="submit" value="Submit" />
</div>
</div>
</div>
<div app-modal="secondModal">
<div class="form-group">
<input type="text" />
<input type="submit" value="Submit" />
</div>
</div>
</div>
Problem: I end up with 2 modals having the same class (in my example above, secondModal will be attached to 2 of my modals)
Why does this happen? I need the value of my directive to be attached to each modal because thats the only way I can open the one I want.
I know this is horrible explanation Let me know if you have any question
Edit:
I want to have 2 app-modal divs, each one having its directive value as a class attached to it. Hope it's more clear now.
Use an isolated scope in the directive
backyApp.directive('appModal', function() {
return {
restrict: 'A',
transclude: true,
link: function($scope, elem, attr){
$scope.modalClass = attr.appModal;
},
scope: {},
templateUrl: './components/modal/modal.html'
};
});
Here is a plunker I did for this
https://embed.plnkr.co/UwjBIqTh5fNlAcbIs6TS/

Decorating AngularJS Directive template inside the directive

I have a directive which is defined, say, as below:
angular.module('some-module').directive('someDirective', function() {
return {
restrict: 'E',
replace: 'true',
templateUrl: 'some-template.html',
link: link,
require: '^form',
transclude: true,
scope: {
decorate: '=',
}
};
});
Let's say this is how the some-template.html looks (there is more in the actual template though):
<div ng-transclude></div>
And this is how I will use the directive:
<some-directive decorate="true">
<input name="x" type="number" ng-model="x">
<input name="y" type="number" ng-model="y">
</some-directive>
<some-directive decorate="false">
<input name="a" type="number" ng-model="a">
<input name="b" type="number" ng-model="b">
</some-directive>
What I want the directive to do is to manipulate the DOM so that if decorate is true then, the two input fields should be decorated with some divs as below:
<div class="some-outer-class">
<div class="some-class-1">
<input name="x" type="number" ng-model="x">
</div>
<div class="some-class-2">
<input name="y" type="number" ng-model="y">
</div>
<div><i class="some-glyph-icon"></i></div>
</div>
If the decorate attribute is false, or absent, the directive shouldn't do any manipulation.
Couldn't figure out how to do this. Any help is appreciated.
You can simply modify the template in link function :
Demo
link: function(scope, elem, attrs){
if(scope.decorate || attrs.decorate != null){
elem.find('INPUT').wrap('<div class="decorate-class"></div>')
}
}
You can do this inside the directive. You first define a controller inside your directive as follows:
angular.module('some-module').directive('someDirective', function() {
var controller = function($scope) {
//The controller methods
};
return {
restrict: 'E',
replace: 'true',
templateUrl: 'some-template.html',
link: link,
require: '^form',
transclude: true,
scope: {
decorate: '=',
},
controller: controller,
controllerAs: 'myCtrl'
};
});
Inside the controller, you check the decorate value, and make the DOM manipulation accordingly. You can access the decorate value from your controller via the $scope.
var controller = function($scope) {
if($scope.decorate){
//Make the DOM manipulation
}
};
DOM manipulation is done as follows:
var initialInput = document.querySelector('query'); //You have to select your desired input elements here
var decoratedInput = document.createElement("div");
decoratedInput.className += " some-class-1";
decoratedInput.innerHTML = "<input name='x' type='number' ng-model='x'>";
initialInput.parentNode.replaceChild(decoratedInput, initialInput);

angular 1.4 directive toggle view

I want to show two different inputs depending on a toggle attribute.
No I have the problem that I should define each attribute/property of the input in my directive, but the binding doesn't work.
Directive:
angular.module('directive')
.directive('inputBlock', function () {
return {
restrict: 'AEC',
replace: true,
scope: {
model: '=',
modernStyle:'=',
name:'=',
type:'=',
label:'='
},
link: function (scope, elem, attrs, ctrl) {},
templateUrl: 'views/templates/inputBlockTemplate.html'
};
});
Template:
<div>
<div ng-if="!modernStyle">
<label>{{label}}</label>
<input ng-model="model" name="{{name}}" type="{{type}}"/>
</div>
<md-input-container ng-if="modernStyle">
<label>{{label}}</label>
<input ng-model="model" name="{{name}}" type=" {{type}}"/>
</md-input-container>
</div>
Usage:
<input-block model="name" label="'firstname'" modern-style="true" name="'firstname'" type="'text'">
</input-block>
Is it possible to do something like a toggle in directives?
Furthermore is it possible to redirect the bindings to directives?
ng-if creates a new child scope, try change by ng-show/ng-hide
Understanding Scopes
Other considerations:
As you use name,label and type as text inside your directive is not necesary that it will be binding, I recomend use # instead of =, or access it directly from the attrs link argument.
The scope.model could be set as ngModel to use the default angular directive.
Javascript:
angular.module("directive").directive("inputBlock", function () {
return {
restrict: "AEC",
replace: true,
scope: {
ngModel: "=",
modernStyle:"#",
//name:"#",
//type:"#",
//label:"#"
},
link: function (scope, elem, attrs, ctrl) {
scope.type=attrs.type;
scope.name=attrs.name;
scope.label=attrs.label;
},
templateUrl: "views/templates/inputBlockTemplate.html"
};
});
Template:
<div>
<div ng-show="!scope.modernStyle">
<label>{{scope.label}}</label>
<input ng-model="scope.model" name="{{scope.name}}" type="{{scope.type}}"/>
</div>
<md-input-container ng-show="scope.modernStyle">
<label>{{scope.label}}</label>
<input ng-model="scope.model" name="{{scope.name}}" type={{scope.type}}"/>
</md-input-container>
</div>
Usage:
<input-block ng-model="name" label="firstname" modern-style="true" name="firstname" type="text">
</input-block>

Custom AngularJS Directive For Working Hours

I was writing an angularJS directive to input opening hours. Something like:
Here is the directive:
.directive('openhoursDay', function() {
return {
scope: {
openhoursDay:"=",
openhoursActive: "=", //import referenced model to our directives scope
openhoursFrom: "=",
openhoursTo: "="
},
templateUrl: 'templates/open_hours.html',
link: function(scope, elem, attr, ctrl) {
}
}
});
The template:
<div >
<span>{{openhoursDay.day}}</span>
<input type="checkbox" ng-model="openhoursDay.active"/>
<input type="text" ng-model="openhoursDay.open"/>
<input type="text" ng-model="openhoursDay.close"/>
<br>
</div>
HTML:
<div ng-model="work.dt[0]" openhours-day="Sun" openhours-active="active" openhours-from="from" openhours-to="to"></div>
<div ng-model="work.dt[1]" openhours-day="Mon" openhours-active="active" openhours-from="from" openhours-to="to"></div>
<div ng-model="work.dt[2]" openhours-day="Tue" openhours-active="active" openhours-from="from" openhours-to="to"></div>
{{work}}
And Controller:
$scope.work={
dt:[]
};
The problem that I am facing is, scope work is never updated whatever I type on input box, or even if click-unclick checkbox. It remain unchanged as: {"dt":[]}
ng-model is for input fields. So you're passing it in but you weren't really using it for anything. Also you are reading the attributes passed in using = but perhaps you meant to use #. I've created a plunkr demonstrating how you could get this working.
Here's the directive:
.directive('openhoursDay', function() {
return {
scope: {
model:"=",
openhoursDay:"#",
openhoursActive: "#", //import referenced model to our directives scope
openhoursFrom: "#",
openhoursTo: "#"
},
templateUrl: 'open_hours.html',
link: function(scope, elem, attr, ctrl) {
scope.model = {};
scope.model.day = scope.openhoursDay;
scope.model.active = scope.openhoursActive;
scope.model.open = scope.openhoursFrom;
scope.model.close = scope.openhoursTo;
}
}
})
The template:
<div >
<span>{{model.day}}</span>
<input type="checkbox" ng-model="model.active"/>
<input type="text" ng-model="model.open"/>
<input type="text" ng-model="model.close"/>
<br>
</div>
HTML:
<div model="work.dt[0]" openhours-day="Sun" openhours-active="active" openhours-from="from" openhours-to="to"></div>
<div model="work.dt[1]" openhours-day="Mon" openhours-active="active" openhours-from="from" openhours-to="to"></div>
<div model="work.dt[2]" openhours-day="Tue" openhours-active="active" openhours-from="from" openhours-to="to"></div>
work:{{work}}
And Controller:
.controller('MainController', ['$scope', function($scope){
$scope.work={
dt:[]
};
}])
You have to pass the ng-model attribute to the isolated scope, and then, use it in the template as following:
.directive('openhoursDay', function() {
return {
scope: {
openhoursDay: "=",
openhoursActive: "=", //import referenced model to our directives scope
openhoursFrom: "=",
openhoursTo: "=",
ngModel: "=" // Here is the ng-model
},
template: '<div ><span>{{openhoursDay.day}}</span><input type="checkbox" ng-model="ngModel.openhoursDay.active"/><input type="text" ng-model="ngModel.openhoursDay.open"/><input type="text" ng-model="ngModel.openhoursDay.close"/><br> </div>',
link: function(scope, elem, attr, ctrl) {}
};
})
I have created a Plunkr which simulates your situation. You could check it out.

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>'
};

Resources