I'm using an input field value attribute to show the query that the has user entered on the SERP of my app. I want to hide the value that was queried on pages where the result is undefined. Is it possible to use ng-show to just hide an attribute and not the entire element?
Jade
div(ng-controller="SearchCtrl", class="header-search-form")
form(id="search-form-homepage" class="input-group input-lg")
input(type="search",
ng-enter="doSearch(query.term)",
ng-hide="query.term === 'undefined'",
ng-model="query.term",
class="form-control header-search-result",
value="#{data.query}")
You will have to create your own directive for this task. As an example - plnkr, I have created a directive that will remove the placeholder attribute.
Note the key difference here is that we can't "hide" attributes since hide is really
display: 0;
we can just remove or add them.
app.directive('attrHide', function(){
return {
restrict: 'A',
scope: {
attrHide: '='
},
link: function(scope, elm, attr){
var targetAttr = attr.hiddenAttribute;
var saveAttr = attr[targetAttr] || '';
scope.$watch('attrHide', function(newVal){
if (newVal){
elm.removeAttr(targetAttr);
} else {
elm.attr(targetAttr,saveAttr);
}
})
}
}
})
In the markup you can have
<input ng-model="target"/>
<input placeholder="hello" hidden-attribute="placeholder" attr-hide="target=='hello'"/>
Here in the attr-hide, you can put the true false expression and in hidden-attribute you can choose the attribute to remove
Related
I have a text input field whose placeholder I want to change every few seconds. However I don't want to pollute my controller with this so I want to encapsulate this functionality into a directive.
This is what my directive looks like:
myApp.directive('searchBox', ['$interval', function($interval) {
return {
restrict: 'A',
link(scope, element, attrs) {
$interval(function() {
attrs.placeholder = 'New';
}, 1000);
}
}
}])
And the html:
<input type="text" class="form-control" placeholder="Old" ng-model="search" search-box>
However the placeholder stubbornly doesn't change, even though in the console attrs.placeholder can be seen to change to 'Test' from 'Hello'. Any ideas?
PLUNKR: https://plnkr.co/edit/Oy1M8FPTXxzB9oYMJqlx?p=preview
You cannot change attributes values via the attr object (it's just a static reflection of your element attributes). Instead, update your element using element.attr('placeholder', 'Test') or attrs.$set('placeholder', 'Test').
I am building an Angular module that will allow a form to be built dynamically.
As elements are selected, HTML is added to a model. The model is attached to an ng-repeat element.
<div ng-repeat="item in list1 track by $index">
<div compiledom="item.content"></div>
</div>
So an item in the model might look like this:
{
'title': 'Full-Column Text',
'drag': true,
'inputValue': '',
'content': '<div class="small-12 columns"><input type="text" dynamic-model="$index" placeholder="Full Width Text" /></div>'
}
I am using a custom directive to compile the HTML fed to the model.
.directive('compiledom', function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compiledom);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
}
})
And using a second directive to bind the model data to the input field in that HTML.
.directive('dynamicModel', function($compile) {
return function(scope, element, attrs) {
scope.$watch(attrs.dynamicModel, function(dynamicModel) {
if (attrs.ngModel || attrs.ngModel == dynamicModel || !dynamicModel) return;
element.attr('ng-model', 'item.inputValue'); <---------- bound here
if (dynamicModel == '') element.removeAttr('ng-model');
element.unbind();
$compile(element)(scope);
});
}
})
My issue is that whatever I put into an input field gets placed to every input element. For some reason, it appears that a single item.inputValue is getting reflected to every item of the same type. The model is bound, but I have broken something in ng-repeat that keeps it in isolation.
For example, if I have two 'Full-Column Text' inputs, if one is set to 'ABC', both are set to 'ABC'. If I also were to have 2 'Half-Column Text' inputs, they would remain unset until I set one of them to 'DCE' - then they are both set to 'DCE'.
A link to a demo/example of the issue will be shared soon.
As it turned out, my directives were fine.
When I was adding to my model, I was using .slice. This was causing a reflection issue. Using angular.copy made a geniune clone, allowing the isolation I was looking for.
$scope.list1[x] = angular.copy($scope.list5[x]);
I have a form with few fields, however a select and an input field are coupled: the validation on the input depends on which value the user chooses in the select field.
I'll try to clarify with an example. Let's say that the select contains names of planets:
<select id="planet" class="form-control" name="planet" ng-model="planet" ng-options="c.val as c.label for c in planets"></select>
in the input I apply custom validation via a custom directive named "input-validation":
<input id="city" input-validation iv-allow-if="planet==='earth'" class="form-control" name="city" ng-model="city" required>
where this is the directive:
.directive('inputValidation', [function() {
return {
require: 'ngModel',
restrict: 'A',
scope: {
ivAllowIf: '='
},
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
//input is allowed if the attribute is not present or the expression evaluates to true
var inputAllowed = attrs.ivAllowIf === undefined || scope.$parent.$eval(attrs.ivAllowIf);
if (inputAllowed) {
ctrl.$setValidity('iv', true);
return viewValue;
} else {
ctrl.$setValidity('iv', false);
return undefined;
}
});
}
};
}])
The full example can be examined in Plnkr: http://plnkr.co/edit/t2xMPy1ehVFA5KNEDfrf?p=preview
Whenever the select is modified, I need the input to be verified again. This is not happening in my code. What am I doing wrong?
I have done the same thing for validation of start-date on change of end-date. In the directive of start-date add watch for change of end-date and then call ngModel.$validate() in case end-date new value is defined.
scope.$watch(function () {
return $parse(attrs.endDate)(scope);
}, function () {
ngModel.$validate();
});
The important part to take is call to ngModel.$validate() inside the directive.
Note
you should use $validators for custom validations above to work. read here, $parsers is the old way - from angularjs 1.3 use $validators
FIXED PLUNKER LINK
I was simply trying to add functionality to an existing directive so I can track any changes that occur. I have a toggle control which is just used to toggle a boolean value. Here is my directive
app.directive('skToggle', function ($timeout) {
return {
replace: true,
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '=',
skcallback: '&callback',
disabled: '=',
emit: '#',
positive: '#',
negative: '#',
skTouched:'=?' //THIS IS NEW
},
template: '<div class="toggle" ng-class="{ disabled: disabled }" ng-click="disabled || toggle($event)">\
<div class="off">\
<span>{{neg}}</span>\
</div>\
<div class="on" ng-class="{ active: ngModel }">\
<span>{{pos}}</span>\
<div class="control"></div>\
</div>\
</div>',
link: function (scope, elem, attrs, ctrl) {
var hasCallback = angular.isDefined(attrs.callback);
scope.pos = scope.positive || "YES"
scope.neg = scope.negative || "NO";
scope.toggle = function (e) {
if (hasCallback) {
scope.skcallback({ event: e });
} else {
ctrl.$setViewValue(!ctrl.$viewValue);
}
if (scope.emit === 'true') {
$timeout(function () {
scope.$emit('toggle');
});
}
}
// THIS IS ALSO NEW
scope.$watch('ngModel', function(newVal, oldVal){
if(scope.skTouched && oldVal !== undefined && newVal !== oldVal){
scope.skTouched.UI.$dirty = true;
}
});
}
}
});
I have commented on which parts of the directive are new.. all I did was add a two-way binding on my directive that takes an object and updates the UI.$dirty property on it. The problem is when I print out the object on the screen, the UI object never gets updated on the parent $scope. I don't know if I'm just spacing on something easy or if I am doing something wrong but, my directive (child) scope is not updating the parent scope like it should be.
<div sk-toggle ng-model="feature.enabled" sk-touched="feature"></div>
So I realized the problem was with using $dirty as my variable. Angular must reserve $dirty for only use with form controllers, which explains why it wasn't showing up when I printed the entire object out on the page. Simply changing the variable to dirty made it work as expected
ngModel is a core angular directive. It may be that there is a conflict with your using ngModel as an attribute to use two-way databinding on.
You may be able to get around this by setting the priority of your directive to be higher than that of ngModel. Add a priority of something > 1 in your directive definition. See the docs for more info on priority.
I am attempting to create an angular directive that will be a custom tag for input fields in our application. Essentially what it will do is create the label, input field and the various bootstrap classes so there is a consistent look to them.
Along with that I would like it if I could add the various data validators that are appropriate for the particular input (such as required and custom validators) as attributes of the custom tag and then have those added to the input field and thus perform validation on that.
I have figured out a way that appears to put the attributes on the input field and the custom validator is getting called and properly evaluating the data, but the form never seems to think that the data is invalid. I think I am having a scope problem where the input being invalid is being set on the directive's scope rather than the parent scope but I'm not 100% sure about that and even if it is the problem I don't know how to fix it.
Here's a sample of what I'd like one of the tags to look like
<textinput ng-model="TestValue" name="TestValue" text="Label Text" config="GetConfigurationForm()" ngx-ip-address required></textinput>
which I want to generate something like
<div class="row">
<div class="form-group" ng-class="{ 'has-error': IsInvalid() }">
<label for="{{name}}" class="control-label">{{text}}</label>
<input id="{{name}}" type="text" class="form-control" ng-model="ngModel" name="{{name}}" ngx-ip-address required>
</div>
</div>
Note that the ngx-ip-address and required have been moved to the input field attributes.
My controller looks like the following (sorry it's so long)
var app = angular.module('test', []);
app.directive('ngxIpAddress', function()
{
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attributes, ngModel)
{
ngModel.$validators.ngxIpAddress = function(modelValue, viewValue)
{
// Value being blank is OK
if (ngModel.$isEmpty(modelValue))
return true;
// If the string starts with a character then
// this is not valid
if (isNaN(parseInt(viewValue[0])))
return false;
var blocks = viewValue.split(".");
if(blocks.length === 4)
{
return blocks.every(function(block)
{
return parseInt(block, 10) >= 0 && parseInt(block, 10) <= 255;
});
}
return false;
};
}
};
});
app.directive('textinput', function ()
{
return {
restrict: 'E',
scope: {
//# reads the attribute value, = provides two-way binding, & works with functions
ngModel: '=',
name: '#',
text: '#',
config: '&'
},
controller: function($scope) {
$scope.IsInvalid = function()
{
var getConfigurationFunction = $scope.config();
if (!getConfigurationFunction || !getConfigurationFunction[$scope.name])
return false;
return getConfigurationFunction[$scope.name].$invalid;
};
},
link: function(scope, element, attributes) {
var inputElement = element.find("input");
for (var attribute in attributes.$attr)
{
if (attribute !== "ngModel"
&& attribute !== "name"
&& attribute !== "text"
&& attribute !== "config")
{
inputElement.attr(attribute, attributes[attribute]);
}
}
},
template: '<div class="row">' +
'<div class="form-group" ng-class="{ \'has-error\': IsInvalid() }">' +
'<label for="{{name}}" class="control-label">{{text}}</label>' +
'<input id="{{name}}" type="text" class="form-control" ng-model="ngModel" name="{{name}}">' +
'</div>' +
'</div>'
};
});
app.controller(
"TestController",
[
"$scope",
function TestController(_scope)
{
_scope.TestValue = "TestTest";
_scope.GetConfigurationForm = function()
{
return _scope.ConfigurationForm;
};
}
]
);
If I put the attributes in the actual template then everything works as expected and the control turns red if the data isn't an ip address. When I add the attributes by moving them that doesn't work.
Here is a plunkr showing what I've got so far: http://plnkr.co/edit/EXkz4jmRif1KY0MdIpiR
Here is a plunkr showing what I'd like the end result to look like where I've added the tags to the template rather than the tag: http://plnkr.co/edit/mUGPcl1EzlHUiMrwshCr
To make this even more fun, in the future I will actually need to pass in a value to the data validation directives from the outside scope as well, but I'd like to get this working first.
Here you may find the correct answer.
The reasons of this issue are:
the attr will convert the attribute from ngxIpAddress to ngxipaddress, namely from the uppercase to lowercase, you can find this issue from this link. To solve it, just pass ngx-ip-address as parameter for function attr.
$compile(inputElement)(scope); need to be added into directive, when one directive is used in another directive. Here is one link.