angularjs - need help for delayed search directive - angularjs

fiddle here: http://jsfiddle.net/graphicsxp/QA4Fa/2/
I'm triying to create a directive for searching. Basically it's just a textbox that detects user input and after a 1 second delay, a search method is called.
It's not working yet and I'm having two issues.
First, why is the filterCriteria not updated in the span when user inputs text ?
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
<div ng-app="myApp" ng-controller="myController">
<delayed-search ng-model="filterCriteria"></delayed-search>
<span>filter criteria is : {{filterCriteria}}</span>
</div>
angular.module('myApp', []).directive("delayedSearch", ['$timeout', function($timeout) {
return {
restrict: "E",
template: '<input type="text" />',
scope: {
filterCriteria : '='
},
link: function (scope, element, attrs) {
},
controller: function ($scope) {
var timer = false;
$scope.$watch('filterCriteria', function () {
if (timer) {
$timeout.cancel(timer);
}
timer = $timeout(function () {
alert('timeout expired');
}, 1000)
});
}
}
}]).controller('myController', function($scope){ });

You should NOT use a controller with a directive ( until you understand it ! ) .
A controller in a directive is meant for directive to directive communication (I wish they had named it something else!).
#Langdon got it right.. But here is another implementation of the same. Note that in both the answer's the controller is missing.
http://jsfiddle.net/QA4Fa/4/

First, why is the filterCriteria not updated in the span when user inputs text ?
Your scope is wrong, it should be scope: { ngModel : '=' },, and your template should be template: '<input type="text" ng-model="ngModel" />.
Second, the watch on filterCriteria is triggered at page loading but not when text is entered in the textbox.
Same as the first problem, you should be watching ngModel.
Also, you don't need the overhead of a controller for this, you can get away with just using the link function. Here's an updated fiddle: http://jsfiddle.net/QA4Fa/3/

Related

How to get modified values from controller to custom directive?

I have defined one variable in controller and i have assigned this value to one attribute of custom directive. So on the basis of this value i am showing the modal box template. Now if i click on the cancel button from modal box template then it calls one function from controller which is modifying the variable value to false but it is not hiding the popup box. Please help me to fix it.
(function () {
'use strict';
angular.module('module1').directive('myDirective', function () {
function linkFunction(scope, elem, attrs) {
//scope.openvalue = attrs.openvalue;
scope.closevalue = false;
scope.close = function () {
console.log("Inside Close");
scope.openvalue = false;
scope.closevalue = false;
};
};
return {
templateUrl: 'confirmTemplate.html',
restrict: 'E',
link: linkFunction,
scope: {
confirmtext: '#',
openvalue: '=',
closeconfirm: '&',
submitconfirm: '&'
},
controller: ['$scope', function ($scope) {
$scope.$watch('openvalue', function () {
console.log("OpenValue : " + $scope.openvalue);
});
}]
};
});
})();
Following is the html for opening this modal.
<div class="col-xs-12 options" ng-click="cntrl.flag1 = true">
<div class="row">
<myDirective openvalue="cntrl.flag1" confirmtext="This is the text from directive"
closeconfirm="cntrl.closeconfirm()" submitconfirm="cntrl.submitconfirm()"></myDirective>
<div class="col-xs-9 no-left-right-padding">My text</div>
</div>
</div>
And i want the updated value of openvalue inside html template but it is not working.
It would be more clear to have you codes here, but I think the problem is when you call the function from controller, it doesn't actually modify the variable of controller scope but modal's scope.
In AngularJS scope, any change of inherited variable in child scope will create a local version.
Based on your words, when you open a modal window it will create a new child scope and when you call the function from controller to modify that scope variable, you actually modifying that child scope variable not controller's.
You can simply add console.log($scope.$id); in controller and the function then you should be able to see the scope id is different.
This Fiddle will give you the idea, press Esc key to close the modal window. However, as I said it would be better to have your code to address exact issue.
Based on your code, a quick fix is assign the cntrl object into directive which will make sure your directive refer to the same object.
Change your modal to
<myDirective cntrl="cntrl" confirmtext="This is the text from directive"></myDirective>
in your directive
scope: {
confirmtext : '#',
cntrl : '='
},
in your linkFunction
function linkFunction(scope, elem, attrs){
scope.close = function(){
scope.cntrl.flag1 = false;
}
you still can access closeconfirm and submitconfirm by $scope.cntrl.closeconfirm and $scope.cntrl.submitconfirm respectively.

input file change listener in a directive doesnt update scope variable [plunker attached]

I have a directive that contains file input. On change of file input I try to update some scope variables, it doesn't update it. When the commented out timeout is uncommented scope variables are updated.
I was under the impression that angular.element .on function does the safe apply (triggering a digest). I am not sure why it requires another $timeout here to work, Could someone shed some light on this behaviour?
Problem reproduced in below plunker.
https://plnkr.co/edit/dHRInri9i21bR0gxe8q1?p=preview
app.directive('fileInput', function($timeout) {
var directive;
directive = {
restrict: 'E',
templateUrl: 'fileinput.html',
link: function(scope, element) {
element
.find('input')
.on('change', function(e) {
console.log(e);
scope.fileName = 'file name--->' + e.target.files[0].name;
/*$timeout(function(){
scope.fileSize = 'fileSize--->' + e.target.files[0].size;
});*/
});
}
};
return directive;
});
directive template:
<input type="file" id="fileUpload" accept="image/*" />
<br>
{{fileName}}
<hr>
{{fileSize}}
element.on is either using jqLite or Jquery. So using scope.$apply is valid in this case as element.on is not AngularJs aware
plunker: https://plnkr.co/edit/hohU97QmjASwat139sEV?p=preview

pass data from controller to directive's link?

In my controller :
myApp.controller('homeCtrl', function($scope, $rootScope, $state, 'red';
$rootScope.$on('new_story', function(event, data) {
$scope.cardObj = {key:'value'};
});
});
In my HTML :
<div clickmeee ></div>
<div id="feedContainer" card='{{cardObj}}'> </div>
In my directive :
myApp.directive('clickmeee', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {   
scope.$watch('card', function(newVal, oldVal) {
alert(scope.card);
});       
});
}
};
});
How do I pass data from controller to this directive. I compile some html and prepend it to the div. All of that is sorted out but I need some data from object I am trying to pass.
Any help??
There are several problems in your code:
you define a scope attribute named 'card', but you use cardObj instead
you use a watch that is completely unnecessary. And worse: you create a new watch every time the element is clicked
you don't define any card attribute on your clickmeee element. Instead, you're placing it on another element, on which the directive is not applied
you're passing the attribute with '#'. That works, but the directive will receive a string, containing the JSONified object, rather than the object itself
you're not showming us where you emit an event that will initialize cardObj in the controller scope
Here is a plunkr showing a working version of your code.
Also, note that using bind('click') is a bad idea. You'd better have a template in your directive and use ng-click in the template, or simply not use a directive at all and just use ng-click directly on the div element.
Bad news. You are doing it wrong all the ways.
Firstly
card='{{cardObj}}' >
this one should be put in the
<div clickmeee ></div>
So you can take it as binded scope variable in your directive registration
Secondly
If you managed to use '#' syntax
card: '#'
it will turn your input to string, not a binded scope. Use '=' instead.
In the end
You dont need to use watch here:
scope.$watch('card', function(newVal, oldVal) {
alert(newVal);
});
since scope.card is binded via '=' connector. Just simple use alert(scope.card). (Need to warn you that alert an object is not a good idea)
I have tried your code here: plunker. Changed a litte bit by using cardObj as string for easier presentation. Does it match your work?
You should watch the card object:
myApp.directive('clickmeee', function() {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
scope.$watch('card', function(value) {
console.log(value);
});
}
};
});
And:
<div clickmeee id="feedContainer" card='{{cardObj}}'> </div>
Whenever the controller changes the cardObj, the directive's watch on card is triggered:
$scope.$apply(function() {
$scope.cardObj = "test";
}

AngularJS $watch controller variable from a directive with scope

From the directive, I want to track changes to a controller variable using $watch.
I have created this jsfiddle. (https://jsfiddle.net/hqz1seqw/7/)
When the page loads, the controller and both directives $watch function gets called but when I change the radio buttons, only the controllers and dir-two $watch function gets called. Why isnt dir-ones $watch function being called?
I want both the directives $watch to fire however, I can only get one of them to (i.e. dir-two). Not sure what I need to change. Does it have something to do with isolated scope? Is there a better way of doing this?
AngularJS Code:
var mod = angular.module("myApp", []);
//Controller
mod.controller("myCtrl", function($scope){
$scope.tempformat = "C";
$scope.one="25 - dir-one";
$scope.$watch('tempformat', function(nv){
alert("nv from controller");
});
$scope.two="35 - dir-two";
});
//dir-one directive
mod.directive("dirOne", function(){
return{
restrict: 'E',
template: "<p>{{info}}</p>",
scope: {info: '='
},
link: function (scope, element, attr) {
scope.$watch('tempformat', function(nv){
alert("nv from directive-one");
if(scope.tempformat === "C"){
element.find("p").append("C");
}
else if(scope.tempformat === "F"){
element.find("p").append("F");
}
});
}
}});
//dir-two directive
mod.directive("dirTwo", function($window){
return{
restrict: "EA",
template: "<p></p>",
link: function (scope, element, attr) {
scope.$watch('tempformat', function(nv){
alert("nv from directive-two");
if(scope.tempformat === "C"){
element.find("p").append("C");
}
else if(scope.tempformat === "F"){
element.find("p").append("F");
}
});
}
}
});
HTML Code:
<div ng-app="myApp" ng-controller="myCtrl">
<h2>Temperature</h2>
<input type="radio" ng-model="tempformat" value="C"/> Celcius
<input type="radio" ng-model="tempformat" value="F"/> Farenheit
<dir-one info="one"></dir-one>
<dir-two info="two"></dir-two>
</div>
Does it have something to do with isolated scope?
The problem is the fact that dir-one separates its scope from the parent. There are some alternatives that can be done in this situation such as:
scope.$watch('$parent.tempformat', function(nv){ //...
which will look to the parent for the specified content.
Another alternative is to bind to the directive itself:
scope: {
info: '=',
tempformat: '='
},
and then in the html:
<dir-one info="one" tempformat="tempformat"></dir-one>
see: the documentation for more information. Particularly the Isolating the Scope of a Directive area.
Is there a better way of doing this?
In general isolate scopes help construct reusable components (as noted in the documentation) so if this is something that is being attempted (from the content noted in the answer) then I would support something along the lines of the second option where you can specify that watch content on the directive itself and consider that the "better" way of doing this.
From my experience, and this is solely my own preference, I would bind it to the directive since I usually isolate my scope(s) for a reason.

Pass form to directive

I want to encapsulate my form fields in a directive so I can simply do this:
<div ng-form='myForm'>
<my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>
</div>
How do I access the myForm in my directive so I can do validation checks, e.g. myForm.Email.$valid?
To access the FormController in a directive:
require: '^form',
Then it will be available as the 4th argument to your link function:
link: function(scope, element, attrs, formCtrl) {
console.log(formCtrl);
}
fiddle
You may only need access to the NgModelController though:
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
console.log(ngModelCtrl);
}
fiddle
If you need access to both:
require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
console.log(ctrls);
}
fiddle
Here a complete example (styled using Bootstrap 3.1)
It contains a form with several inputs (name, email, age, and country).
Name, email and age are directives. Country is a "regular" input.
For each input is displayed an help message when the user does not enter a correct value.
The form contains a save button which is disabled if the form contains at least one error.
<!-- index.html -->
<body ng-controller="AppCtrl">
<script>
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope) {
$scope.person = {};
});
</script>
<script src="inputName.js"></script>
<script src="InputNameCtrl.js"></script>
<!-- ... -->
<form name="myForm" class="form-horizontal" novalidate>
<div class="form-group">
<input-name ng-model='person.name' required></input-name>
</div>
<!-- ... -->
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button class="btn btn-primary" ng-disabled="myForm.$invalid">
<span class="glyphicon glyphicon-cloud-upload"></span> Save
</button>
</div>
</div>
</form>
Person: <pre>{{person | json}}</pre>
Form $error: <pre>{{myForm.$error | json}}</pre>
<p>Is the form valid?: {{myForm.$valid}}</p>
<p>Is name valid?: {{myForm.name.$valid}}</p>
</body>
// inputName.js
app.directive('inputName', function() {
return {
restrict: 'E',
templateUrl: 'input-name.html',
replace: false,
controller: 'InputNameCtrl',
require: ['^form', 'ngModel'],
// See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
scope: {},
link: function(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
var ngModel = ctrls[1];
if (attrs.required !== undefined) {
// If attribute required exists
// ng-required takes a boolean
scope.required = true;
}
scope.$watch('name', function() {
ngModel.$setViewValue(scope.name);
});
}
};
});
// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);
Edit 2: I'll leave my answer, as it might be helpful for other reasons, but the other answer from Mark Rajcok is what I originally wanted to do, but failed to get to work. Apparently the parent controller here would be form, not ngForm.
You can pass it in using an attribute on your directive, although that will get rather verbose.
Example
Here's a working, simplified jsFiddle.
Code
HTML:
<div ng-form="myForm">
<my-input form="myForm"></my-input>
</div>
Essential parts of the directive:
app.directive('myInput', function() {
return {
scope: {
form: '='
},
link: function(scope, element, attrs) {
console.log(scope.form);
}
};
});
What's happening
We've asked Angular to bind the scope value named in the form attribute to our isolated scope, by using an '='.
Doing it this way decouples the actual form from the input directive.
Note: I tried using require: "^ngForm", but the ngForm directive does not define a controller, and cannot be used in that manner (which is too bad).
All that being said, I think this is a very verbose and messy way to handle this. You might be better off adding a new directive to the form element, and use require to access that item. I'll see if I can put something together.
Edit: Using a parent directive
OK, here's the best I could figure out using a parent directive, I'll explain more in a second:
Working jsFiddle using parent directive
HTML:
<div ng-app="myApp">
<div ng-form="theForm">
<my-form form="theForm">
<my-input></my-input>
</my-form>
</div>
</div>
JS (partial):
app.directive('myForm', function() {
return {
restrict: 'E',
scope: {
form: '='
},
controller: ['$scope', function($scope) {
this.getForm = function() {
return $scope.form;
}
}]
}
});
app.directive('myInput', function() {
return {
require: '^myForm',
link: function(scope, element, attrs, myForm) {
console.log(myForm.getForm());
}
};
});
This stores the form in the parent directive scope (myForm), and allows child directives to access it by requiring the parent form (require: '^myForm'), and accessing the directive's controller in the linking function (myForm.getForm()).
Benefits:
You only need to identify the form in one place
You can use your parent controller to house common code
Negatives:
You need an extra node
You need to put the form name in twice
What I'd prefer
I was trying to get it to work using an attribute on the form element. If this worked, you'd only have to add the directive to the same element as ngForm.
However, I was getting some weird behavior with the scope, where the myFormName variable would be visible within $scope, but would be undefined when I tried to access it. That one has me confused.
Starting with AngularJS 1.5.0, there is much cleaner solution for this (as opposed to using the link function directly). If you want to access a form's FormController in your subcomponent's directive controller, you can simply slap the require attribute on the directive, like so:
return {
restrict : 'EA',
require : {
form : '^'
},
controller : MyDirectiveController,
controllerAs : 'vm',
bindToController : true,
...
};
Next, you'll be able to access it in your template or directive controller like you would any other scope variable, e.g.:
function MyDirectiveController() {
var vm = this;
console.log('Is the form valid? - %s', vm.form.$valid);
}
Note that for this to work, you also need to have the bindToController: true attribute set on your directive. See the documentation for $compile and this question for more information.
Relevant parts from the documentation:
require
Require another directive and inject its controller as the fourth argument to the linking function. The require property can be a string, an array or an object:
If the require property is an object and bindToController is truthy, then the required controllers are bound to the controller using the keys of the require property. If the name of the required controller is the same as the local name (the key), the name can be omitted. For example, {parentDir: '^parentDir'} is equivalent to {parentDir: '^'}.
Made your 'What I'd prefer' fiddle work!
For some reason you could see the "$scope.ngForm" string in a console.log, but logging it directly didn't work, resulting in undefined.
However, you can get it if you pass attributes to the controller function.
app.directive('myForm', function() {
return {
restrict: 'A',
controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
this.getForm = function() {
return $scope[$attrs['ngForm']];
}
}]
}
});
http://jsfiddle.net/vZ6MD/20/

Resources