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

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>

Related

ng-messages not showing in directive

I'm trying to re-use code as much as I can so I'm wrapping ng-messages in a directive. However, the messages are not shown..
<form name="elementForm" novalidate>
<md-input-container>
<label>Category</label>
<input ng-required="true" ng-model="item.category" name="category" type="text">
<administration-input-validation control="category" formname="elementForm"></administration-input-validation>
</md-input-container>
</form>
Directive:
angular.module('app.analytics')
.directive('administrationInputValidation', function () {
return {
template:
'<div ng-messages>' +
'<div ng-message="required">This is required</div>' +
'</div>',
restrict: 'E',
replace: true,
require: '^form',
scope: {
control: '=',
formname: '='
},
link: function(scope, element, attrs) {
var formStr = attrs.formname + "." + attrs.control;
element.attr("ng-messages", formStr + "." + "$error");
}
};
});
I also tried hardcoding but it's still not showing
<div ng-messages="elementForm.category.$error">
UPDATE
Binding works now, but it's not showing the messages

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 Directive for twitter bootstrap form-group

I've been playing with angular lately, so far so good, but im struggling with directives.
I'm trying to create a directive that generates the html mark up for a standard bootstrap form group with its corresponding validation messages.
So basically I'm trying to convert this:
<form-group label="Password">
<input type="password" data-ng-model="vm.password" name="password" id="password" class="form-control form-control-validate"
required data-ng-minlength="6"
data-required-error="Password is required" data-minlength-error="Your password must have at least 6 characters" />
</form-group>
into this:
<div class="form-group" data-ng-class="{'has-error': invalid}">
<label for="password" class="col-md-2 control-label">Password</label>
<div class="col-md-10">
<input data-ng-model="vm.password" type="password" id="password" name="password" class="form-control"
required data-ng-minlength="6"/>
<div data-ng-show="changePasswordForm.$dirty && changePasswordForm.oldPassword.$invalid">
<label data-ng-show="changePasswordForm.oldPassword.$error.required" class="label label-danger">
Password is required
<br />
</label>
<label data-ng-show="changePasswordForm.oldPassword.$error.minlength" class="label label-danger">
Your password must have at least 6 characters
</label>
</div>
</div>
</div>
So far this is what I have:
app.directive('formGroup', function () {
return {
templateUrl: 'app/directives/formGroup.html',
restrict: 'E',
replace: true,
transclude: true,
require: "^form",
scope: {
label: "#",
},
link: function (scope, element, attrs, formController) {
var input = element.find(":input");
var id = input.attr("id");
scope.for = id;
var inputName = input.attr("name");
// Build the scope expression that contains the validation status.
// e.g. "form.example.$invalid"
var inputInvalid = [formController.$name, inputName, "$invalid"].join(".");
scope.$parent.$watch(inputInvalid, function (invalid) {
scope.invalid = invalid;
});
}
};
});
formGroup.html:
<div class="form-group" ng-class="{ 'has-error': invalid }">
<label class="col-md-2 control-label" for="{{for}}">{{label}}</label>
<div class="col-md-10">
<div data-ng-transclude=""></div>
</div>
</div>
This sets correctly the bootstrap class "has-error" to the form-group if the input is invalid.
Now I want to add validation messages, and I couldn't find a way that works. This is what I have:
app.directive('formControlValidate', function () {
return {
templateUrl: "app/directives/formControlValidate.html",
restrict: 'C',
require: ["^form", "ngModel"],
scope: { },
transclude: true,
//replace: true,
link: function (scope, element, attrs, controls) {
var form = controls[0];
var inputName = attrs.name;
var inputErrors = [form.$name, inputName, "$error"].join(".");
scope.$parent.$watch(inputErrors, function (newValue) {
if (newValue) {
scope.errors = [];
angular.forEach(newValue, function (value, key) {
var error = attrs[key + 'Error'];
if (value && error) {
scope.errors.push(error);
}
});
}
}, true);
}
};
formControlValidate.html:
<div class="controls" ng-transclude></div>
<div data-ng-repeat="error in errors">
<div class="label label-danger">
{{error}}
</div>
</div>
But this doesn't work. I'm randomly changing parameters in both directives but can't figure out what how to make it work.
Any ideas or improvements would be greatly appreciated.
Thanks!
UPDATE: this is my latest gist (angular 1.3): https://gist.github.com/lpsBetty/3259e966947809465cbe
OLD solution:
I tried something similiar, maybe this link can help you too: http://kazimanzurrashid.com/posts/create-angularjs-directives-to-remove-duplicate-codes-in-form
This was my solution. I don't know why but I had to use form.$dirty, it was not possible to use input.$dirty..
(and I use angular-translate)
HTML:
<form-group input="form.password">
<input type="password" class="form-control" placeholder="{{ 'user.password' | translate }}" required
name="password" ng-model="user.password" />
</form-group>
Directive:
app.directive('formGroup', function ($parse) {
return {
restrict: 'E',
require: '^form',
transclude: true,
replace: true,
scope: {
cssClass: '#class',
input: '='
},
template: '<div class="form-group" ng-class="{\'has-error\':hasError, cssClass:true}">'+
'<div ng-transclude></div>' +
'<div ng-show="hasError">' +
'<span ng-repeat="(key,error) in input.$error" class="help-block"' +
'ng-show="input.$error[key]">{{\'form.invalid.\'+key | translate}}</span>' +
'</div>' +
'</div>',
link: function (scope, element, attrs, ctrl) {
var form = ctrl;
var input = attrs.input;
scope.$parent.$watch(input+'.$invalid', function (hasError) {
scope.hasError = hasError && form.$dirty;
});
}
};
});

Resources