How to make $watch function executed in Test? - angularjs

I am implementing a simple directive that represents a form field with all its extras like label, error field, regex all in a single line.
The directive is as follow:
<div ng-controller="parentController">
{{username}}
<!-- the directive -- >
<form-field label="Username:" regex="someRegex" constrainsViolationMessage="someValidationMessage" model="username" place-holder="some input value">
</form-field>
</div>
Now, I want to test the data binding between the directive scope and the parent scope.
The test is:
it("should bind input field to the scope variable provided by parent scope ! ", function () {
var formInput = ele.find('.form-input');
formInput.val("some input");
expect(ele.find('p').text()).toEqual('some input');
});
This problem is that I don't know why test don't pass, even the directive works correctly. Here is a fiddle of the directive.
And here is the whole test and test set up.
var formsModule = angular.module('forms', []);
formsModule.controller('parentController', function ($scope) {
});
formsModule.directive('formField', function () {
var label;
var constrainsViolationMessage;
var placeHolder;
var model;
return {
restrict:'E',
transclude:true,
replace:false,
scope:{
model:'='
},
link:function (scope, element, attr) {
console.log("link function is executed .... ");
scope.$watch('formInput', function (newValue, oldValue) {
console.log("watch function is executed .... !")
scope.model = newValue;
});
scope.label = attr.label;
},
template:'<div class="control-group ">' +
'<div class="form-label control-label">{{label}}</div> ' +
'<div class="controls controls-row"> ' +
'<input type="text" size="15" class="form-input input-medium" ng-model="formInput" placeholder="{{placeHolder}}">' +
'<label class="error" ng-show={{hasViolationConstrain}}>{{constrainsViolationMessage}}</label>' +
'</div>'
}
});
beforeEach(module('forms'));
var ele;
var linkingFunction;
var elementBody;
var scope;
var text = "";
var placeHolder = "filed place holder";
var label = "someLabel";
var regex = "^[a-z]{5}$";
beforeEach(inject(function ($compile, $rootScope) {
scope = $rootScope;
elementBody = angular.element('<div ng-controller="parentController">' +
'<p>{{username}}</p>' +
'<form-field label="Username:" regex="someRegex" constrainsViolationMessage="someValidationMessage" model="username" place-holder="some input value"> </form-field>');
ele = $compile(elementBody)(scope);
scope.$digest();
}
));
afterEach(function () {
scope.$destroy();
});
iit("should bind input field to the scope variable provided by parent scope ! ", function () {
var formInput = ele.find('.form-input');
formInput.val("some input");
expect(ele.find('p').text()).toEqual('some input');
});
As you can see, I want to assert that form input is reflected in the scope variable set in the 'model' attribute provided by the parent scope.
Am I missing something here ?
Thanks for helping me ... !

You're missing the scope.$apply() call after you set the input value, so the change is never getting digested. In the regular application lifecycle this would happen automatically, but you have to manually trigger this in your tests
Take a look at https://github.com/angular/angular.js/blob/master/test/ng/directive/formSpec.js for a ton of examples.

Use $scope.$digest() after adding condition to execute watch. It will fire watch.

Related

Angular directive to use ng-blur and ng-focus

I'm having trouble understanding Angular directives. I'd like to use a simple attribute to expand into more complicated html, but with some parts of this template being replaceable via parameters.
Given this code:
<form role="form" name="databaseForm">
<input type="text" name="connectionName"
ng-focus="databaseForm.connectionName.$focused = true;databaseForm.connectionName.$blurred = false;"
ng-blur="databaseForm.connectionName.$blurred = true;databaseForm.connectionName.$focused = false;"
>
</form>
I'd like to write it using a more terse directive (such as "blurred-focused"):
<form role="form" name="databaseForm">
<input type="text" name="connectionName"
blurred-focused="'databaseForm.connectionName'"
>
</form>
So that means I can very easily re-use it for other inputs:
<form role="form" name="databaseForm">
<input type="text" name="username"
blurred-focused="'databaseForm.username'"
>
</form>
The expected result from this is that the inputs with this directive will have the $blurred and $focused properties automatically applied to it, based on the user interaction with the input.
Thank you.
Update:
I ended up using MMHunter's version where the scope is non-isolated. I added some logic to automatically find the form and field object, so that I didn't need to specify it all.
My directive:
(function () {
"use strict";
angular
.module("app.shared.widgets")
.directive("blurredFocused", blurredFocused);
blurredFocused.$inject = ["_"];
/* #ngInject */
function blurredFocused(_) {
return {
restrict: "A",
priority: -1,
link: function(scope, element, attributes) {
element.on("blur", function () {
var formFieldName = element[0].form.name + "." + element[0].name;
var target = _.get(scope, formFieldName);
scope.$apply(function() {
target.$blurred = true;
target.$focused = false;
});
});
element.on("focus", function () {
var formFieldName = element[0].form.name + "." + element[0].name;
var target = _.get(scope, formFieldName);
scope.$apply(function() {
target.$blurred = false;
target.$focused = true;
});
});
}
};
}
})();
And an example of its usage:
<form role="form" name="databaseForm">
<input type="text" name="connectionName" blurred-focused>
</form>
You need is not difficult to achieve with angular directive. But things can be different based on whether isolated scope is used.
With isolated scope, the following code is straightForward. Binding the value to an isolated scope in the 'blurred-focused' directive and listen to the events.
//with isolated scope
app.directive("blurredFocused", [function () {
return {
restrict:"A",
priority:-1,
scope:{
blurredFocused:"="
},
link:function(scope,ele,attrs){
ele.on("blur",function(){
scope.$apply(function(){
scope.blurredFocused.$blurred = true;
scope.blurredFocused.$focused = false;
})
})
ele.on("focus",function(){
scope.$apply(function(){
scope.blurredFocused.$blurred = false;
scope.blurredFocused.$focused = true;
})
})
}
}
}]);
But without isolated scope, things could be a little bit tricky. we need to find the scope value manually by the attributes value.
//without isolated scope
app.directive("blurredFocused", [function () {
return {
restrict:"A",
priority:-1,
link:function(scope,ele,attrs){
ele.on("blur",function(){
var targetField = scope[attrs.blurredFocused];
scope.$apply(function(){
targetField.$blurred = true;
targetField.$focused = false;
})
})
ele.on("focus",function(){
var targetField = scope[attrs.blurredFocused];
scope.$apply(function(){
targetField.$blurred = false;
targetField.$focused = true;
})
})
}
}
}]);
Here is the plunker
I would recommend you use the one without isolated scope. Attribute directives are always used together so it may not be a good idea to have isolated scopes.

How to update both model and view value in Angular directive?

Apologies in advance, directives are not my strong suit!
I have a simple attribute-only directive, the purpose of which is to automatically convert a string in a field to an HH:mm format upon blur'ing the field. This is the directive:
(function () {
'use strict';
angular
.module('app.format-as-time')
.directive('formatAsTime', timeDirective);
timeDirective.$inject = [
'isValid'
];
function timeDirective (isValid) {
return {
require: 'ngModel',
restrict: 'A',
link: LinkFunction
};
function LinkFunction (scope, elem, attrs, ngModel) {
elem.bind('blur', function () {
var currentVal = ngModel.$modelValue,
formattedVal = '';
// Format something like 0115 to 01:15
if (currentVal.length === 4) {
formattedVal = currentVal.substr(0, 2) + ':' + currentVal.substr(2, 2);
// Format something like 115 to 01:15
} else if (currentVal.length === 3) {
formattedVal = '0' + currentVal.substr(0, 1) + ':' + currentVal.substr(1, 2);
// Format something like 15 to 00:15
} else if (currentVal.length === 2) {
formattedVal = '00:' + currentVal;
}
// If our formatted time is valid, apply it!
if (isValid.time(formattedVal)) {
scope.$applyAsync(function () {
ngModel.$viewValue = formattedVal;
ngModel.$render();
});
}
});
}
}
}());
And the associated view:
<div ng-controller="TestController as test">
<input type="text"
maxlength="5"
placeholder="HH:mm"
ng-model="test.startTime"
format-as-time>
<button ng-click="test.getStartTime()">Get Start Time</button>
</div>
And the associated Controller:
(function () {
'use strict';
angular
.module('app.testModule')
.controller('TestController', TestController);
function TestController () {
var vm = this;
vm.startTime = '';
vm.getStartTime = function () {
console.log(vm.startTime);
}
}
}());
At present, the directive works as expected for the view but the model in my controller does not get updated, i.e. the input will contain 01:15 but the model will console.log() 115.
I have tried using:
scope: {
ngModel: '='
}
in the directive but this did not do anything.
Have I done this the right way, and if so what do I need to add to ensure both the model and view remain in sync?
If I have done this the wrong way, what would be the best way to do it correctly?
The problem lies in this line ngModel.$viewValue = formattedVal; Angular has a pipeline used to set a modelValue which includes running it through registered $parsers and $validators. The proper way to set the value is by calling $setViewValue(formattedVal) which will run the value through this pipeline.

Dynamically create directives from a loop

I have a main directive that has an array on it's scope that contains data for constructing other directives that should be compiled and appended to the main directive.
The problem is that when I iterate through that array I only get the data from the last element in array,
so I can't properly bind respective data for each custom directive.
Plunker
main directive :
angular.module('testApp')
.directive('mainDirective', ["$compile", function ($compile) {
return {
template: ' <div><p>Main Directive </p><div class="insertion-point"></div></div>',
link: function (scope, element, attributes, controller) {
var insertionPoint = element.find('.insertion-point');
angular.forEach(scope.demoObj.panels, function (value, index) {
var directiveName = value.type;
scope.value = value;
var directiveString = "<div " + directiveName + " panel-data=value ></div>";
var compiledElement = $compile(directiveString)(scope);
insertionPoint.append(compiledElement);
});
}
}
}]);
directive to be nested:
angular.module('testApp')
.directive('nestedDirective', [function () {
return {
scope:{
panelData:'='
},
template:' <div><p>Nested Directive </p>{{panelData.data.test}}</div>'
}
}]);
data looks like this:
$scope.demoObj = {
panels:[
{
id:'unique_id_1',
type:'nested-directive',
data:{
test:'test 1'
}
},
{
id:'unique_id_2',
type:'nested-directive',
data:{
test:'test 2'
}
},
{
id:'unique_id_3',
type:'nested-directive',
data:{
test:'test 3'
}
}
]
}
As far as I can understand , the compilation is not happening immediately in the forEach statement, that's why every directive gets the data from the object with the id unique_id_3 (last element in array). Also all directives have isolated scope.
edit: I understand that in forEach I need to add the value to the scope so I can pass it to the nested directive isolated scope, and I understand that when the loop finishes scope.value will be the last value of the loop, but I was under the impression that compile will immediately pass the value to the nested directive and be done with it.
So, when does the compilation happen?
How can I circumvent this limitation?
The problem is the link step of the compiledElement will happen in the next digest cycle, at that time, scope.value is the last value of the data.
The solution is to create different value properties on scope, like this:
var directiveName = value.type;
var valueProp = 'value' + index;
scope[valueProp] = value;
var directiveString = "<div " + directiveName + " panel-data=" + valueProp + "></div>";
plunk
Please find the update code below. Rather than creating duplicate variable in scope Below is the solution. I have created plunker for the same
angular.module('testApp')
.directive('mainDirective', ["$compile", function ($compile) {
return {
template: ' <div><p>Main Directive </p><div class="insertion-point"></div></div>',
link: function (scope, element, attributes, controller) {
var insertionPoint = element.find('.insertion-point');
angular.forEach(scope.demoObj.panels, function (value, index) {
var directiveName = value.type;
var directiveString = "<div " + directiveName + " panel-data=demoObj.panels["+ index+"]></div>";
var compiledElement = $compile(directiveString)(scope);
insertionPoint.append(compiledElement);
});
}
}
}]);

How to test a directive containing a text field in Angularjs

I have a directive containing a text field, and I want to test to make sure that the text entered into the field makes it to the model.
The directive:
define(function(require) {
'use strict';
var module = require('reporting/js/directives/app.directives');
var template = require('text!reporting/templates/text.box.tpl');
module.directive('textField', function () {
return {
restrict: 'A',
replace: true,
template:template,
scope: {
textField : "=",
textBoxResponses : "="
},
link: function(scope) {
scope.debug = function () {
scope;
// debugger;
};
}
};
});
return module;
});
The markup:
<div ng-form name="textBox">
<!-- <button ng-click="debug()">debug the text box button</button> -->
<h1>Text Box!</h1>
{{textField.label}} <input type="text" name="textBox" ng-model="textBoxResponses[textField.fieldName]">{{name}}
</div>
The test code:
/* global inject, expect, angular */
define(function(require){
'use strict';
require('angular');
require('angularMock');
require('reporting/js/directives/app.directives');
require('reporting/js/directives/text.box.directive');
describe("builder experimenter", function() {
var directive, scope;
beforeEach(module('app.directives'));
beforeEach(inject(function($compile, $rootScope) {
scope = $rootScope;
scope.textBoxResponses = {};
scope.textBoxField = {
fieldName : "textBox1"
};
directive = angular.element('<div text-field="textBoxField" text-box-responses="textBoxResponses"></div>');
$compile(directive)(scope);
scope.$digest();
}));
it('should put the text box value on the model', inject(function() {
directive.find(":text").val("something");
expect(scope.textBoxResponses.textBox1).toBe("something");
}));
});
});
So, what I'm trying to do in the last it block is to simulate typing in the text field, and then check to make sure that the new value of the text field makes it to the model. The issue is that the model is never updated with the new value.
The issue is ng-model is never informed that anything is in the textfield. ng-model is listening for the input event. All you have to do to fix your code is:
var text = directive.find(":text");
text.val("something");
text.trigger('input');
expect(scope.textBoxResponses.textBox1).toBe("something");
When the ng-model gets the event input, then check your scope and everything will be what you expect.
I got this done by using the sniffer service.
Your test will look like this:
var sniffer;
beforeEach(inject(function($compile, $rootScope, $sniffer) {
scope = $rootScope;
sniffer = $sniffer;
scope.textBoxResponses = {};
scope.textBoxField = {
fieldName : "textBox1"
};
directive = angular.element('<div text-field="textBoxField" text-box-responses="textBoxResponses"></div>');
$compile(directive)(scope);
scope.$digest();
}));
it('should put the text box value on the model', inject(function() {
directive.find(":text").val("something");
directive.find(":text").trigger(sniffer.hasEvent('input') ? 'input' : 'change');
expect(directive.isolateScope().textBoxResponses.textBox1).toBe("something");
}));
I found this pattern here: angular-ui-bootstrap typeahead test
The trigger basically makes the view value go into the model.
Hope this helps

Angular.js: choosing a pre-compiled template depending on a condition

[disclaimer: I've just a couple of weeks of angular behind me]
In the angular app I'm trying to write, I need to display some information and let the user edit it provided they activated a switch. The corresponding HTML is:
<span ng-hide="editing" class="uneditable-input" ng:bind='value'>
</span>
<input ng-show="editing" type="text" name="desc" ng:model='value' value={{value}}>
where editing is a boolean (set by a switch) and value the model.
I figured this is the kind of situation directives are designed for and I've been trying to implement one. The idea is to precompile the <span> and the <input> elements, then choose which one to display depending on the value of the editing boolean. Here's what I have so far:
angular.module('mod', [])
.controller('BaseController',
function ($scope) {
$scope.value = 0;
$scope.editing = true;
})
.directive('toggleEdit',
function($compile) {
var compiler = function(scope, element, attrs) {
scope.$watch("editflag", function(){
var content = '';
if (scope.editflag) {
var options='type="' + (attrs.type || "text")+'"';
if (attrs.min) options += ' min='+attrs.min;
options += ' ng:model="' + attrs.ngModel
+'" value={{' + attrs.ngModel +'}}';
content = '<input '+ options +'></input>';
} else {
content = '<span class="uneditable-input" ng:bind="'+attrs.ngModel+'"></span>';
};
console.log("compile.editing:" + scope.editflag);
console.log("compile.attrs:" + angular.toJson(attrs));
console.log("compile.content:" + content);
})
};
return {
require:'ngModel',
restrict: 'E',
replace: true,
transclude: true,
scope: {
editflag:'='
},
link: compiler
}
});
(the whole html+js is available here).
Right now, the directive doesn't do anything but output some message on the console. How do I replace a <toggle-edit ...> element of my html with the content I define in the directive? If I understood the doc correctly, I should compile the content before linking it: that'd be the preLink method of the directive's compile, right ? But how do I implement it in practice ?
Bonus question: I'd like to be able to use this <toggle-edit> element with some options, such as:
<toggle-edit type="text" ...></toggle-edit>
<toggle-edit type="number" min=0 max=1 step=0.01></toggle-edit>
I could add tests on the presence of the various options (like I did for min in the example above), but I wondered whether there was a smarter way, like putting all the attrs but the ngModel and the editflag at once when defining the template ?
Thanks for any insight.
Here is a tutorial by John Lindquist that shows how to do what you want. http://www.youtube.com/watch?v=nKJDHnXaKTY
Here is his code:
angular.module('myApp', [])
.directive('jlMarkdown', function () {
var converter = new Showdown.converter();
var editTemplate = '<textarea ng-show="isEditMode" ng-dblclick="switchToPreview()" rows="10" cols="10" ng-model="markdown"></textarea>';
var previewTemplate = '<div ng-hide="isEditMode" ng-dblclick="switchToEdit()">Preview</div>';
return{
restrict:'E',
scope:{},
compile:function (tElement, tAttrs, transclude) {
var markdown = tElement.text();
tElement.html(editTemplate);
var previewElement = angular.element(previewTemplate);
tElement.append(previewElement);
return function (scope, element, attrs) {
scope.isEditMode = true;
scope.markdown = markdown;
scope.switchToPreview = function () {
var makeHtml = converter.makeHtml(scope.markdown);
previewElement.html(makeHtml);
scope.isEditMode = false;
}
scope.switchToEdit = function () {
scope.isEditMode = true;
}
}
}
}
});
You can see it working here: http://jsfiddle.net/moderndegree/cRXr6/

Resources