I have an angular app linked to a Rails a backend.
What I want is to handle Rails validation errors with Angular.
Based on differents tutorials, I did this:
edit.html
<div id="Wrapper" ng-controller="EditCtrl"
<div class="row margin-top-100">
<form name="form">
<div>
<!--- FIELD -->
<input class="field" type="text" ng-blur="update()" ng-model="current.title" name="title">
</div>
</form>
</div>
</div>
edit.js
.controller('EditCtrl',
function($scope, $rootScope, $state, $stateParams, $http, CurrentSpot, $filter, Notification, $modal,$compile, $timeout){
$scope.update = function() {
$http.put('/c/'+$scope.current.id.$oid, {c: $scope.current})
.then(function(success) {
$scope.current = success.data;
Notification.success("Great !")
}, function(error) {
console.log(error)
form.title.$setValidity('server', false);
console.log("FORM", form.title)
})
}
})
Which produces this error form.title.$setValidity is not a function in the console when Rails refused the update.
Maybe one more information: console.log("FORM", form) returns:
<input class="field ng-pristine ng-untouched ng-valid ng-not-empty" type="text" ng-blur="update()" ng-model="current.title" name="title" >
but {{form.title}} in the view returns:
{
"$viewValue":"Cc",
"$modelValue":"Cc",
"$validators":{
},
"$asyncValidators":{
},
"$parsers":[
],
"$formatters":[
null
],
"$viewChangeListeners":[
],
"$untouched":true,
"$touched":false,
"$pristine":true,
"$dirty":false,
"$valid":true,
"$invalid":false,
"$error":{
},
"$name":"title",
"$options":null
}
After many hours of reading tutorials and documentations, I'm not able to find where the problem comes from.
Thanks
When you call the function as form.title.$setValidity('server', false);, form return DOM object, not an Angular object with $-named methods.
Angular creates a object in the $scope for the named form.
Check JSFiddle and take a look at the browser console.
Related
I have form where I need validation on server side. Backend use one request to validate multiple fields.
I try to add validation message after response. Here is example:
This is my html code:
<div ng-app="MyApp">
<div ng-controller="MyController">
<form name="MyForm" novalidate ng-submit="send()">
<div>
<label>Email</label>
<input type="email" name="email" ng-model="user.email" required>
<div ng-show="MyForm.email.$invalid">error message</div>
</div>
<input type="submit" ng-disabled="MyForm.$invalid">
<pre>{{ MyForm.email.$error | json }}</pre>
</form>
</div>
</div>
And my js code:
var myApp = angular.module('MyApp', []);
myApp.controller("MyController", ['$scope', '$timeout', '$log', function($scope, $timeout, $log) {
$scope.user = { "email" : "" }
$scope.send = function() {
$timeout(function() {
$log.log("Response from server. Multiple fields validation.");
$scope.MyForm["email"].$setValidity("server-side-error", false, $scope.MyForm)
// here I want to add something what listen once on $scope.MyForm["email"] changed
}, 1000);
};
}]);
But I need remove error when value changed. How to achieve it using Angular?
Here is example: https://jsfiddle.net/9wqhd89z/
You can call a check function whenever the user change the input using ng-change. So it will check if the email is currently invalid, and if it is it will set as valid again.
<input type="email" name="email" ng-model="user.email" ng-change="check()" required>
$scope.check = function(){
if ($scope.MyForm.email.$valid == false){
$scope.MyForm.email.$setValidity('server-side-error', true);
}
}
You can find a working version in this jsFiddle
I know Customizing out of the box typeahead directive is not a good idea, so went ahead and created another directive called type-ahead-custom for my customization.
here's plunker https://plnkr.co/edit/PmYRm37Uqn6CFYAXuUcl
OUTPUT
s = no results displayed on ui, even though we got back data from server.
so = Now you see the results of previous query results i.e 's', even though we got back data for 'so'
sou = Now you see the results of previous query results i.e 'so', even though we got back data for 'sou'
this continues.
HTML
<div ng-app="exampleApp">
<form class="form-horizontal" ng-controller="myCtrl" >
<div class="form-group">
<div>
<label for="account" class="col-sm-2 col-md-2 control-label customize-label ">Typeahead</label>
<div class="col-sm-8">
<div class="inner-addon right-addon">
<input type="text" ng-model="selectedOptions.planes" uib-typeahead="plane as plane.formatted_address for plane in data" type-ahead-custom="maps" typeahead-loading="loadingdata" typeahead-no-results="noResults" class="form-control ng-valid ng-dirty ng-valid-parse ng-touched" aria-autocomplete="list" aria-expanded="false" aria-owns="typeahead-4-8758" />
</div>
</div>
</div>
</div>
</form>
</div>
for example in above code type-ahead-custom="maps"is my directive, here i supply the table name to get data from (here for example sake i am just passing a string).
Javascript
var exampleApp = angular.module('exampleApp', ['ui.bootstrap']);
exampleApp.directive('typeAheadCustom', function($http) {
return {
priority: 1,
link: function($scope, $element, $attributes) {
$scope.data = [];
$scope.$watch($attributes.ngModel, function(newValue, oldValue) {
$http.get('//maps.googleapis.com/'+$attributes.typeAheadCustom+'/api/geocode/json', {
params: {
address: newValue,
sensor: false
}
}).success(function(data) {
$scope.data = data.results;
});
});
}
}
});
Since the DOM has both directive type-ahead-custom and uib-typeahead, i have given my directive priority:1 to make it executes first.
Problem: with my directive in place, typeahead seems to lag one $http request behind while showing the data on UI.
For example, if you start by typing.
I am not sure why this is happening or how to fix this. Any pointers will be helpful.
Using $q service helped me with the same issue. I modified your code a little bit.
In your index.html change plane in data to plane in search($viewValue)
<input type="text" ng-model="selectedOptions.planes" uib-typeahead="plane as plane.formatted_address for plane in search($viewValue)" type-ahead-custom="maps" typeahead-loading="loadingdata" typeahead-no-results="noResults" class="form-control ng-valid ng-dirty ng-valid-parse ng-touched" aria-autocomplete="list" aria-expanded="false" aria-owns="typeahead-4-8758" />
And in your link replace watcher with the search function that returns a promise.
$scope.search = function(newValue) {
var dfr = $q.defer();
$http.get('//maps.googleapis.com/'+$attributes.typeAheadCustom+'/api/geocode/json', {
params: {
address: newValue,
sensor: false
}
}).success(function(data) {
dfr.resolve(data.results);
});
return dfr.promise;
};
And of course, you need to inject $q service:
exampleApp.directive('typeAheadCustom', function($http, $q) {
...
Working plnkr exampe: https://plnkr.co/edit/oJBoV5M58tLHMvvSAOkS
Hi I have converted a previous function for editing records in an api into the following resource:
app.factory("Wine", function($resource){
return $resource("http://greatwines.8000.com/wines:id", {
//id as a variable
id: "#id"
},
{
update: {
method: "PUT"
}
});
});
I now want to use this by triggering a form with the "wine" records to edit with the following CTA inside the wine ng-repeat for each wine:
Edit Wine
In my controller I pass the "Wine" resource:
app.controller("editWineCtrl", function ($scope, $http, $routeParams, Wine, $location){
Wine.get({ id: $routeParams.id }, function(wine){
$scope.wine = wine;
});
...
However, in spite of the form URL returning the ID:
http://greatwines.8000.com/#/wines/1323
None of the fields i.e. :
div class="margin-top-20">
<input type="text" class="form-control" ng-model="wine.year" />
</div>
<div class="bold margin-top-20">
Grapes
</div>
<div class="margin-top-20">
<input type="text" class="form-control" ng-model="wine.grapes" />
</div>
Are being populated. Am I using the resource int he correct way?
There is a typo in url
wines:id
It should be
wines/:id
I have the following Form which I am including in the main app through ng-view
Form Snippet
<form action = "#" method="post" class="form-horizontal" id="commentForm" role="form">
<div class="form-group">
<div class="col-sm-10">
<textarea class="form-control" type="text" ng-model="userComment" placeholder="New Discussion Topic" rows="5"></textarea>
</div>
</div>
<div class="form-group" id="div_submitComment">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn" type="button" id="submitComment" ng-click="vm.addComment()">Submit comment</button>
</div>
</div>
</form>
Upon clicking the submit button, the controller function will be called. In the controller function, I am not able to access the variable $scope.userComment
Controller Snippet
(function () {
'use strict';
angular
.module('app')
.controller('DiscussionBoardController', DiscussionBoardController);
DiscussionBoardController.$inject = ['UserService', '$rootScope', '$scope', '$cookieStore', 'AuthenticationService'];
function DiscussionBoardController(UserService, $rootScope, $scope, $cookieStore, AuthenticationService) {
function addComment() {
$.getJSON(
"http://localhost/DBoardPage/server/discussion-board/postComment.php", // The server URL
{ userName: $scope.currentUser , userComment:$scope.userComment , commentID:0 }, // Data you want to pass to the server.
$scope.addCommentResponse // The function to call on completion.
);
};
/*End - Discussion Board related Functions*/
}
})();
Though I know that a child scope will be created when we use ng-include, ng-view and ng-repeat, I am not getting an example to explain the usage.
Please let me know how I can get around this preoblem
Are you sure it isn't $scope.currentUser that doesn't exist? You are using a variable that isn't declared.
Try adding:
$scope.currentUser = "";
$scope.userComment = "";
Since you are using AngularJS it might be a better idea creating your function the Angular way.
$scope.addComment = function(){....
If it still doesn't work show more of your setup.
I'm trying to unit test a connection forms controller with karma and jasmine, but on thing I can't get to grips with.
The controller:
.controller('connectionController', ['$scope', '$rootScope', '$state', '$stateParams', '$http', '$localStorage',
function($scope, $rootScope, $state, $stateParams, $http, $localStorage){
$scope.userCredentials = {};
$scope.connectionError = false;
$scope.connectUser=function(){
if ($scope.connectionForm.$valid) {
//Do the login stuff here
//IF good --> state.go...
}
else {
console.log('Invalide.');
$scope.connectionError = true;
}
}
}])
Then in my HTML (simplified to a maximum):
<form name="connectionForm" novalidate>
<div class="form-group input-group" show-errors='{ showSuccess: true }'>
<span for="login" class="input-group-addon"></span>
<input type="text" ng-model="userCredentials.login" required>
</div>
<div class="form-group input-group" show-errors='{ showSuccess: true }'>
<span for="password" class="input-group-addon"></span>
<input type="password" ng-model="userCredentials.password" required>
</div>
<button class="btn btn-primary" ng-click="connectUser()">Connect</button>
</form>
And finally the test (specific 'it' that doesn't pass):
it('should connect user', function(){
scope.userCredentials.login = 'aUser';
scope.userCredentials.password = 'aPassword';
$httpBackend.when('POST', 'http://localhost:4711/api/token').
respond({ token: 'xxx', user: aUser });
scope.connectUser();
$httpBackend.flush();
});
So the the problem I get is that this:
TypeError: Cannot read property '$valid' of undefined
at Scope.$scope.connectUser (connection.js)
at null.<anonymous> (connection.js)
Si I think the basic problem comes from the fact that the controller doesn't have the access to stuff defined only on the html (like connectionForm...) and cannot get their properties.
Another thing to notice is that I do a state.go(..) when the connection was successful, could it be this ?
What can I do ? Or more, what am I doing wrong ?