Share code between controllers and directives - angularjs

In my angular app I have two input fields on most views that have a start and end date. These are updated by the user and the result is stored in the URL to maintain state or allow people to share links and start exactly where they left off.
I am trying to extract this into a directive that updates the url on every change to one of those two fields. Additionally I want each controller to be able to access the dates as they are used as query parameters for other endpoints.
Here is my directive:
angular
.module('dateDemo')
.directive('DateFields',['$location', DateFields]);
function DateFields($location) {
return {
templateUrl: 'shared/partials/_dateFields.html',
scope: false,
bindToController: {
start_date : '=',
end_date : '='
},
controller: DateFieldCtrl,
controllerAs: 'dateCtrl'
};
function DateFieldCtrl() {
var dateCtrl = this;
dateCtrl.refreshUrl = refreshUrl;
function refreshUrl() {
var searchString = '';
searchString += '&start_dt=' + moment(dateCtrl.start_dt).format('YYYY-MM-DD');
searchString += '&end_dt=' + moment(dateCtrl.end_dt).format('YYYY-MM-DD');
$location.search(searchString);
}
}
}
My Partial:
<div class="col-md-3">
<label><i class="glyphicon glyphicon-calendar"></i> <b>Date</b></label>
<div class="form-group">
<input type="date" class="form-control input-sm" ng-model="vm.data.start_dt" ng-change="refreshURL()">
<p class="text-right"><small class="help-block">From Date</small></p>
</div>
</div>
<div class="col-md-3">
<label> </label>
<div class="form-group">
<input type="date" class="form-control input-sm" ng-model="vm.data.end_dt" ng-change="refreshURL()">
<p class="text-right"><small class="help-block">To Date</small></p>
</div>
What I'm attempting to do is bind the vm.data.end_dt and vm.data.start_dt variables so that the directive manages the updating, while the controllers have access to these values. I can't get the function in my directive to trigger, and I've tried different combinations of which variable goes in the partial to no avail. What am I doing wrong?

Since you are using controllerAs: 'dateCtrl', you need to change your ng-change code to call dateCtrl.refreshUrl()
Also, you have vm.data referenced in your ng-model, which I think should also be dateCtrl
<div class="col-md-3">
<label><i class="glyphicon glyphicon-calendar"></i> <b>Date</b></label>
<div class="form-group">
<input type="date" class="form-control input-sm" ng-model="dateCtrl.start_dt" ng-change="dateCtrl.refreshURL()">
<p class="text-right"><small class="help-block">From Date</small></p>
</div>
</div>
<div class="col-md-3">
<label> </label>
<div class="form-group">
<input type="date" class="form-control input-sm" ng-model="dateCtrl.end_dt" ng-change="dateCtrl.refreshURL()">
<p class="text-right"><small class="help-block">To Date</small></p>
</div>
</div>

I was able to fix it by including bindToController: true.

Related

Angularjs form scope bug

I have a Angularjs form with a list with names.
When I click on a name the form will change with here profile.
When I change one input and don't save it and I click on a other profile every thing change except the one input that has changed.
<form class="form-horizontal bordered-row" >
<div class="form-group">
<label class="col-sm-4 control-label">Naam</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="" value="{{gegevens.naam | capitalize}}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Categorie</label>
<div class="col-sm-6">
<select class="form-control">
<option ng-repeat="x in producten_categorie" value="{{x.value}}" ng-selected="gegevens.categorie == x.value">{{x.name}}</option>
</select>
</div>
</div>
<div class="form-group pad25L">
<button class="btn btn-info" ng-click="productAlgemeen_update(gegevens.naam);">Save</button>
</div>
</form>
And the change scope:
$scope.productGegevens = function(product){
$http.post("php/producten-locatie.php", {'id':product.id}).then(function(response){
$scope.producten_locatie = response.data.records[0];
$scope.gegevens = {
id:product.id,
naam:product.naam,
categorie:product.categorie,
straatnaam:$scope.producten_locatie.straatnaam,
huisnummer:$scope.producten_locatie.huisnummer,
postcode:$scope.producten_locatie.postcode,
stadsnaam:$scope.producten_locatie.stadsnaam
};
});
}
Please note that input data needs to bind with ng-model whereas you are entering your input with value tag. it means the value is rendered in html not in model, so when you click on other profile UI can not detect the change. Please use ng-model for input value not value tag of input box.

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

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>

One model not accessible, one is

I have two tiny projects, both use essentially the same setup, one returns a model value one returns the model value as undefined. At this point I am lost as to why these two examples are behaving differently. I checked the failing example, and the model correctly populates
Successful Behavior
Partial
<div class="row">
<div class="col-md-4 .col-md-offset-4 content-pane no-margin">
<h4>Input Your Inquiry</h4>
<form ng-submit="submit(invoice)">
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<span class="add-on">Invoice Number</span>
<input id="who" type="text" class="input-xlarge" ng-model="invoice.number">
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<span class="add-on">PO Number</span>
<input id="who" type="text" class="input-xlarge" ng-model="invoice.po">
</div>
</div>
</div>
<input type="submit">
</form>
</div>
<div ng-if="status" class="col-md-4 .col-md-offset-4 content-pane no-margin">
<h4>Invoice Status</h4>
<div>{{status}}</div>
</div>
</div>
App.js
app.config(function($routeProvider){
$routeProvider
.when('/',
{
templateUrl: 'app/partials/inquiry-input-form.html',
controller: 'MainCtrl'
})
.otherwise(
{
templateUrl: 'app/partials/404.html'
})
});
app.controller('MainCtrl', function($scope,formsResults) {
$scope.submit = function() {
var invoiceNum = $scope.invoice.number;
var po = $scope.invoice.po;
formsResults.getInvoiceStatus(po,invoiceNum).success(function(data) {
var status = data.records;
if (angular.isUndefined(status)) {
$scope.status = 'Pending Reciept or Entry';
} else {
$scope.status = status[0].field_124208[0];
}
});
};
});
app.factory('formsResults', function($http) {............
Returning Model as undefined in controller
Partial
<tabset>
<tab active=workspace.active>
<tab-heading>
Search All Fields
</tab-heading>
<div>
<br /><br />
<form class="form-horizontal" ng-submit="submitAll(searches)">
<div class="form-group">
<div class="col-md-4">
<input id="textinput" name="textinput" type="text" class="form-control input-md" ng-model="searches.term">
<span class="help-block">Enter the term you wish to search on.</span>
{{searches.term}}
</div>
</div>
<!-- Button -->
<div class="form-group">
<div class="col-md-4">
<button id="singlebutton" name="singlebutton" class="btn btn-primary" type="submit">Search</button>
</div>
</div>
</form>
</div>
</tab>
<tab>
<tab-heading>
Search By Field
</tab-heading>
<div>
<br /><br />
<form class="form-horizontal" ng-submit="submitSpecific(search)">
<div class="form-group">
<div class="col-md-4">
<select id="selectbasic" name="selectbasic" class="form-control">
<option value="4">Country</option>
</select>
<span class="help-block">Enter the field you wish to search on.</span>
</div>
</div>
<!-- Text input-->
<div class="form-group">
<div class="col-md-4">
<input id="textinput" name="textinput" type="text" class="form-control input-md">
<span class="help-block">Enter the term you wish to search on.</span>
</div>
</div>
<div class="form-group">
<div class="col-md-4">
<button id="singlebutton" name="singlebutton" class="btn btn-primary" type="submit">Search</button>
</div>
</div>
</form>
</div>
</tab>
</tabset>
App.js
var app = angular.module ('app',['ngRoute','ngSanitize','ui.bootstrap','dialogs.controllers','dialogs']);
app.config(function($routeProvider){
//http://docs.angularjs.org/tutorial/step_07
$routeProvider
.when('/',
{
templateUrl: 'app/partials/home_tpl.html',
controller: 'HomeCtrl'
})
.when('/search',
{
templateUrl: 'app/partials/search-form.html',
controller: 'SearchCtrl'
})
.when('/add-sop',
{
templateUrl: 'app/partials/support-process.html',
controller: 'EditSOPCtrl'
})
.otherwise(
{
redirectTo: '/'
})
});
app.controller('HomeCtrl', function($scope) {
});
app.controller('SearchCtrl', function($scope,formsSopSearchCriteria) {
$scope.submitAll = function() {
var searchTerm = $scope.searches.term;
console.log(searchTerm);
var searchPackage={
"offset":0,
"limit":0,
"fields":["record_id","dt_created","created_by","dt_updated","updated_by",149654,149655,149692],
"filters":{"and":[{"field_id":"149655","operator":"contains","value":searchTerm}]},
"sort":{}
};
searchPackage=JSON.stringify(searchPackage);
$scope.searchPackage = searchPackage;
formsSopSearchCriteria.getMatches(searchPackage).success(function(data) {
$scope.sopRecords = data.records;
});
};
$scope.submitSpecific = function() {
var searchPackage = ""
searchPackage=JSON.stringify(searchPackage);
$scope.searchPackage = searchPackage;
formsSopSearchCriteria.getMatches(searchPackage).success(function(data) {
$scope.sopRecords = data.records;
});
};
});
app.factory('formsSopSearchCriteria',function($http) {........
Any ideas? My understanding is that it as an issue of scope. But in the failing example, the partial I provide is mapped to the SearchCtrl in the routeProvider. As a result the model should be available in the controller, just like the example that is working. I suspect I must be missing something, but I am not clear what I am missing.
You need to create $scope.searches as an empty object on initialization:
app.controller('SearchCtrl', function($scope,formsSopSearchCriteria) {
$scope.searches = {};
Or for clarity:
$scope.searches =
{
term: ''
};
If you don't, it will not be created until the first input is entered (I believe it is the $parse service that the ng-model directive uses that creates the searches object in this case).
The problem is that the object might not be created in the scope you want it to.
The reason it works in the first case is because there are no child scopes, so the invoice object is created in the controller scope.
In the second case there is one or many child scopes (probably from the tabs, I haven't dug any deeper into it), so the searches object is created in one of those instead.
Due to how prototypical inheritance works in Javascript, if you predefine the objects in the controllers' scopes, the properties will be created correctly.

Resources