NgModel not found while create directive - angularjs

Here is my directive fileUpload for validation of image extenstion. I am not getting what is missing here. (Missing Required Controller Controller 'ngModel', required by directive 'fileUpload', can't be found! ) Please if any one can look in to this.
I found so many reference to the same question but no luck . please dont mark this as repeat question.
App.directive('fileUpload', function () {
return {
scope: true, //create a new scope
require: 'ngModel',
link: function (scope, el, attrs,ctrl) {
ctrl.$validators.images = function(modelValue, viewValue) {
console.log(modelValue);
console.log(viewValue);
};
ngModel.$setValidity('extension', true);
el.bind('change', function (event) {
var files = event.target.files;
var validFormats = ['jpg', 'jpeg', 'png', 'gif'];
var validSize = 500000;
for (var i = 0;i<files.length;i++) {
var ext = files[i].name.split('.');
var size = files[i].size;
if (ext !== undefined) {
var isExist = validFormats.indexOf(ext[1].toLowerCase()) >= 0 ? 1 : 0;
if (isExist == true && validSize >= (size)) {
ngModel.$setValidity('extension', true);
} else {
ngModel.$setValidity('extension', false);
}
}
//emit event upward
scope.$emit("fileSelected", { file: files[i] });
}
});
}
};
});
Input :
<div class="form-group has-feedback" style="margin-left: 10px;">
<input name="file" type="file" ng-model="images" accept="image/*" multiple="true" file-upload />
<div style="color:grey; font-size: 12px;">
Extensions : jpg , jpeg , png , gif<br>
Size : 500KB
</div>
<div ng-messages="frmSupportPanelist.file.$error" ng-if="frmSupportPanelist.file.$touched">
<label id="message-required-error" class="validation-error-label" for="file" ng-message="extension">Please select valid Image </label>
</div>
</div>

Related

how to use controller scope in directive without using controller option in directive

This is my directive file code
function imageUploadDirective(fileReader, rootScope) {
function imageUploadLinker(scope, elem, attr) {
scope.$inputFileElement = elem.find(":file");
scope.$inputFileElement.bind("change", function (e) {
rootScope.invalid_image_msg = "";
e.stopPropagation();
var files = e.target.files === undefined ? (e.target && e.target.value ? [{
name: e.target.value.replace(/^.+\\/, '')
}] : []) : e.target.files;
console.log("files: ", files);
console.log("FILE ZERO INDEX");
console.log(files[0]);
if (files.length === 0 || files.length > 1 || !fileReader.isImageFile(files[0])) {
if (!fileReader.isImageFile(files[0])) {
rootScope.invalid_image_msg = "IT's Not an Image, Please Select Valid Format of Image";
} else {
rootScope.invalid_image_msg = "Please Select an Valid Image";
}
rootScope.is_image_valid = false;
console.log("reset");
scope.reset();
return;
}
scope.fileName = files[0];
fileReader
.readAsDataUrl(scope.fileName, scope)
.then(function (result) {
rootScope.userInfo.imageSrc = result;
});
});
}
return {
restrict: "EA",
templateUrl: "/user/imageUploadTemplate",
scope: {
name: "#",
alt: "#",
selectBtnTitle: "#",
changeBtnTitle: "#",
removeBtnTitle: "#",
noImage: "#"
},
controller: "lugbeeUserSPACtrl",
link: imageUploadLinker
};
}
imageUploadDirective.$inject = ["fileReader", "$rootScope"];
angular
.module("lugbeeUserSPA")
.directive("imageUpload", imageUploadDirective);
This is my template html file code
<div class="fileinput">
<div data-text="{{noImage}}" class="thumbnail image-upload-preview">
<img ng-show="hostInfo.imageSrc" ng-src="{{hostInfo.profile_image_path}}" alt="{{imageSrc ? alt : ''}}">
</div>
<div>
<span class="btn btn-default btn-file">
<span ng-if="!fileName" ng-click="openFileSelector()">{{:: selectBtnTitle}}</span>
<span ng-if="!!fileName" ng-click="openFileSelector()">{{:: changeBtnTitle}}</span>
<input type="file" class="inputfile" name="{{:: name}}" id="{{:: name}}">
</span>
{{:: removeBtnTitle}}
</div>
How to remove controller option from the above code and also how can I replace scope option object with controller scope variable.
please help me to solve this problem because the controller onload function calls two times because of controller option that's why I want this.
I have recreated a scenario with your code, and the controller is only activated once. The issue must be somewhere else.
function imageUploadDirective(fileReader, rootScope, $timeout) {
function imageUploadLinker(scope, elem, attr, ngController) {
ngController.onLoad("I have access to this method now also from the link function");
scope.$inputFileElement = elem.find(":file");
scope.$inputFileElement.bind("change", function(e) {
rootScope.invalid_image_msg = "";
e.stopPropagation();
var files = e.target.files === undefined ? (e.target && e.target.value ? [{
name: e.target.value.replace(/^.+\\/, '')
}] : []) : e.target.files;
console.log("files: ", files);
console.log("FILE ZERO INDEX");
console.log(files[0]);
if (files.length === 0 || files.length > 1 || !fileReader.isImageFile(files[0])) {
if (!fileReader.isImageFile(files[0])) {
rootScope.invalid_image_msg = "IT's Not an Image, Please Select Valid Format of Image";
} else {
rootScope.invalid_image_msg = "Please Select an Valid Image";
}
rootScope.is_image_valid = false;
console.log("reset");
scope.reset();
return;
}
scope.fileName = files[0];
fileReader
.readAsDataUrl(scope.fileName, scope)
.then(function(result) {
rootScope.userInfo.imageSrc = result;
});
});
}
return {
restrict: "EA",
templateUrl: "/user/imageUploadTemplate",
scope: {
name: "#",
alt: "#",
selectBtnTitle: "#",
changeBtnTitle: "#",
removeBtnTitle: "#",
noImage: "#"
},
require: '^ngController',
link: imageUploadLinker
};
}
imageUploadDirective.$inject = ["fileReader", "$rootScope", "$timeout"];
angular
.module('app', [])
.directive('imageUpload', imageUploadDirective)
.factory('fileReader', function() {
return {};
})
.controller('lugbeeUserSPACtrl', function($scope) {
this.onLoad = function onLoad(message) {
console.log(message);
};
this.onLoad('controller activated when initialized in ngController');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<body ng-app="app" ng-controller="lugbeeUserSPACtrl">
<image-upload></image-upload>
<script type="text/ng-template" id="/user/imageUploadTemplate">
<div class="fileinput">
<div data-text="{{noImage}}" class="thumbnail image-upload-preview">
<img ng-show="hostInfo.imageSrc" ng-src="{{hostInfo.profile_image_path}}" alt="{{imageSrc ? alt : ''}}">
</div>
<div>
<span class="btn btn-default btn-file">
<span ng-if="!fileName" ng-click="openFileSelector()">{{:: selectBtnTitle}}</span>
<span ng-if="!!fileName" ng-click="openFileSelector()">{{:: changeBtnTitle}}</span>
<input type="file" class="inputfile" name="{{:: name}}" id="{{:: name}}">
</span>
{{:: removeBtnTitle}}
</div>
</script>
</body>
I found my solution if you remove scope option from directive and access controller scope variable in directive template than can solve this problem like this.
Here is my updated code which can be used to solve this type of problem
function imageUploadDirective(fileReader, rootScope) {
function imageUploadLinker(scope, elem, attr) {
scope.$inputFileElement = elem.find(":file");
scope.$inputFileElement.bind("change", function (e) {
rootScope.invalid_image_msg = "";
e.stopPropagation();
var files = e.target.files === undefined ? (e.target && e.target.value ? [{
name: e.target.value.replace(/^.+\\/, '')
}] : []) : e.target.files;
console.log("files: ", files);
console.log("FILE ZERO INDEX");
console.log(files[0]);
if (files.length === 0 || files.length > 1 || !fileReader.isImageFile(files[0])) {
if (!fileReader.isImageFile(files[0])) {
rootScope.invalid_image_msg = "IT's Not an Image, Please Select Valid Format of Image";
} else {
rootScope.invalid_image_msg = "Please Select an Valid Image";
}
rootScope.is_image_valid = false;
console.log("reset");
scope.reset();
return;
}
scope.fileName = files[0];
fileReader
.readAsDataUrl(scope.fileName, scope)
.then(function (result) {
console.log("VALUE OF USER INFO IN IMAGE DIRECTIVE");
console.log(rootScope.userInfo);
rootScope.userInfo.imageSrc = result;
rootScope.userInfo.profile_image_path = result;
rootScope.avatarFile = files[0];
rootScope.is_image_valid = true;
console.log("IN DIRECTIVE CODE IMAGE SRC");
console.log(rootScope.imageSrc);
});
});
}
return {
restrict: "EA",
templateUrl: "/user/imageUploadTemplate",
link: imageUploadLinker
};
}
imageUploadDirective.$inject = ["fileReader", "$rootScope"];
angular
.module("lugbeeUserSPA")
.directive("imageUpload", imageUploadDirective);

Nested ng-repeat gives error after 10 levels deep: 10 $digest() iterations reached

Getting this error: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
I have created a reddit style nested comment system using AngularJS. But after 10 levels deep i get a very ugly error on my console that looks like this:
This happens after exactly 10 levels deep:
The nested comments are directives that look like this:
commentCont.tpl.html:
<div class="comment-cont">
<div
class="upvote-arrow"
ng-show="!collapsed"
ng-click="vote()"
ng-class="{'active' : node.votedByMe}"
>
<div class="upvote-arrow-top"></div>
<div class="upvote-arrow-bottom"></div>
</div>
<div class="wrapper">
<div class="info">
<span class="collapse" ng-click="collapsed = !collapsed">[ {{collapsed ? '+' : '–'}} ]</span>
<span ng-class="{'collapsed-style' : collapsed}">
<a ui-sref="main.profile({id: node.userId})">{{node.username}}</a>
<span>{{node.upvotes}} point{{node.upvotes != 1 ? 's' : ''}}</span>
<span mg-moment-auto-update="node.createdTime"></span>
<span ng-show="collapsed">({{node.children.length}} child{{node.children.length != 1 ? 'ren' : ''}})</span>
</span>
</div>
<div class="text" ng-bind-html="node.comment | autolink | nl2br" ng-show="!collapsed"></div>
<div class="reply" ng-show="!collapsed">
<span ng-click="formHidden = !formHidden">reply</span>
</div>
</div>
<div class="input-area" ng-show="!collapsed">
<form ng-show="!formHidden" name="form" autocomplete="off" novalidate ng-submit="submitted = true; submit(formData, form)">
<div class="form-group comment-input-feedback-branch has-error" ng-show="form.comment.$invalid && form.comment.$dirty">
<div ng-messages="form.comment.$error" ng-if="form.comment.$dirty">
<div class="input-feedback" ng-message="required">Comment text is required.</div>
<div class="input-feedback" ng-message="maxlength">Comment text cannot exceed 2500 characters.</div>
</div>
</div>
<div
class="form-group"
ng-class="{ 'has-error' : form.comment.$invalid && form.comment.$dirty, 'has-success' : form.comment.$valid }"
>
<textarea
name="comment"
class="textarea comment-cont-textarea"
ng-model="formData.comment"
required
ng-maxlength="2500"
textarea-autoresize
></textarea>
</div>
<div class="form-group">
<mg-button-loading
mgbl-condition="awaitingResponse"
mgbl-text="Save"
mgbl-loading-text="Saving..."
mgbl-class="btn-blue btn-small"
mgbl-disabled="!form.$valid"
></mg-button-loading>
<mg-button-loading
mgbl-text="Cancel"
mgbl-class="btn-white btn-small"
ng-click="formHidden=true;"
></mg-button-loading>
</div>
</form>
</div>
<div ng-show="!collapsed">
<div ng-repeat="node in node.children" ng-include="'commentTree'"></div>
</div>
</div>
commentCont.directive.js:
(function () {
'use strict';
angular
.module('app')
.directive('commentCont', commentCont);
/* #ngInject */
function commentCont ($http, user, $timeout) {
return {
restrict: 'E',
replace: true,
scope: {
node: '='
},
templateUrl: 'app/post/commentCont.tpl.html',
link: function (scope, element, attrs) {
var textarea = element.querySelector('.comment-cont-textarea');
var voteOK = true;
var action = '';
var userInfo = user.get();
scope.formHidden = true; // Do not ng-init="" inside an ng-repeat.
scope.collapsed = false; // Do not ng-init="" inside an ng-repeat.
// Autofocus textarea when reply link is clicked.
scope.$watch('formHidden', function(newValue, oldValue) {
if (newValue !== true) {
$timeout(function() {
textarea[0].focus();
});
}
});
scope.submit = function (formData, form) {
if (form.$valid) {
scope.awaitingResponse = true;
formData.parentId = scope.node.id;
formData.postId = scope.node.postId;
formData.type = 4;
formData.fromUsername = userInfo.username;
formData.toId = scope.node.userId;
formData.fromImage = userInfo.thumbnail36x36.split('/img/')[1];
// console.log(formData);
$http.post('/api/comment', formData)
.then(function (response) {
scope.awaitingResponse = false;
if (response.data.success) {
if (response.data.rateLimit) {
alert(rateLimitMessage);
return false;
}
// id and createdTime is sent with response.data.comment.
var c = response.data.comment;
var newCommentNode = {
id: c.id,
userId: userInfo.id,
username: userInfo.username,
parentId: formData.parentId,
comment: formData.comment,
upvotes: 0,
createdTime: c.createdTime,
votedByMe: false,
children: [],
postId: scope.node.postId
};
// console.log('user', user.get());
// console.log('scope.node', scope.node);
// console.log('response', response);
// console.log('newCommentNode', newCommentNode);
formData.comment = '';
form.comment.$setPristine();
scope.formHidden = true;
scope.node.children.unshift(newCommentNode);
}
});
}
};
scope.vote = function() {
if (voteOK) {
voteOK = false;
if (!scope.node.votedByMe) {
scope.node.votedByMe = true;
action = 'add';
} else {
scope.node.votedByMe = false;
action = 'remove';
}
var data = {
commentId: scope.node.id,
action: action
};
$http.post('/api/comment/vote', data)
.then(function (response) {
// console.log(response.data);
voteOK = true;
if (action === 'add') {
scope.node.upvotes++;
} else {
scope.node.upvotes--;
}
});
}
};
}
};
}
}());
The tree is being called like this:
<script type="text/ng-template" id="commentTree">
<comment-cont
node="node"
></comment-cont>
</script>
<div ng-repeat="node in tree[0].children" ng-include="'commentTree'"></div>
How can I have more than 10 levels of nested ng-repeat without getting an error like this?
The default implementation of $digest() has limit of 10 iterations . If the scope is still dirty after 10 iterations error is thrown from $digest().
The below stated is one way of configuring the limit of digest iterations to 20.
var app = angular.module('plunker', [], function($rootScopeProvider) {
$rootScopeProvider.digestTtl(20); // 20 being the limit of iterations.
});
But you should look in to stabilizing your model rather than configuring the limit of iterations.
I was dealing with a similar issue. end up with the following directive:
(function () {
'use strict';
angular
.module('app')
.directive('deferDom', ['$compile', ($compile) => {
return {
restrict: 'A',
compile: (tElement) => {
// Find, remove, and compile the li.node element.
let $el = tElement.find( "li.node" );
let transclude = $compile($el);
$el.remove();
return function($scope){
// Append li.node to the list.
$scope.$applyAsync(()=>{
tElement.append(transclude($scope));
});
}
},
};
}]);
})();

error message with AsyncValidator is not visible

The required directive shows the red error message that works!
The uniqueschoolclassnumberValidator directive shows NOT the red error message!
From the server I always return exists => true, but I also tried it with false.
What do I wrong? The custom directive is triggered for sure!
Directive
'use strict';
angular.module('TGB').directive('uniqueschoolclassnumberValidator', function (schoolclassCodeService) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$asyncValidators.unique = function (schoolclass) {
var schoolclassNumber = "0a";
var schoolyearId = 1;
return schoolclassCodeService.exists(schoolyearId, schoolclassNumber);
};
}
};
});
Service
this.exists = function (schoolyearId, schoolclassNumber) {
var path = 'api/schoolyears/' + schoolyearId + '/schoolclasses/' + schoolclassNumber;
return $http.get(path).then(function (response) {
if (response.data == true) {
$q.reject("schoolclass number has already been taken");
}
else {
return $q.resolve();
}
});
};
Html
<form name="myForm">
<div class="col-sm-8">
<input type="text" unique-schoolclasnumber-Validator name="myInput"
ng-model-options="{ updateOn: 'default blur', debounce: {'default': 300, 'blur': 0} }"
ng-model="schoolclassNumber" class="form-control"
required placeholder="Enter schoolclass">
</div>
<div ng-messages="myForm.myInput.$error" style="color:red" role="alert">
<div ng-message="required">You did not enter anything.</div>
<div ng-message="unique">That schoolclass number already exists.</div>
</div>
</form>
In your service's exists method there should be return keyword before $q.reject:
if (response.data == true) {
return $q.reject("schoolclass number has already been taken");
}
else {
return $q.resolve();
}
Directive should be named uniqueSchoolclassnumberValidator instead of uniqueschoolclassnumberValidator (AngularJS change dash-delimited format to camelCase).
There is also a misspell in html code, in word "class". It should be unique-schoolclassnumber-Validator instead of unique-schoolclasnumber-Validator.

Handling Parameter of Directive in AngularJS

i am passing the parameter(id) to the directive as follows
<input type="text" ng-model="entry.sale_qty" required style="width: 200px" on-keyupfn="handleKeypress(id)" >
And i have the definition of the on-keyupfn directive is as follows
app.directive('onKeyupFn', function() {
return function(scope, elm, attrs) {
var keyupFn = scope.$eval(attrs.onKeyupFn);
elm.bind('keyup', function(evt) {
scope.$apply(function() {
keyupFn.call(scope, evt.which);
});
});
};
});
And the mentioned function(handleKeypress) for directive is as this
$scope.handleKeypress = function(key) {
$scope.keylog.push(key);
if ($scope.keylog[len - 1] == 17 && $scope.keylog[len] == 13)
$scope.addUser();
len = $scope.keylog.length;
}
Now can anybody help me with the accessing of passed parameter(id) from html in the function "$scope.handleKeypress = function(key) {}"???
You might want to use ng-keyup here:
html:
<input type="text" ng-model="entry.sale_qty" required style="width: 200px" ng-keyup="handleKeypress($event)" >
js:
app.directive('onKeyupFn', function() {
return function(scope, elm, attrs) {
scope.handleKeypress = function (e) {
$scope.keylog.push(e.which);
if ($scope.keylog[len - 1] == 17 && $scope.keylog[len] == 13) {
$scope.addUser();
}
len = $scope.keylog.length;
};
});

Use ngMessages with Angular 1.2

Does anyone know if there is a fork of Angular 1.2 that supports ngMessages?
I'd love to use this but I have a requirement for IE8.
Thanks in advance for your help.
Here is my directive I use:
/**
* Ui-messages is similar implementation of ng-messages from angular 1.3
*
* #author Umed Khudoiberdiev <info#zar.tj>
*/
angular.module('uiMessages', []).directive('uiMessages', function () {
return {
restrict: 'EA',
link: function (scope, element, attrs) {
// hide all message elements
var messageElements = element[0].querySelectorAll('[ui-message]');
angular.forEach(messageElements, function(message) {
message.style.display = 'none';
});
// watch when messages object change - change display state of the elements
scope.$watchCollection(attrs.uiMessages, function(messages) {
var oneElementAlreadyShowed = false;
angular.forEach(messageElements, function(message) {
var uiMessage = angular.element(message).attr('ui-message');
if (!oneElementAlreadyShowed && messages[uiMessage] && messages[uiMessage] === true) {
message.style.display = 'block';
oneElementAlreadyShowed = true;
} else {
message.style.display = 'none';
}
});
});
}
};
});
I've used ui-messages instead of ng-messages to avoid conflicts.
<div ui-messages="form.name.$error">
<div ui-message="minlength">too short</div>
<div ui-message="required">this is required</div>
<div ui-message="pattern">pattern dismatch</div>
</div>
I don't know for sure if a fork exists but it would be easy enough to roll your own ng-message (or something that serves the same purpose). I think the following would do it:
Controller
app.controller("Test", function ($scope) {
$scope.messages = {
"key1": "Message1",
"key2": "Message2",
"key3": "Message3"};
$scope.getMessage = function (keyVariable) {
return $scope.messages[keyVariable.toLowerCase()];
};
$scope.keyVariable = 'key1';
});
HTML (example)
ENTER A KEY: <input type="text" ng-model="keyVariable" />
<h1 ng-bind="getMessage(keyVariable)" ng-show="getMessage(keyVariable) != ''"></h1>
See It Working (Plunker)
I've updated pleerock's answer to handle element directives having for and when attributes like ngMessages and ngMessage. You can find the same in this github repo
angular.module('uiMessages', []).directive('uiMessages', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
// hide all message elements
var messageElements = element.find('ui-message,[ui-message]').css('display', 'none');
// watch when messages object change - change display state of the elements
scope.$watchCollection(attrs.uiMessages || attrs['for'], function(messages) {
var oneElementAlreadyShowed = false;
angular.forEach(messageElements, function(messageElement) {
messageElement = angular.element(messageElement);
var message = messageElement.attr('ui-message') || messageElement.attr('when');
if (!oneElementAlreadyShowed && messages[message] && messages[message] === true) {
messageElement.css('display', 'block');
oneElementAlreadyShowed = true;
} else {
messageElement.css('display', 'none');
}
});
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<form name="userForm" ng-app="uiMessages" novalidate>
<input type="text" name="firstname" ng-model="user.firstname" required />
<ui-messages for="userForm.firstname.$error" ng-show="userForm.firstname.$dirty">
<ui-message when="required">This field is mandatory</ui-message>
</ui-messages>
<br />
<input type="text" name="lastname" ng-model="user.lastname" required />
<div ui-messages="userForm.lastname.$error" ng-show="userForm.lastname.$dirty">
<div ui-message="required">This field is mandatory</div>
</div>
</form>

Resources