I am new to AngularJS and would like to create functionality for a login page similar to what you find here when you click the 'Forgot Password' link:
http://bootsnipp.com/snippets/featured/login-amp-password-reminder#comments
Is it best to use a directive, since this is behavioral, instead of a controller? I've tried quite a bit with creating a controller for it, but as I search for help on the subject I find that using a controller for this may not be the way to go. Here was my latest trials which was unsuccessful (the link does nothing when clicked):
on controller side in a js file:
angular.module('mean.users')
.controller('SwitcherCtrl', ['$document',
function($document) {
$document.ready(function () {
$document.getElementById('olvidado').click(function (e) {
e.preventDefault();
$document.getElementById('form-olvidado').toggle('500');
});
$document.getElementById('acceso').click(function (e) {
e.preventDefault();
$document.getElementById('form-olvidado').toggle('500');
});
});
}
])
on html side, I included ng-controller="SwitcherCtrl" where necessary.
JQuery approach is completely incompatible with AngularJS. DOM Manipulation is only allowed in directives in the link function otherwise it is a very bad practice. Try to start from scratch and forget about JQuery. The magic of AngularJS happens with 2-way bindings.
You could use a directive, with a login controller and a factory/service to hold the username and password and send it to the database. For this login there is definitely no need for JQuery at all. You can check this question here:
AngularJS- Login and Authentication in each route and controller
edit: In your question above, it is not a directive instead of a controller. A directive can have a controller that is applied to a specific scope. You could do the same thing with both but depends how many times you will reuse this login snippet - I guess you won't need it but I believe it is still good practice to make one.
edit 2: if you havent' read this one, please do it! I believe you will answer most of your questions about the two different (opposite I would say) technologies. "Thinking in AngularJS" if I have a jQuery background?
Also since I came from Jquery background too, I followed these four resources in order and now I can make most of the things I want:
Introduction to Angular.js in 50 Examples (part 1) https://www.youtube.com/watch?v=TRrL5j3MIvo
Introduction to Angular.js in 50 Examples (part 2) https://www.youtube.com/watch?v=6J08m1H2BME
Free Interactive AngularJS learning for Beginners https://www.codeschool.com/courses/shaping-up-with-angular-js
More AngularJS resources by topic egghead.io https://egghead.io/technologies/angularjs
edit 3: Since I saw good interest in my answer I decided to expand it with what to avoid/best practices so the code is more testable, maintainable and easier to migrate to Angular 2:
5 Guidelines For Avoiding Scope Soup in Angular
http://www.technofattie.com/2014/03/21/five-guidelines-for-avoiding-scope-soup-in-angular.html
No $scope soup, bindToController in AngularJS
https://toddmotto.com/no-scope-soup-bind-to-controller-angularjs/
Angular JS - you probably shouldn't use $watch in your controllers.
http://www.benlesh.com/2013/10/title.html
11 Tips to Improve AngularJS Performance
http://www.alexkras.com/11-tips-to-improve-angularjs-performance/
You can try this out, it uses directives and html. Whether the login or forgot username shows is tied to the state of a scope variable in the directives link function.
Fiddle
<div ng-app="myApp">
<div my-auth>
<div ng-show="active">
<form name="login">
<input type="email" ng-model="email" placeholder="your#email.com" required />
<input type="password" ng-model="passwd" required />
<button ng-disabled="!login.$valid">Login</button>
</form>
forgot password?
</div>
<div ng-show="!active">
<form name="forgot">
<input type="email" ng-model="email" placeholder="your#email.com" required />
<button ng-disabled="!forgot.$valid">Reset Password</button>
</form>
access
</div>
</div>
</div>
The directive
angular.module('myApp', [])
.directive('myAuth', function(){
return {
link: function(scope, elem, attr){
scope.active = true;
scope.toggle = function(){
scope.active = !scope.active;
};
}
};
});
Thanks to everyone who answered. They kicked off this thing moving in the right direction and I was able to improve upon it to come to the exact answer shown below.
The HTML:
<div ng-controller="SwitcherCtrl">
<div data-fold-toggle="active">
<form id="login">
<input type="email" ng-model="email" required="">
<input type="password" ng-model="password" required="">
<button type="submit">Login</button>
<a ng-click="active=!active">Forgot your password?</a>
</form>
</div>
<div data-fold-toggle="active" style="display: none;">
<form id="forgotpw">
<input type="email" ng-model="email" required="">
<button type="submit">Reset Password</button>
<a ng-click="active=!active">Account Access</a>
</form>
</div>
</div>
The Controller & Directive:
.controller('SwitcherCtrl', function($scope) {
$scope.active = true;
})
.directive('foldToggle', function() {
return {
restrict: 'A',
scope:{
isOpen: '=foldToggle'
},
link: function(scope, element) {
scope.$watch('isOpen', function(newVal,oldVal){
if(newVal !== oldVal){
element.toggle(200);
}
});
}
};
});
Related
My ng-model is value not updating in controller bonded with $scope. There is no syntax error in my code that I can assure. Only one controller bonded with html.
$watch is only running once when page is loaded.
Note that I have 220 around total watchers in my file and controller file is very big like 1500+ lines of code. To cross check I also bind one div with different controller and it is working as expected both $watch and $scope.
I have verified that my variable is not having duplicate name in entire project. I think angular gives no performance issue till 2000 watchers per page. But here that is not the case. Can someone please shed some light on this strange behavior. Any help would be appreciated.
Note: I am not posting code here as it was working fine when I started working on controller initially and on separate prototype also code works well. This behavior has been introduced recently with more and more code.
HTML file:
<div id="studentSearch" class="form-group__text row studentmargin">
<input id="search" type="text" class="studentSearch" tabindex="2"
ng-keyup="$event.which === 13 && !disableSearch ? clickSearchButton(): ''"
placeholder="Search by Name or Email"
ng-model="searchString">
<label for="search">
<button type="button" class="link" tabindex="3"
ng-click="clickSearchButton()" ng-if="!searchResultFlag"
ng-disabled="disableSearch">
<span class="icon-search"></span>
</button>
</label>
<button type="button" class="link" ng-click="clearStudentsSearch()"
ng-if="searchResultFlag" tabindex="4">
<span class="icon-close"></span>
</button>
Controller:
angular.module('app.pages.course.details').controller("CourseDetailController", ['$scope', function($scope){
$scope.disableSearch = true;
$scope.$watch('searchString', function(oldValue, newValue){
if(newValue.length >= 3) {
$scope.disableSearch = false;
}
});
$scope.clickSearchButton = function() { /* Search logic */ }
$scope.clearStudentsSearch = function() {
$scope.searchString = "";
} }]);
So the logic is search icon will only get enabled once the searchString is greater than or equal to 3 letters. On clearing search button it gets cleared from UI and if we print the scope it has that value, Because of that on ENTER press, search work again.
Could you try using parent with the model ng-model="parent.name".
I have a form on a page which is validated just fine if all the form elements are on the page, http://jsfiddle.net/nkanand4/6za8h8xg/1/.
<div ng-form="myform">
<div>
Name: <input type="text" ng-model="user.name" required name="input"/>
</div>
</div>
This, however, stops working if I am doing a wizard kind of form, where each step is populated using directive, http://jsfiddle.net/nkanand4/pe17afvq/2/.
<div ng-form="myform">
<form-element step="selectedStep"></form-element>
</div>
Any ideas on how to solve this will be appreciated. Thanks.
EDIT:
I initially had started with ng-include but dropped that approach because if I use it, the data is not persisted from step 1 to step 2 to back to step 1. Reason being a new scope is created when you move back and forth. Hence I needed a way to keep all the data under a scope property, like $scope.data.user.name, so that i can pass back $scope.data when its requested.
Don't compile HTML yourself, you can let Angular do it properly for you:
.directive('formElement', function($log, $templateCache, $compile) {
return {
template: '<div ng-include="\'step\' + selectedStep.step + \'.html\'">',
link: function(scope, elem, attrs) {
// ... nothing really here
}
};
});
Demo: http://jsfiddle.net/pe17afvq/4/
Finally got it sorted out. This was reported as a bug on angularJS issue tracker, https://github.com/angular/angular.js/issues/7519. The trick is to use the clone in the link function that is returned when you are using $compile.
$compile(html)(scope, function(clone) {
elem.empty().append(clone);
});
Updated my jsfiddle accordingly, and it works!
What I tried to do the last days is something like that:
%myform(name='somename' ng-controller='whatever')
%myinput(ng-model='user.firstName' ...
controller has a user structure with firstName, lastname, ...
myform should just add some attributes to the <form>-tag, myinput should render a label, the input field and the errors when the somename-form-element is dirty and invalid. Pretty simple stuff.
As easy everything in AngularJS is, I had no chance. Had to move the ng-controller up to an extra div because nothing worked when the controller is defined in the myform tag (ng-click ignored, ...). Ugly but can live with that. No access to the scope in transcluded directives. Can be fixed with the link function and the append. Problem, the whole form validation stuff is not working when this fix is used. So I can have access to the form OR the scope.
What is the correct way to do this in AngularJS? I am really out of ideas and in despair after 4 days of trying and researching (learned the whole AngularJS in less than a day and not a single other problem).
Don't know if it makes sense to post ~ 30 different versions of trying to get this done. Maybe someone can provide a clean solution that is working and following the ideas behind the AngularJS framework (paypal beer thank you included).
Thank you very much in advance!
Anton
scope-fix-solutions:
http://angular-tips.com/blog/2014/03/transclusion-and-scopes/
Issue with transcoded directives: https://github.com/angular/angular.js/issues/5489
... there are thousands of problems about directives and transcoding, seems to be the most ugly part in Angular. Wanted to include more links to solutions I tried, but I am only allowed to post 2.
If somebody needs the solution (small example) - whole example on Plunker - provided by Sander Elias, many thanks!
HTML:
<body ng-controller='AppController as appVm'>
<h1>Hello angular {{appVm.version}}</h1>
<my-form name="test">
<div class="input-group">
<span class="input-group-addon">#</span>
<input type="text" class="form-control" ng-model='appVm.user' required placeholder="Username" name='username' ng-minlength=5>
</div>
<div ng-hide="test.$pristine">
<div ng-show="test.username.$error.required" class="alert alert-danger" role="alert">this is a required field</div>
<div ng-show="test.username.$error.minlength" class="alert alert-danger" role="alert">At least 5 chars</div>
</div>
<button class="btn btn-primary" ng-show='test.$touched || test.$valid'>submit</button>
</my-form>
</body>
JavaScript:
angular.element(document).ready( function() {
// generate module
myModule = angular.module( 'myApp',[]);
// define a simple controller and put the user's name into the scope
myModule.controller('SampleController', ['$scope', function ($scope) {
$scope.user = {
name: 'Hugo'
};
}]);
// make the form directive (just put the two attributes in the form...)
myModule.directive('myform', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<form ng-attr-name="{{name}}" autocomplete="off" novalidate=true>' +
'<fix-transclude></fix-transclude>' +
'</form>',
scope: {
name: '#'
},
link: function (scope, elm, attr, contrl, transclFn) {
scope.$parent[scope.name] = scope[scope.name];
// attach the parent scope (originating one!) to the transcluded content!
transclFn(scope.$parent,function (clone) {
elm.find('fix-transclude').replaceWith(clone);
});
}
}
});
// bootstrap AngularJS
angular.bootstrap(document, ['myApp']);
});
I was using ng-include on a few of my pages, however I had to stop using ng-include because it was breaking the angular-ui datepicker. I opened this Github bug.
I am wondering if anyone else was having issues with directives not functioning the same way when being used as part of a ng-include.
Is there a way to make the datepicker work as expected as part of a ng-include?
Here is a plunker showing how it is broken. http://plnkr.co/edit/AboEJGxAK3Uz76CfpaZ0?p=preview
Here is the html that works when on the view, but does not work when part of a ng include.
<div class="row">
<div class="col-md-2">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="yyyy/MM/dd" ng-model="something.dt2" is-open="secondCal"
min-date="minDate" name="secondCal" max-date="'2015-06-22'" datepicker-options="dateOptions"
date-disabled="disabled(date, mode)" ng-required="true" close-text="Close"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" style="line-height: 1.2em" ng-click="open($event, 'secondCal')">
<i class="ss-icon ss-calendar"></i>
</button>
</span>
</p>
</div>
</div>
Here is the JS from the controller.
$scope.open = function ($event, elementOpened) {
$event.preventDefault();
$event.stopPropagation();
$scope[elementOpened] = !$scope[elementOpened];
};
And two ways I was doing ng-include
<div ng-include src="'dist/partials/myPartial.html'"></div>
<div ng-include="'dist/partials/myPartial.html'"></div>
Update
I found that this is because the ng-include directive creates a new scope for each include. This SO post creates a new directive that does the include without creating a new scope. However there seems there "should" be a way to fix it without using a different include.
The datepicker will be unable to open as soon as the is-open is changed by the datepicker directive itself (e.g. click outside to close the datepicker).
This is a common issue regarding the "Prototypal Inheritance" characteristic of scope.
For a complete detail, you could read this: Understanding-Scopes
You could easily solve this by not storing any primitive values directly into $scope, but some object and using dot-notation like this:
<input type="text" class="form-control"
datepicker-popup="yyyy/MM/dd" ng-model="something.dt2"
is-open="model.secondCal"
and in your controller:
app.controller('MainCtrl', function($scope) {
$scope.model = {};
$scope.open = function($event, elementOpened) {
$event.preventDefault();
$event.stopPropagation();
$scope.model[elementOpened] = !$scope.model[elementOpened];
};
});
Example Plunker: http://plnkr.co/edit/dJNIwSz2Uot3McmIMhd4?p=preview
I've created Plunker to debug it but it works fine with your code
http://plnkr.co/edit/nxYCiwRqdWMOkfZoRhGY?p=preview
<body ng-controller="MainCtrl">
<div ng-include="'partial.html'"></div>
</body>
after clarification and further tests i see that calendar with ng-include lose the scope when triggering the change not by scope method, the easy workaround would be as per this plunker
http://plnkr.co/edit/nxYCiwRqdWMOkfZoRhGY?p=preview
Don't remember which one of the angular team said it but if you don't have a dot in your model you are doing it wrong.
a little explanation why it works:
if you do
$scope.valueName = value
it will assign value to the current scope immediately
but if you do
$scope.something.valueName = value
what will happen is that angular will first locate $scope.something, if it doesn't exists on current scope it will look in parent (as long as you don't work in isolated scope)
then when it finds it it will assign value or return something like 'cant find valueName of undefined'
from the angularjs documentation:
https://docs.angularjs.org/api/ng/directive/ngInclude
This directive creates new scope.
This directive executes at priority level 400.
I'm trying to get started learning AngularJS for an Ionic app I'm working on, and I'm having a little trouble understanding AngularJS having had most previous experience on jQuery which focuses on DOM manipulation rather than frameworking.
If I have the following markup:
<label class="item-input-wrapper">
<i class="icon ion-ios7-chatbubble placeholder-icon"></i>
<input type="text" placeholder="Send a message...">
</label>
<button class="button button-clear button-positive">
Send
</button>
How can I pass the value of the input on to the controller when enter or send is clicked? I'm working on a chat app, so I believe that a model approach is needed so that the message thread can be automatically updated but other than that I have no idea.
Could someone help me out or at least point me in the right direction?
There are several ways to pass value to your controller. Here is the simplest example. As Justin said, you should look into angular basics.
HTML:
<div ng-controller="MyCtrl">
<input type="text" ng-model="foo" placeholder="Enter something" />
<input type="button" ng-click="doSomething()" value="Send" ng-disabled="foo == null" />
</div>
JS:
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.foo = null;
$scope.doSomething = function () {
alert("Hello, " + $scope.foo);
}
}
Here is the working fiddle
And I would recommend you to go through this post.
The following markup you posted is the view. What you'll need to do is write a separate JS script called a controller that gets linked to the view. Look into basic Angular tutorials on how to do that and mainly look into how $scope works in Angular.
Your controller will have a function...say:
$scope.foo = function(input) { alert(input); };
From the view, you can pass a value onto the controller with Angular functions such as ng-click.
<a ng-click="foo('this string will be passed in as the text of the alert')">click me</a>