how to pass a json as a string param to a directive - angularjs

When I try to eval the below json form it gives me an error -
eval("{form: 'form' , nameToMatch: 'password1'}")
Why is the above form not valid ?
However the below works fine -
eval("{form: 'form'}")
I am trying to pass the above json as a string, as a param input to a directive.
Below is the html -
<input type="password" name="password2" ng-model="user.confirmPassword" placeholder="Confirm Password" match="{form: 'form', nameToMatch: 'password1'}" required="required"/>
Thanks,
Murtaza

Put parens around your json:
eval("({form: 'form' , nameToMatch: 'password1'})")
Doesn't seem like an angular question though. Not sure what you're trying to do:
Anyhow, to pass the json to the directive there are lots of ways to do that. I'm not sure why you'd want to do that and not just pass an object though.
passing json can be done a lot of ways...
From your attributes object:
app.directive('foo', function () {
return function(scope, element, attrs) {
var obj = eval('(' + attrs.foo + ')');
};
});
where
<div foo="{'test':'wee'}"></div>
From an isolated scope:
app.directive('foo', function () {
return {
restrict: 'E',
scope: {
'jsonIn' : '#'
},
link: function(scope, element, attrs) {
var obj = eval('(' + scope.jsonIn + ')');
};
};
});
where
<foo json-in="{'test':'wee'}"></foo>
But it's by far better to avoid using the native eval at all costs, if you can. Which in almost all cases you can. If you have some data just put it in an object on a scoped parameter and pass it in either via a two-way property on an isolated scope, or by name and do an angular $eval on it.
EDIT: The pass an object in...
You could use two way binding on an isolated scope:
app.directive('foo', function (){
return {
restrict: 'E',
scope: {
'data' : '='
},
link: function(scope, elem, attrs) {
console.log(scope.data);
}
};
});
where
<foo data="{ test: 'wee' }"></foo>
The really cool thing about doing it this way, is if you're using a scoped property it will update bi-directionally:
app.controller('MainCtrl', function($scope) {
$scope.bar = { id: 123, name: 'Bob' };
});
where
<foo data="bar"></foo>
I hope that helps.

It looks like you are trying to confirm a password in a form. There are many ways that you can go about this without resorting to JSON to pass values around in AngularJS. The most useful resource I've found online is from this Google Group thread:
1) http://jsfiddle.net/pkozlowski_opensource/GcxuT/23/ will compare
value in a second field with model value of the first field
2)
http://jsfiddle.net/S8TYF/ will compare value in a second field with
input value of the first field
The difference might be subtle but has practical consequences: with
(2) the confirm validation will kick-in as soon as you start typing
anything in the first field. With (1) the confirm validation will
kick-in only after the first field is valid. In the e-mail confirm
example it means that you won't start showing confirmation errors till
e-mail validation errors are sorted out (so a user can focus on one
error at the time).
Source: https://groups.google.com/d/msg/angular/R4QeNsNksdY/migbplv8GxIJ
From the first link, the directive is used as follows:
<label>e-mail</label>
<input name="email" type="email" required ng-model="email">
<label>repeat e-mail</label>
<input name="emailRepeat" type="email" required
ng-model="emailRepeat"
ui-validate-equals="email">
Where the ui-validate-equals directive points to the email model that was defined in the first input.
There are some StackOverflow answers to this if you would like to look there as well for additional ideas to solve your problem.

See also. #Blesh has mentioned in passing, but not emphasized: Angular provides you with a 'safe'-ish version of eval(), which works perfectly for passing declarative objects/arrays into your directive if you don't want to declare them on your scope, and wisely don't want to use native eval(). In the OP's example, just use this inside the directive:
angular.$eval(attrs.match);

Doesn't seem like an angular question though.
Agreed - vanilla JS can handle this just fine.
// Markup (HTML)
<div mydirective='{"test1": "foo", "test2": "bar"}'></div>
// App (JS)
JSON.parse(attrs['mydirective']);

Use JSON.parse(string) in angular. be sure that your parameter is in string format.

Related

Pass a dynamic listener to ng-change

I want my directive to support ng-change events but instead of defining a particular function to call when ng-change is triggered, I want to enable ng-change to take any sort of function with single or multiple parameters and execute it. I could think of of two ways but dont know which is right or which will work. it goes something like this:
Method 1:
HTML:
<currency-input width="200px" id="iextz" ng-model="currency1" ng-change="someFunction(argument1,argument2,.....)" currency="$" decimals="2"></currency-input>
JS:
numericApp.directive('currencyInput',........
...................................
return {
restrict: 'E',
scope:{
ngModel : "=",
id : "#"
},
link: function($scope, $elm, $attrs) {
........................................
*//some logic to execute the function passed to ng-change*
executeNgChangeFucntion(){
...........
...........
};
}]);
Method 2
HTML:
<currency-input width="200px" id="iextz" ng-model="currency1" ng-change="change(someFunctionWithArguments)" currency="$" decimals="2"></currency-input>
JS:
numericApp.directive('currencyInput',........
...................................
return {
restrict: 'E',
scope:{
ngModel : "=",
id : "#"
},
link: function($scope, $elm, $attrs) {
........................................
//ng-change handler
$scope.change = function(someFunctionWithArguments){
//execute someFuncitonWithArguments
..........................
};
}]);
I'm not really experienced in advanced concepts of AngularJS, so whatever I could think of I put it out here. It would be a great help if someone could provide some inputs. Thanks in advance!
Actually I'm building a numeric field component which would take in numbers and format it according to the set currency, set decimal digits etc., that sort of stuff, anyway the important part is that it's for someone else to use(let's say a client). Folks can use this component in their application and they need to process whatever input comes into the field with ng-change. They have their own event handlers. It could be anything. So I need to provide a way so that they can pass one of their functions to be executed whenever the input changes(as in what happens with n-change). For ex.:
<decimal-input ng-model="employees.calculatedChangeAmounts[job.id][jobChangeReasonCodes[$index]].changeAmount" decimals="2" ng-change="recalculate.salaryAmount($index, job)"></decimal-input>
I hope that helps in clearing any doubts. Now please help in solving mine, thanks!
If the only purpose of your directive is to format the input, you might be better off using one of the existing masked input directives. For example: https://github.com/angular-ui/ui-mask.
If you decide that you definitely need to create a new directive, you probably want to implement it using the NgModelController. This is the standard way to implement full support for two-way binding and validation in a directive. In addition to custom binding, ng-change works directly with ng-model to receive change notifications.
The user of your directive will bind to a model in their scope using ng-model, then process the changed value of their model using ng-change. For example:
<currency-input ng-model="item.currencyValue" ng-change="process(item.currencyValue)">
</currency-input>
(Remember to always have a dot in your ng-model bindings.)
If the directive is input, then you can consider this (or your 1st approach by defining someFunction(arg...) in your controller).
numericApp.directive('currencyInput',........
...................................
return {
restrict: 'E',
scope:{
ngModel : "=",
id : "#"
},
link: function($scope, $elm, $attrs)
{
$elm.bind("change",function(evt){
alert("Value changed");
});
}]);
I am not sure if i understood the first method, but the second one seems close to how ng-changed "should" be used.
My HTML
<p>My phone is {{phoneNumber}} !</p>
<p>Increment the phone number below:</p>
<input type="checkbox" ng-model="checkBoxData" ng-change="changeNumber(myShitVar)" />
My controller
app.controller('MainCtrl', ['$scope', function($scope) {
$scope.phoneNumber = 23323123;
$scope.changeNumber = function(myShitVar) {
if (!myShitVar) {
$scope.phoneNumber++;
}
};
If you want to add a variable amount of arguments to the function just pass them. You can pass how many arguments you want to a function, if some values are not defined they will be passed as undefined.
Plunker link

Passing a model to a custom directive - clearing a text input

What I'm trying to achieve is relatively simple, but I've been going round in circles with this for too long, and now it's time to seek help.
Basically, I have created a directive that is comprised of a text input and a link to clear it.
I pass in the id via an attribute which works in fine, but I cannot seem to work out how to pass the model in to clear it when the reset link is clicked.
Here is what I have so far:
In my view:
<text-input-with-reset input-id="the-relevant-id" input-model="the.relevant.model"/>
My directive:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
template: '<div class="text-input-with-reset">' +
'<input ng-model="inputModel" id="input-id" type="text" class="form-control">' +
'<a href class="btn-reset"><span aria-hidden="true">×</span></a>' +
'</div>',
link: function(scope, elem, attrs) {
// set ID of input for clickable labels (works)
elem.find('input').attr('id', attrs.inputId);
// Reset model and clear text field (not working)
elem.find('a').bind('click', function() {
scope[attrs.inputModel] = '';
});
}
};
});
I'm obviously missing something fundamental - any help would be greatly appreciated.
You should call scope.$apply() after resetting inputModel in your function where you reset the value.
elem.find('a').bind('click', function() {
scope.inputModel = '';
scope.$apply();
});
Please, read about scope in AngularJS here.
$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.
I've also added declaring of your inputModel attribute in scope of your directive.
scope: {
inputModel: "="
}
See demo on plunker.
But if you can use ng-click in your template - use it, it's much better.
OK, I seem to have fixed it by making use of the directive scope and using ng-click in the template:
My view:
<text-input-with-reset input-id="the-relevant-id" input-model="the.relevant.model"/>
My directive:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
scope: {
inputModel: '='
},
template: '<div class="text-input-with-reset">' +
'<input ng-model="inputModel" id="input-id" type="text" class="form-control">' +
'<a href ng-click="inputModel = \'\'" class="btn-reset"><span aria-hidden="true">×</span></a>' +
'</div>',
link: function(scope, elem, attrs) {
elem.find('input').attr('id', attrs.inputId);
};
});
It looks like you've already answered your question, but I'll leave my answer here for further explanations in case someone else lands on the same problem.
In its current state, there are two things wrong with your directive:
The click handler will trigger outside of Angular's digest cycle. Basically, even if you manage to clear the model's value, Angular won't know about it. You can wrap your logic in a scope.$apply() call to fix this, but it's not the correct solution in this case - keep reading.
Accessing the scope via scope[attrs.inputModel] would evaluate to something like scope['the.relevant.model']. Obviously, the name of your model is not literally the.relevant.model, as the dots typically imply nesting instead of being a literal part of the name. You need a different way of referencing the model.
You should use an isolate scope (see here and here) for a directive like this. Basically, you'd modify your directive to look like this:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
template: [...],
// define an isolate scope for the directive, passing in these scope variables
scope: {
// scope.inputId = input-id attribute on directive
inputId: '=inputId',
// scope.inputModel = input-model attribute on directive
inputModel: '=inputModel'
},
link: function(scope, elem, attrs) {
// set ID of input for clickable labels (works)
elem.find('input').attr('id', scope.inputId);
// Reset model and clear text field (not working)
elem.find('a').bind('click', function() {
scope.inputModel = '';
});
}
};
});
Notice that when you define an isolate scope, the directive gets its own scope with the requested variables. This means that you can simply use scope.inputId and scope.inputModel within the directive, instead of trying to reference them in a roundabout way.
This is untested, but it should pretty much work (you'll need to use the scope.$apply() fix I mentioned before). You might want to test the inputId binding, as you might need to pass it a literal string now (e.g. put 'input-id' in the attribute to specify that it is a literal string, instead of input-id which would imply there is an input-id variable in the scope).
After you get your directive to work, let's try to make it work even more in "the Angular way." Now that you have an isolate scope in your directive, there is no need to implement custom logic in the link function. Whenever your link function has a .click() or a .attr(), there is probably a better way of writing it.
In this case, you can simplify your directive by using more built-in Angular logic instead of manually modifying the DOM in the link() function:
<div class="text-input-with-reset">
<input ng-model="inputModel" id="{{ inputId }}" type="text" class="form-control">
<span aria-hidden="true">×</span>
</div>
Now, all your link() function (or, better yet, your directive's controller) needs to do is define a reset() function on the scope. Everything else will automatically just work!

AngularJS - setting focus to element using NON-ISOLATE directive

I know this question has been asked about 100 times (trust me, I've read them all), but I'm having trouble getting focus to go to an input box when the directive does NOT use isolate scope. The scope.$watch doesn't fire when the backing data changes.
Why not just use the one with isolate scope, you ask? Well, my understanding is that you should ONLY use isolate scope if your directive has a template.
The only differences in the directives is:
// works
app.directive('doesFocus', function ($timeout) {
return {
scope: { trigger: '#doesFocus' },
link: function (scope, element) {
scope.$watch('trigger', function (value) {
// sets focus
}
...
// does not work, and in fact when I inspect attrs.doesNotFocus it is undefined
app.directive('doesNotFocus', function ($timeout) {
return {
scope: false,
link: function (scope, element, attrs) {
scope.$watch(attrs.doesNotFocus, function (value) {
// sets focus
}
...
I'm on week 3 of using Angular, so I must be missing some silly semantic issue.
Here is a fiddle illustrating my issue.
http://jsfiddle.net/tpeiffer/eAFmJ/
EDIT
My actual problem was that my real code was like this (hazard of mocking the problem, you sometimes mask the real problem):
<input should-focus="{{isDrawerOpen()}" ... ></input>
but because I was using a function, not a property, I was missing the required ticks
<input should-focus="{{'isDrawerOpen()'}}" ... ></input>
Making this change fixed the problem and my directive can still be like this:
scope.$watch(attrs.shouldFocus, focusCallback(newValue));
END EDIT
Thanks for helping me in my quest for angular excellence!
Thad
Remove {{}} from your HTML. So instead of:
<input class="filter-item" placeholder="Enter filter"
does-not-focus="{{bottomDrawerOpen}}" type="text">
use
<input class="filter-item" placeholder="Enter filter"
does-not-focus="bottomDrawerOpen" type="text">
Then it works with watching attrs.doesNotFocus:
scope.$watch(attrs.doesNotFocus, function (value) {...} );
Fiddle
Your bottom drawer was watching a function isDrawerOpen(), not a property.
Change
scope.$watch('isDrawerOpen()',...);
to
scope.$watch('toggleBottomDrawer',...);

How to access the $ngModelController from inside the Controller without a Form and without a Directive

Maybe it's a rookie mistake, but I can't seem to access the $scope.model's $ngModelController so I can grab the $viewValue from it.
I have an input without a form (im using ui-mask directive):
<input type="text" ng-model="inicio" name="inicio" ui-mask="99/99/9999">
// inside my controller
$scope.inicio = dateFilter((new Date).getTime(), 'dd/MM/yyyy');
ui-mask set the $modelValue a different value than $viewValue, making it hard to send formatted data to the server. When the $scope.inicio model changes, the value is a date without slashes, like 01012014. So I need to be able to get the controller for that input, but without having to wrap it in a form, and have to use $scope.myForm.inicio.$viewValue. It MUST be possible...
Things I know I can do, but seems hacky, there must be a simpler way:
Put the element inside a form and access it through $scope.myForm.input.$viewValue
Get the element data using jQuery $('input[name="inicio"]').data('$ngModelController');
Get the element using angular.element('input[name="inicio"]').controller('ngModel');
Create a directive, put it in the input, and update my scope model with it
app.directive('viewValue', function(){
return {
priority: 10,
require: 'ngModel',
link: function(scope, element, attrs, controller){
scope.$watch(attrs.viewValue, function(newValue, oldValue){
if (newValue !== oldValue){
scope[attrs.viewValue] = controller.$viewValue;
}
});
}
}
});
<input type="text" ui-mask="99/99/9999" ng-model="inicio" view-value="inicio">
I like the directive alternative. Essentially the ui-mask directive isn't doing what you want, so you might as well write your own directive.
You shouldn't have to pass inicio to your view-value directive. Instead, add your own parser to ngModelCtrl.$parsers. Here's an example: https://stackoverflow.com/a/15556249/215945

Angularjs + kendoui dropdownlist

I have this directive
angular.module('xxx', [
])
.directive('qnDropdown', [
'$parse',
function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ngModel) {
scope.$watch(attr.qnDropdown, function(source) {
var model = $parse(attr.ngModel);
elem.kendoDropDownList({
dataTextField: "Name",
dataValueField: "ID",
value: attr.value,
select: function(e) {
var item = this.dataItem(e.item.index());
scope.$apply(function() {
model.assign(scope, item.value);
});
},
//template: '<strong>${ data.Name }</strong><p>${ data.ID }</p>',
dataSource: source
});
});
}
};
}]);
Input field is
<input qn:dropdown="locations" ng:model="installation.LocationID" value="{{installation.LocationID}}" />
EVerything works fine but initial value for kendoDropDownList is not filled (value: attr.value).
I suppose I am doing something at wrong place or time but not sure what?
You probably need to use $observe:
Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined. -- docs, see section Attributes.
Here's an example where I used $observe recently. See also #asgoth's answer there, where he uses $watch, but he also created an isolate scope.
I'm still not clear on when we need to use $observe vs when we can use $watch.
Are you sure {{installation.LocationID}} has a value you expect? I was able to copy-paste your code with some tweaks for my situation and the dropdownlist is working wonderfully (thank you for doing the hard work for me!). I'm populating value on the input field and when the directive executes, attr.value has it and Kendo shows it as expected. Perhaps this was an Angular issue a couple versions ago?
I had the same problem, the attr.value was empty. The problem was related to an $http async call being made to get the data. The scope data was not yet populated when the dropdownlist was being defined in the directive.
I fixed this by watching attr.ngModel instead of attr.qnDropdown in the link function of the directive. This way the dropdownlist gets defined when the scope data is populated.

Resources