AngularJS evaluate $rootScope variable in directive template - angularjs

I have a directive that creates an input field.
I need to set the ng-model attribute of this input field to the value of a $rootScope
variable.
The reason behind this is that I want the input field to be in the layout, and bind to different models depending on what page is loaded.
I thought I'd set this global variable in each controller and access it in the Directive.
ATM the variable is hard coded
App.run(function($rootScope){
$rootScope.mymodel = 'search.name';
})
And the Directive
Directives.directive('inputFilter', function(){
return{
restrict: 'E',
replace:true,
controller: function($scope, $rootScope){
console.log($scope.mymodel);
console.log($rootScope.mymodel)
},
template: '<input class="filter" type="text" ng-model="mymodel" placeholder="Nach filtern">'
}
});
It gets rendered as
<input class="filter ng-pristine ng-valid" type="text" ng-model="mymodel" placeholder="Filter">
and the text inside the input field is the value of mymodel variable. The console.log shows
search.name
search.name
Could anyone please shed some light on this issue?

What I think you want is
template: '<input class="filter" type="text" ng-model="'
+ $rootScope.mymodel + '" placeholder="Nach filtern">'
Fiddle.
Note that you will need to inject $rootScope into your directive:
Directives.directive('inputFilter', function($rootScope) {

Related

Data binding with text input doesn't work when using `ng-if` in a directive

I created an Angular directive (see plunkr) as follows:
JS:
angular.module('plunker', []);
angular.module('plunker').controller('MainCtrl', function($scope) {
$scope.myInputs = {
email: "test#test.org"
}
$scope.logToConsole = function() {
console.log($scope.myInputs.email);
}
});
angular.module('plunker').directive('myEmail', function(){
return {
restrict: 'E',
scope: {
myngmodel: '='
},
template: '<input type="email" ng-if="true" ng-model="myngmodel" />'
};
});
It's called from HTML like this:
<body ng-controller="MainCtrl">
<my-email myngmodel="myInputs.email"></my-email>
<input type="button" value="log to console!" ng-click="logToConsole()">
</body>
The issue is as follows:
When I don't put ng-if="true", in the template's textinput, or I use ng-show instead, the binding works correctly.
But when ng-if="true" is present, the binding no longer works; when I edit the field and click button, the old value is always is written to console.
Is it a bug or works as designed?
Is the issue due to ng-model="myngmodel" not following "the dot rule"?
If so, how could I rewrite the directive so that I can pass the data model entry from outside world in the same spirit?
ng-if creates a child scope - that's the reason why it happens. Here the quote from the documentation:
The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope.
So, when you modify the value in your input box - the new value is written to the child scope that inherits the directive's isolated scope:
<my-email myngmodel="myInputs.email" class="ng-isolate-scope">
<input type="email" ng-if="true" ng-model="myngmodel" class="ng-scope">
</my-email>
The MainCtrl scope var is not updated because it is bound to the directive's isolated scope var - not to its child scope.
The workaround is to use the dot notation - to make sure ng-model reads from and writes to the parent isolated scope of the directive:
directive's template:
<input type="email" ng-if="true" ng-model="myngmodel.email" />
binding to the outer scope:
<my-email myngmodel="myInputs"></my-email>

Delayed bind on ngModel

I am trying to create a typeahead directive that does not bind the typed text to the model while typing.
This is as such no problem, but I would like to use the ngModel directive for my binding so I am able to use something similar to
<input type="text" ng-model="model.field" typeahead="sourceForTypeahead" />
instead of my current approach which works as a charm
<input type="text" ng-model="tmpFieldForInput" typeahead="sourceForTypeahead" typeahead-model="model.field" />
I can't figure if it is possible to change the "target" of ng-model internally in the directive so I get the typed input, and then is able to set the external model when an result from the source is selected.
Use ngModelOptions to specify when you'd like to bind the input text to the model:
<input type="text" ng-model="myModel" ng-model-options="{ updateOn: 'blur' }">
<p>Hello {{myModel}}!</p>
There are different events you can trigger on, but in this case, the text will only be bound to the model once the end-user leaves focus from the field.
Additional resources: https://docs.angularjs.org/api/ng/directive/ngModelOptions
Like #lux has mentioned, the right way to go about it is to use ng-model-options
But in your case, the ideal solution would be to do wrap your input in a form and bind on submit:
<form name="myForm">
<input type="text" ng-model="myModel" ng-model-options="{ updateOn: 'submit' }">
<p>Hello {{myModel}}!</p>
<input type="submit" value="Submit">
</form>
This will bind the value to the model only when you click your Submit button. This of course can be put anywhere you please.
I found a solution after looking into an old version of the checkbox-list module.
The solution is to change the ngModel attribute on compile time and make it point to an internal property in the directive and then compile in the postlink method.
I have updated the plunker for others to see the prototype: http://plnkr.co/edit/LbHH2pJGX1Iii8ZqqSie?p=preview
(Stack requires me to post code - so here is the )
app.directive('typeahead', ['$parse', '$compile', function ($parse, $compile) {
return {
priority: 1000,
terminal: true,
scope: {
source: '=typeahead',
ngModel: '='
},
compile: function(tElement, tAttrs) {
tElement.removeAttr("typeahead");
tElement.attr("ng-model", "searchTerm");
return function(scope, element, attrs) {
$compile(element)(scope);
// all my logic here

Passing value to ng-model in custom directive

Right, I'm probably missing something fairly obvious here but here goes. I have a bit of HTML from bootstrap that I want to reuse so I wanted to make it into a custom directive.
<label class="toggle">
<input ng-model='model' type="checkbox" class="toggleInput">
<div class="track">
<div ng-show="test" class="toggle-label on">
{{onText}}
</div>
<div ng-show="!test" class="toggle-label off">
{{offText}}
</div>
<div class="handle"></div>
</div></label>
snippet of html I want to use in my pages that Angular will recompile into the above:
<toggle on-text="On" off-text="Off" ng-model="myModelName"></toggle>
My directive is as follows:
.directive('toggle', function() {
return {
restrict: 'AE',
templateUrl: 'views/toggle.view.html',
replace: true,
scope: {
onText: '#',
offText: '#',
ngModel : '=',
},
};
});
However, when looking at the html markup the ng-model attribute has not changed to 'myModelName' and still shows just 'modal' so hasn't updated.
What am I doing wrong?
Thanks all
Try replacing
<input ng-model='model' type="checkbox" class="toggleInput">
with
<input ng-model='{{ngModel}}' type="checkbox" class="toggleInput">
because you want to bind the value of the incoming model name into the ng-model attribute of the template inside the directive.
Figured it out.... used compile in the link function to dynamically add the model to the elements I needed:
return $compile($('input.toggleInput',element).attr('ng-model', model))(scope);

Angular.js - ng-change not firing when ng-pattern is $invalid

I am using ng-pattern to validate some form fields, and I am using ng-change with it to watch and process any changes, however ng-change (or $scope.$watch) will only fire when the form element is in the $valid state! I'm new to angular, so I don't know how to solve this issue, although I suspect a new directive is the way to go.
How can I get ng-change to fire in both $invalid and $valid form element states, with ng-pattern still setting the form element states as before?
Html:
<div ng-app="test">
<div ng-controller="controller">
<form name="form">
<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" ng-change="change()" ng-model="inputtext"> Changes: {{ changes }}
</form>
<br>
Type in any amount of numbers, and changes should increment.
<br><br>
Now enter anything that isn't a number, and changes will stop incrementing. When the form is in the $invalid state, ng-change doesn't fire.
<br><br>
Now remove all characters that aren't numbers. It will increment like normal again. When the form is in the $valid state, ng-change will fire.
<br><br>
I would like ng-change to fire even when the the form is $invalid.
<br><br>
form.$valid: <font color="red">{{ form.$valid }}</font>
</div>
</div>
Javascript:
angular.module('test', []).controller('controller', function ($scope) {
$scope.changes = 0;
$scope.change = function () {
$scope.changes += 1;
};
});
I have created a working JS Fiddle which shows the problem I am having.
http://jsfiddle.net/JAN3x/1/
By the way, this angular issue also seems to be relevant:
https://github.com/angular/angular.js/issues/1296
You can change the behavior of your input by using ng-model-options.
Just add this attribute to your input and the ng-change event will fire:
ng-model-options="{allowInvalid: true}"
see: https://docs.angularjs.org/api/ng/directive/ngModelOptions
you just need to add
ng-model-options="{ updateOn: 'default' , allowInvalid:'true'}"
this indicates that the model can be set with values that did not validate correctly instead of the default behaviour.
Edit This was answered when ng-model-options was not available. Please see the top-voted answer.
you can write a simple directive to listen input event.
HTML:
<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" watch-change="change()" ng-model="inputtext"> Changes: {{ changes }}
JS:
app.directive('watchChange', function() {
return {
scope: {
onchange: '&watchChange'
},
link: function(scope, element, attrs) {
element.on('input', function() {
scope.$apply(function () {
scope.onchange();
});
});
}
};
});
http://jsfiddle.net/H2EAB/
Inspired by the Li Yin Kong ingenious solution :
His solution has an issue concerning the ndModel update (see the comments of his post).
My fix essentially changes the scope type of the directive. It lets directive access to controller scope (and methods)
Then, watch-change directive does not need an "instruction to eval" (change()) anymore, but only the "name of the controller method to call" (change).
And to get the new value of the input in this function, I pass the context (this = the input itself). So I can get the value or any property of it.
This way, we don't care about ngModel updates (or if the form is invalid, which was another issue of the initial solution : ngModel is deleted if form is invalid)
HTML :
<input type="text" name="textbox" ng-pattern="/^[0-9]+$/" watch-change="change" ng-model="inputtext">
JAVASCRIPT :
app.directive('watchChange', function() {
return {
restrict : 'A',
link: function(scope, element, attrs) {
element.on('input', function(){
scope[attrs.watchChange](this);
})
}
};
});
DEMO : http://jsfiddle.net/msieurtoph/0Ld5p2t4/

How can I add a class to an input if the input is changed with AngularJS?

I coded the following in my form:
<td><input type="text" ng-model="row.title" /></td>
When I look at my DOM with Chrome developer tools I see the following:
<input type="text" ng-model="row.title" class="ng-pristine ng-valid">
How can I make it so that when there is a change made to the input that the input has a class added to it?
There are two good ways to approach this problem:
1. Use the built-in ng-dirty class that Angular puts on the element.
When you change an input managed by Angular, it adds some CSS classes to the input for various states. These include:
ng-pristine - the input has not been modified
ng-dirty - the input has been modified
So, if you can modify your CSS to be based off the .ng-dirty class, you're good to go.
2. Use a form directive with the $dirty flag.
When you use a form element, Angular assigns a FormController instance on the scope with the same name as the name attribute on the form; each input inside the form gets attached to that FormController instance as a property, again with the same name as the name attribute on the input. For example,
<form name="myForm">
<input type="text" name="myInput">
</form>
gives you
$scope.myForm.myInput
Each input property has some of its own properties on it, including $pristine and $dirty; these work just like the CSS classes listed above. Thus, you can check for the $dirty flag on the input and use ng-class to conditionally apply a class to the element. An example:
<div ng-controller="MainController">
<form name="myForm">
<input name="myInput" ng-model="model" ng-maxlength="3"
ng-class="{changed: myForm.myInput.$dirty}">
</form>
</div>
You can find a working example here: http://jsfiddle.net/BinaryMuse/BDB5b/
Take a look at this jsfiddle: http://jsfiddle.net/hNrEV/2/
The main idea is using $scope.$watch to watch for changes to the input box. I gave it an id of rowTitle, and used a directive called watchRowTitle that watches for changes to $scope.row.title, and adds a class 'red' that colors the text red whenever the text in the input box is equal to 'wrong title'.
It is probably good practice to do DOM manipulation in directives. Here, the watchRowTitle directive returns an object with 4 keys:
template - the html that replaces the watch-row-title tag. we dont need this here
scope - Here we make use of an isolated scope. Basically, the '=' establishes a 2-way data binding between between scope.title inside the watch-row-title directive and the $scope.row.title value inside the MyCtrl controller.
restrict - We give it a value of E, which stands for element. So this restricts the use of the watch-row-title directive within html tags, in other words: <watch-row-title></watch-row-title>
link - this is the link function, where the interesting stuff happens. In here, we use scope.$watch on title. We have to supply a function with 2 parameters newValue and oldValue (you can name them to something else, but naming them this way is more meaningful), that holds the new and old values of the variable being watched. Whenever the scope.title variable becomes the string 'wrong title', it adds the CSS class 'red' to the input box with id rowTitle (notice how the text in the input box turns red). Otherwise, it removes that CSS class. This portion is done using JQuery.
HTML:
<div ng-app="myApp" ng-controller="MyCtrl">
<input id="rowTitle" type="text" ng-model="row.title" class="ng-pristine ng-valid" />
<watch-row-title title="row.title"></watch-row-title>
</div>
CSS:
.red {
color: red;
}
JavaScript:
angular.module('myApp', [])
.controller('MyCtrl', [
'$scope',
function ($scope) {
$scope.row = {};
}
])
.directive('watchRowTitle', [
function () {
return {
template: '',
scope: {
title: '='
},
restrict: 'E',
link: function(scope, element, attr) {
scope.$watch('title', function(newValue, oldValue) {
if (newValue === 'wrong title') {
$('#rowTitle').addClass('red');
} else {
$('#rowTitle').removeClass('red');
}
});
}
};
}
]);
HTML
<input type="text" id="inputTitle" ng-model="row.title" />
JS
$scope.$watch('row.title', function(newValue) {
// Add CSS class on input
$('#inputTitle').addClass('YourCSSClass');
}, true);

Resources