Angular binding directive scope to controller scope - angularjs

I am creating a forgot password function for an application I am building and so I built a function that sends an $http request to a PHP resource that sends the user a reset email. When the request resolves than I display a twitter bootstrp modal using an angular directive that tells the user if their email was successfully sent or not.
The issue I am having is that the scope of the controller and the directive seem different as updating the feedback in my controller does not update the output of the directive.
My controller:
angular
.module('enigma.auth')
.controller('Auth', Auth);
Auth.$inject = ['authFactory'];
function Auth(authFactory) {
var auth = this;
auth.forgotPassword = forgotPassword;
auth.forgotPasswordFeedback = '';
function forgotPassword(email) {
if(email) {
authFactory.forgotPassword({ email: email })
.then(function(res) {
auth.forgotPasswordFeedback = res.message; // auth.forgotPasswordFeedback is set to the correct value now, however this isn't reflected in the directive.
$('#forgotPassword').modal();
});
}
};
}
My directive:
angular
.module('enigma.forgotPasswordDirective', [])
.directive('forgotPasswordDirective', forgotPasswordDirective);
function forgotPasswordDirective() {
return {
bindToController: true, // I thought this told the directive to use the controllers scope instead of an isolated scope...
controller: 'Auth',
controllerAs: 'auth',
templateUrl: 'modules/auth/forgotPassword.html'
}
}
The template:
<div class='modal fade' id='forgotPassword'>
<div class='modal-dialog'>
<div class='modal-content'>
<button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>
<div class='row'>
<div class='col-xs-12'>
<h3>Password Recovery</h3>
<p>
{{auth.forgotPasswordFeedback}} <!-- this value is never updated :( -->
</p>
<p>
<button class='btn btn-primary' data-dismiss='modal'>Close</button>
</p>
</div>
</div>
</div>
</div>
</div>
Lastly I have this all included inside my registration template which is bound to the registration controller (Note: Don't the forgot password directive is bound to the auth controller), here's the relevant bits.
<div class='form-group'>
<label for='email'>Email:</label>
<input class='form-control' name='email' ng-model='reg.user.email' required type='email' ng-blur='reg.alreadyRegistered()' />
<div ng-messages='reg.regForm.email.$error' ng-if='reg.regForm.email.$dirty'>
<div class='error' ng-message='userExists'>This user already exists. <a href='#' ng-click='auth.forgotPassword(reg.user.email)' title='Forgot password' ng-controller='Auth as auth'>Forgot password?</a></div>
</div>
</div>
...
<forgot-password-directive></forgot-password-directive>

It works, for example, if you wrap the directive inside the controller:
<div ng-controller='Auth as auth'>
<div class='form-group'>
...
<a href='#' ng-click='auth.forgotPassword(reg.user.email)' title='Forgot password'>Forgot password?</a>
</div>
<forgot-password-directive></forgot-password-directive>
</div>
However, in addition, I would use a simpler approach and define the directive followingly:
function forgotPasswordDirective() {
return {
scope: false,
templateUrl: 'modules/auth/forgotPassword.html'
}
}
If you are familiar with Node, you may clone and run the example from here: https://github.com/masa67/NgBind.

Related

Add/Remove class if checkbox is checked/unchecked (AngularJS)

I need to toggle a class if a checkbox is checked/unchecked with AngularJS. My Code is working on Plunker but in my controller there is no functionality.
Here is the Plunker: Click!
My Controller looks like this:
angular.module('inspinia')
.controller('MainController', function ($location, $scope, $state) {
// initialize iCheck
angular.element('.i-checks').iCheck({
checkboxClass: 'icheckbox_square-green',
radioClass: 'iradio_square-green'
});
//Checkbox functionality
$scope.isChecked = function() {
if ($scope.checked) {
return false;
}else{
return true;
}
};
});
HTML
<div class="panel panel-default confirm-panel accept-terminplanung">
<div class="panel-body text-muted ibox-heading">
<form action="#" method="post" role="form">
<div class="i-checks" iCheck>
<label class="">
<div class="icheckbox_square-green">
<input type="checkbox" class="i-checks" ng-model="checked">
</div>
<p class="no-margins">Hiermit akzeptiere ich die Bedingungen des geschlossenen Dozentenvertrages und nehme den Bildungsauftrag für die ausgewählten Ausbildungen verbindlich an.</p>
</label>
</div>
<button type="button" name="termin-bestaetigen" class="btn btn-w-m btn-warning submit-appointment disabled" ng-class="{disabled: isChecked()}">Terminplanung übernehmen</button>
</form>
</div>
</div>
What am I doing wrong?
As I can understand from the provided piece of code iCheck is something that is not working in angular scope, like JQuery. So every time your value inside this component changes you need to trigger a $digest cycle with a use of $scope.$digest() or $scope.$apply().
In iCheck documentation you can find callbacks section, so you can subscribe to that event and do $scope.$apply() in it.
For example, inside your controller:
<your-iCheck-element>.on('ifChecked', function(event){
$scope.$apply();
});
Your function is basically doing nothing, but giving back the opposite value of checked. You can simply use negation of your model.
ng-class="{disabled: !checked}"

Angular UI: field entered in Modal is undefined in controller

Please run this plunker, if you enter a value in the modal and then click on show value, the value is undefined in $scope, how to get the value?
HTML
<div ng-app="app" ng-controller="myCtl">
<input type="button" ng-click="openModal()" value="Open Modal">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h4 class="modal-title">The Title</h4>
</div>
<form name="myForm" novalidate>
Enter a value <input type="text" ng-model="someField" />
</form>
<div class="modal-footer">
<button ng-click="showValue()">Show value</button>
</div>
</script>
</div>
Javascript
var app = angular.module('app', ['ui.bootstrap']);
app.controller('myCtl', function($scope,$uibModal) {
$scope.openModal = function() {
$scope.modalInstance = $uibModal.open({
animation: false,
templateUrl: 'myModalContent.html',
scope: $scope
});
};
$scope.showValue = function() {
alert('value entered=' + $scope.someField);
};
});
You are seeing this because the scope of the modal is isolated. Even though you pass the $scope to modal. It still does not use the same $scope.
For your example the following would work.
Update the modal template:
<button ng-click="showValue(someField)">Show value</button>
Update your controller showValue method as follows:
$scope.showValue = function(theValue) {
$scope.someField = theValue;
alert('value entered=' + $scope.someField);
};
Really though the best way to use the modal is to use the modal instance created by the open method to track the close and dismiss events and handle the result that way. Take a look at the example on on the ui-modal section of the ui-bootstrap documentation

How to get the form data when the form is in a directive in Angular?

I have this this template:
<div class="modal" id="popupModal" tabindex="-1" role="dialog" aria-labelledby="createBuildingLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="createBuildingLabel">{{ title }}</h4>
</div>
<form data-ng-submit="submit()">
<div class="modal-body" data-ng-transclude>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-ng-click="visible = false">Annuleren</button>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-save"></span>Maken</button>
</div>
</form>
</div>
</div>
</div>
and here's the directive:
app.directive("modalPopup", [function () {
return {
restrict: 'E',
templateUrl: 'Utils/ModalPopup',
scope: {
title: '#',
onSubmit: '&',
visible: '='
},
transclude: true,
link: function (scope, element, attributes) {
var container = $("#popupModal");
scope.submit = function (newGroup) {
scope.onSubmit(newGroup);
}
scope.hide = function () {
container.modal('hide');
}
scope.show = function () {
container.modal('show');
}
scope.$watch('visible', function (newVal, oldVal) {
if (newVal === true) {
scope.show();
}
else {
scope.hide();
}
})
}
}
}]);
As you can see I have declared my form tag inside the directive and I also use transclude to determine how my form is going to look like. For now I have this:
<modal-popup title="Nieuwe groep aanmaken" data-on-submit="createGroup()" visible="showAddGroupForm">
<div class="row">
<div class="col-md-3">Kopieren van:</div>
<div class="col-md-8">
<select class="form-control" data-ng-model="newGroup.Year">
<option value="">Nieuw jaar</option>
<option data-ng-repeat="year in years" value="{{year.Id}}">{{year.Name}}</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-3">Naam</div>
<div class="col-md-8">
<input type="text" class="form-control" data-ng-model="newGroup.Name" />
</div>
</div>
</modal-popup>
When the submit button is pressed, I want the data to be available in my controller.
I ques the data isn't available because of the isolated scope, however I'm not sure. What do I need to do to get the data back from the directive into my controller?
PS: I know about angular-ui and angularstrap, but I'm doing this to learn about Angular.
EDIT:
Here's a Fiddle
I think the cause is a misunderstanding about how scopes work (especially with transclusion).
Let's start with this code (from the fiddle):
<div ng-controller="MyCtrl">
<my-popup on-submit="formSubmitted(model)">
<input type="text" ng-model="model.Name"/>
</my-popup>
</div>
Since <my-popup> transcludes its content, the scope above is that of MyCtrl, even in the content of the directive. By content I mean the <input>, NOT the directive template, i.e. the <div><form ... code.
Therefore it is implied that model (as used in ng-model="model.Name") is a property of the scope of MyCtrl, as is formSubmitted(). Since both are members of the same scope, you do not need to pass the model as argument; you could just do:
(in the template:)
<my-popup on-submit="formSubmitted()"><!-- no `model` argument -->
(the controller:)
function MyCtrl($scope) {
// I like declaring $scope members explicitly,
// though it can work without it (charlietfl comments)
$scope.model = {};
$scope.submittedValue = null;
$scope.formSubmitted = function() {
// another bug was here; `model` is always a member of the `$scope`
// object, not a local var
$scope.submittedValue = $scope.model.Name;
}
}
Another bug is in the directive code:
link: function(scope, element, attributes){
scope.submit = function(){
scope.onSubmit({model: model});
}
}
The variable model (not the name model:) is undefined! It is a property of the parent scope, so you would have a chance if the scope was not isolated. With the isolated scope of the directive, it may work with an awful workaround that I am not even considering to write :)
Luckily, you do not need the directive to know about things happening in the external scope. The directive has one function, to display the form and the submit button and invoke a callback when the submit button is clicked. So the following is not only enough for this example, but also conceptually correct (the directive does not care what is happenning outside it):
link: function(scope, element, attributes){
scope.submit = function(){
scope.onSubmit();
}
}
See the fiddle: http://jsfiddle.net/PRnYg/
By the way: You are using Angular v1.0.1. This is WAAAAY too old, seriously consider upgrading!!! If you do upgrade, add the closing </div> to the template: <div ng-transclude></div>.

How to access a dom element in a Angular directive

I am trying to attach a keyup event to a directive in my Angular project. Here is the directive:
angular.module('clinicalApp').directive('chatContainer', function() {
return {
scope: {
encounter: '=',
count: '='
}
templateUrl: 'views/chat.container.html',
link: function(scope, elem, attrs) {
scope.count = 500;
}
};
});
And here is the html from the template:
<div class="span4 chat-container">
<div class="chat-body">
<form accept-charset="UTF-8" action="#" method="POST">
<div class="text-area-container">
<textarea id="chatBox" class="chat-box" rows="2"></textarea>
</div>
<div class="button-container btn-group btn-group-chat">
<input id="comment" class="btn btn-primary btn-small btn-comment disabled" value="Comment" ng-click="addMessage()"/>
</div>
</form>
</div>
</div>
</div>
I want to access the chatbox in my link function and attach the keyup event to it. I know I can get it with jQuery, but that cannot be the Angular way. What is the proper way to grab that element from the dom?
You can easily do it with Angular' element' find() method:
var chatbox = elem.find("textarea"); // Finding
chatbox.bind("keyup",function(){ // Binding
console.log("KEYUP!")
})
Live example: http://jsfiddle.net/cherniv/S7XdK/
You can use element.find(yourSelector) as previously mentioned, but it is better to use ngKeyUp, similar to how you would use ngClick:
https://docs.angularjs.org/api/ng/directive/ngKeyup

How do I add an object to a nested array in AngularJS?

I'm new to AngularJS. I've researched the theory behind ng-repeats but I cannot find any good examples of 2-way data binding or object creation for nested ng-repeats. I understand that the syntax has changed in recent versions. I'm using 1.1.5.
I have post objects that have a nested array of comment objects. I want to be able to add new comment objects to the array of comments in the post. I've tried lots of different versions.
This is my HTML:
<div id='posts' ng-controller="PostsCtrl">
<ul>
<div id='post' ng-repeat="post in posts track by post.id">
<div id='postMedia'>
<img ng-click="" ng-src={{post.attachment}} />
</div>
<div ng-click="">
<div ng-click="">{{post.message}}</div>
<div ng-click="">{{post.created_at}}</div>
</div>
<h1>Comments</h1>
<div id='comment' ng-repeat="comment in post.comments track by post.comment.id">
<div ng-click="">{{comment.body}}</div>
</div>
<form ng-submit="addComment()">
<input id="body" type="text" placeholder="Reply…" ng-model="post.comment.body">
<input type="submit" value="Add">
</form>
</div>
</ul>
</div>
This is the controller:
myApp.controller('PostsCtrl', ['$scope', 'angularFire',
function MyCtrl($scope, angularFire) {
var url = 'https://inviter-dev.firebaseio.com/posts';
$scope.items = angularFire(url, $scope, 'posts', {});
$scope.commentProp = 'comments';
$scope.addComment = function() {
$scope.comments.push($scope.newcomment);
}
}
]);
SOLUTION:
Thanks to Chandermani answer for solving this. I modified the controller slightly because I wanted the to use a key called body in the data store -
myApp.controller('PostsCtrl', ['$scope', 'angularFire',
function MyCtrl($scope, angularFire) {
var url = 'https://inviter-dev.firebaseio.com/posts';
$scope.items = angularFire(url, $scope, 'posts', [] );
$scope.addComment = function(post,comment) {
post.comments.push({body:comment});
}
}
]);
The problem with you addComment method is that it does not know which post it needs to add comment to. Also the comment input that is part of you ng-repeat for comments can have a independent model which can be passed to the controller method.
You need to make the following changes. In html
<form ng-submit="addComment(post,commentData)">
<input id="body" type="text" placeholder="Reply…" ng-model="commentData">
<input type="submit" value="Add">
</form>
In your controller
$scope.addComment = function(post,comment) {
post.comments.push(comment);
}

Resources