Using angularjs without $routeProvider & $location? - javascript

I have been playing with angularjs for couple of days and like it so far. I am trying to build a chrome extension which attaches a small widget below every gmail message when the user is on gmail.com. So far so good. As part of authentication code, I handle 401 error in this way. Whenever there is a 401 error, I use $location.path( "/login" ) to redirect the user to the login screen/template. This changes browser address bar which seems to be the default behavior. So, if the current address was https://mail.google.com/mail/u/0/, it becomes https://mail.google.com/mail/u/0/#/login. But mine is not standalone app, its more like widget that attaches to a div when on gmail.com site. My app should not mess with the browser address bar. I am now starting to think if I can really use angularjs for my app as I am going against the default behavior. Should I use angularjs at all?
I also posted it here
https://groups.google.com/forum/?fromgroups#!topic/angular/TrT54r_IYmg

You can emit/broadcast events on rootScope and subscribe to them in your login directive.
Here is little clue http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application
it uses interceptors to catch 401
myapp.config(function($httpProvider) {
var interceptor = ['$rootScope','$q', function(scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
var deferred = $q.defer();
var req = {
config: response.config,
deferred: deferred
}
scope.requests401.push(req);
scope.$broadcast('event:loginRequired');
return deferred.promise;
}
// otherwise
return $q.reject(response);
}
return function(promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
});
And simplest directive could be this
myapp.directive("loginForm",function($http){
return function(scope,element,attrs){
element.hide();
scope.$root.$on('event:loginRequired', function(event) {
element.show();
});
scope.login=function(){
// You can set controller for this directive, but I skiped that part for sake of simplicity
var payload = $.param({username: scope.username, password: scope.password});
var config = {
headers: {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}
}
$http.post('/some/login/url', payload, config).success(function(data) {
if (data === 'AUTHENTICATION_SUCCESS') {
element.hide();
}else{
alert("username or password was wrong, please try again");
elements.find("form").reset(); // reset form, or you coud reset just password field
}
});
};
};
});
Now, directive in action
<div login-form>
<form ng-submit="login()">
<label for="username">Username</label>
<input type="text" id="username" ng-model="username"/>
<br />
<label for="password">Password</label>
<input type="password" id="password" ng-model="password" />
<hr/>
<input type="submit" value="Login" />
</form>
</div>
Please note code above is not tested, probably there is some misspell or something. But let me know if you have trouble implementing this, I will try to take some time and effort to make it work.

Related

Server errors for AngularJS ng-fab-form

I am using validation on AngularJS side as well as serve side (e.g. for duplicate value check) and would like to expose these errors to users. Without ng-fab-form I was able to build custom server error directive and use it like this:
<input type="number" id="level" name="level" ng-model="vm.record.level"
server-error
required>
<div ng-messages="vm.form.role_level.$error">
<p ng-message="server">{{ vm.errors.level }}</p>
</div>
But the goal of the library is to get rid of this kind of duplication. As you can see I am using Controller as syntax and assigning the errors to each field, when saving/updating the model fails:
angular.forEach(result.data.errors, function (errors, field) {
vm.form[field].$setValidity('server', false);
vm.errors[field] = errors.join(', ');
});
I customised validation template to show messages for server error, however, I am not able to display dynamic error text. I suppose the problem is related to scope inheritance. Any ideas how could I achieve the desired effect?
I just found a way how to do it:
Add custom attribute, to hold the server error message value and watch the error in it.
<input type="number" id="level" name="level" ng-model="vm.record.level"
validation="{{ vm.errors.level }}
server-error
required>
In validation template display the attribute value.
<li ng-message="server">{{ attrs.validation }}</li>
I would recommend using $asyncValidators instead of $setValidity from angular#1.3.2 on.
app.directive('username', function($q, $timeout) {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
ctrl.$asyncValidators.username = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty model valid
return $q.when();
}
var def = $q.defer();
$timeout(function() {
// Mock a delayed response
if (usernames.indexOf(modelValue) === -1) {
// The username is available
def.resolve();
} else {
def.reject();
}
}, 2000);
return def.promise;
};
}
};
});
(example taken from the docs)
Then you could just add the message to your own validations template:
angular.module('exampleApp', [
'ngFabForm'
])
.config(function (ngFabFormProvider)
{
ngFabFormProvider.extendConfig({
validationsTemplate : 'path/to/your-fabulous-validation-template.html'
});
});
And in the template add:
<li ng-message="username">I'm not valid!!! ;(</li>
There also was an issue about that on the github-page of the module: https://github.com/johannesjo/ng-fab-form/issues/34
For a cleaner solution there would have to be an interface, which doesn't exist yet, but you could always open an issue for that.

Angular Server side form validation

I am creating my first angular app and have just finished setting up a form that uses ngResource to talk to my Rails web service. I found this tutorial extremely helpful in getting my validation to work.
I do however have a small issue. When I submit the form for the first time, without entering any value, my web-service responds with a 500 error. If I then type in a single letter and resubmit, it responds with 422 (like it should). If I remove this letter and resubmit it once again responds with a 422 (like it should). Thus no error messages are displayed on the first null value submission but if I allow the system to return an error on purpose and resubmit a null value it behaves like it should.
Here is my code:
Controller:
$scope.memberData = {};
$scope.create = function() {
var error, success;
$scope.errors = {};
success = function() {
$scope.memberData = {};
};
error = function(result) {
return angular.forEach(result.data.errors, function(errors, field) {
$scope.form[field].$setValidity('server', false);
$scope.errors[field] = errors.join(', ');
});
};
BetaMember.save({ beta_member: { email: $scope.memberData.email }}).$promise.then(success, error);
};
View:
<form ng-submit="create(memberData)" name="form" novalidate>
<input type="text" ng-model="memberData.email"name="email" server-error>
<span class="errors" ng-show="form.email.$dirty && form.email.$invalid">
<span ng-show="form.email.$error.server">{{errors.email}}</span>
</span>
<button type="submit" class="btn btn-danger">Join</button>
</form>
Directive:
appServices.directive('serverError', function(){
return {
restrict: 'A',
require: '?ngModel',
link: function(scope,element,attrs,ctrl){
element.on('change keyup', function(){
scope.$apply(function(){
ctrl.$setValidity('server', true);
});
});
}
};
});
My Question
Why would this cause the web service to render a server error (500) if I submit an empty form initially? After submitting a random value, that does not pass server validation and renders a 422 error (that is displayed in my form) and resubmitting an empty form, I get a 422 error stating that the field is required.

Service to indicate some done criteria using Promise

We have several controllers in an application, for several different tabs/pages.
I want to have some mechanism to indicate that something is finished in one of
them for use in another controller. Promises should be this mechanism,
so I am trying to get a hang of it.
I have played around at http://jsfiddle.net/ExN6Q/ and gotten something that works like I want, but I am not super happy with the result for the services.
If I have the following html:
<div ng-app="myApp">
<div ng-controller=myController1>
Fill in this field: <input type="text">
<input type="submit" value="Done" ng-click="submit()">
</div>
<div ng-controller=myController2 ng-show="done1">
And this field: <input type="text">
<input type="submit" value="Done" ng-click="submit()">
</div>
<div ng-controller=myController3 ng-show="done2">
As well as this field: <input type="text">
<input type="submit" value="Done" ng-click="submit()">
</div>
</div>
and then the following controllers:
my_module.controller('myController1', function ($scope, Done1Service) {
$scope.submit = Done1Service.done;
});
my_module.controller('myController2', function ($scope, Done1Service, Done2Service) {
$scope.done1 = false;
$scope.submit = Done2Service.done;
Done1Service.get_promise().then(function () {
$scope.done1 = true;
});
});
my_module.controller('myController3', function ($scope, Done2Service, Done3Service) {
$scope.done2 = false;
$scope.submit = Done3Service.done;
Done2Service.get_promise().then(function () {
$scope.done2 = true;
});
Done3Service.get_promise().then(function () {
alert("Congratulations, you're done!");
});
});
then I am actually satisfied with the result, the "problem" is the implementation of
the services:
my_module.factory('Done1Service', function ($q) {
var deferred = $q.defer();
var get_promise_fn = function () {
return deferred.promise;
};
var done_fn = function () {
console.log("I'm done!");
return deferred.resolve(true);
};
return {
get_promise: get_promise_fn,
done: done_fn
};
});
my_module.factory('Done2Service', function ($q) {
... // identical except console.log("I'm done again!")
});
my_module.factory('Done3Service', function ($q) {
... // identical except console.log("I'm done at last!");
});
These feel a bit too boilerplatish, and I wonder if I am doing something wrong.
Could I create one common service and make three instances of it? Is this the normal way
to handle this by returning a promise from a dedicated get_promise function (
which I then assume would probably be called something else)?
As it is right now, your services are a one shot deal since promises cannot be reset. I think you could solve this problem by either using a single EventBus service through which every controllers communicates. E.g. The first controller sends a 'step1completed' message on the bus which is intercepted by the second controller, which does it's work and fires a 'step2completed' event, etc.
If you have many identical processes that have to run in parallel, you could allow creating multiple EventBus instances which would serve as independent communication channels for a specific set of objects.
Angular already allow you to emit or broadcast events through scopes, have a look at $emit and $broadcast.

Why is my variable "unresolved" in AngularJS

I have a really stupid question and I'm hoping someone can help me understand AngularJS a little better here whilst I trawl through more documentation... please be aware that I have been working with AngularJS for a week now as I have inherited a project off a colleague, anyway...
The unit tests associated with my project are failing with the following error message "scope.signupForm is undefined in /Users/.../.../.../app/login/login.js"
In WebStorm my code is being highlighted (underlined grey) with the following messages "Unresolved Variable signinForm" & "Unresolved Variable signupForm", the code where this is being raised is below...
this is part of the controller...
function LoginController($scope, userService) {
$scope.loggedInUser = null;
$scope.signIn = function (user) {
console.log("SignIn");
$scope.loggedInUser = { userName: user.userName };
$scope.user = undefined;
$scope.signinForm.$setPristine(); // Error here is "Unresolved Variable signinForm"
};
$scope.register = function (user) {
console.log("Register");
$scope.loggedInUser = user;
$scope.user = undefined;
console.log(user);
userService.addUser(user);
$scope.signupForm.$setPristine();// Error here is "Unresolved Variable signupForm"
};
$scope.signOut = function () {
console.log("SignOut");
$scope.loggedInUser = undefined;
$scope.signInVisible = false;
};
... // more code here
Now this is my HTML code contained in a View (for want of a better word)
<div id="login-signin" class="loginLeftBox">
<form name="signinForm" novalidate ng-submit="signIn(loginUser)" autocomplete="off">
<div> ... Form Stuff...</div>
</form>
<div ng-show="signinForm.userName.$dirty && signupForm.userName.$invalid">
... Validation Stuff...
</div>
<div ng-show="signinForm.password.$dirty && signupForm.password.$invalid">
... Validation Stuff...
</div>
</div>
<div id="login-register" class="loginRightBox">
<form name="signupForm" novalidate ng-submit="register(user)" autocomplete="off">
... Form Stuff...
</form>
</div>
Any explanations would be appreciated...
You have to place your controller in the same level as the form:
<form name="signinForm" ng-controller="SinginFormCtrl" ...>
Then the SinginFormCtrl will have the signinForm in scope, e.g.:
function SinginFormCtrl($scope, userService) {
$scope.signIn = function (user) {
...
$scope.signinForm.$setPristine(); // WILL WORK NOW
};
...
}
This probably means that you will have to restructure your code a bit.
I found that, after writing $scope to the console that both $scope.signinForm & $scope.signupForm where present and defined! Thus I added the following condition to the controller and now all the Unit Tests seem to work?
reset = function(){
if($scope.signinForm){
$scope.signinForm.$setPristine();
}
if($scope.signinForm){
$scope.signupForm.$setPristine();
}
};
Not sure if this is a solution or a hack?

check if data already exists on server in angular.js

I am new to angular.js
<input type="email" disabled='disabled' name="email" ng-model="userInfo.emailAddress"
class="input-xlarge settingitem" style="height:30px;">
<span ng-show="myForm.email.$error.email" class="help-inline" style="color:red;font-size:10px;">
Not a Valid Email Address</span>
I have email field, corresponding to which i need to check at server if it already exists in database or not.
Can anyone guide me through the steps how it can be done using angular directives and services for checking at server
I would suggest writing a directive that would plug into NgModelController#$parsers pipeline (check "Custom Validation" from http://docs.angularjs.org/guide/forms).
Here is a sketch of such a directive:
.directive('uniqueEmail', ["Users", function (Users) {
return {
require:'ngModel',
restrict:'A',
link:function (scope, el, attrs, ctrl) {
//TODO: We need to check that the value is different to the original
//using push() here to run it as the last parser, after we are sure that other validators were run
ctrl.$parsers.push(function (viewValue) {
if (viewValue) {
Users.query({email:viewValue}, function (users) {
if (users.length === 0) {
ctrl.$setValidity('uniqueEmail', true);
} else {
ctrl.$setValidity('uniqueEmail', false);
}
});
return viewValue;
}
});
}
};
}])
Where the Users.query is an async call to check if an email is unique or not. Of course you should substitute this with a call to your back-end.
When done, this directive can be used like so:
<input type="email" ng-model="user.email" unique-email>
Example of this directive was taken from the angular-app that some of the AngularJS community members try to put together to illustrate common use-cases. It might be worth checking out to see how all this fits together in the complete app.

Categories

Resources