We have a page that opens a modal dialog with a form like below. However when we hit the controller that should handle the form action, the form object is undefined and I am too much of an Angular newbie to understand why...
This is the parent page controller holds the function to open the modal dialog:
app.controller('organisationStructureController', ['$scope', ..., '$modal', function ($scope, ..., $modal) {
$scope.openInvitationDialog = function (targetOrganisationId) {
$modal.open({
templateUrl: 'send-invitation.html',
controller: 'sendInvitationController',
resolve: {$targetOrganisationId: function () {
return targetOrganisationId;
}
}
}
);
};
on a page like this:
// inside a loop over organisations
<a ng-click="openInvitationDialog({{organisation.id}})">Invite new member</a>
the invitation-dialog html looks like this:
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<!-- ... -->
</div>
<div class="modal-body">
<form name="invitationForm">
<div class="form-group">
<label for="email" style="color:white;">Email</label>
<input type="email" class="form-control" autocomplete="off" placeholder="New member email" id="email" name="email" ng-model="invitation.email" required="true"/>
<span class="error animated fadeIn" ng-show="invitationForm.email.$dirty && invitationForm.email.$error.required">Please enter an email address!</span>
<span class="error animated fadeIn" ng-show="invitationForm.email.$error.email">Invalid email</span>
</div>
<!-- ... -->
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button>
<button type="submit" class="btn btn-primary" ng-click="sendInvitation()">Invite</button>
</div>
</form>
</div>
</div>
</div>
The controller that should handle the invitation is somewhere else:
app.controller('sendInvitationController', ['$targetOrganisationId', '$scope', ...,
function ($targetOrganisationId, $scope, ...) {
$scope.invitation = {
// ...
targetOrganisation: {
id: $targetOrganisationId
}
};
$scope.sendInvitation = function () {
// $scope.invitationForm is undefined
if ($scope.invitationForm.$invalid) {
return false;
}
// send the invitation...
};
}]);
So what's the correct way to get the form scope into the controller?
Maybe I need to inject $modal
into the sendInvitationController
and add the sendInvitation
function to it? But when I do that the action never enters the controller. Or do I have to add the function that handles the submit action to $modal.open({ ...
instead of referencing the controller? Though I'd much prefer to have the sendInvitationController in its own file and scope.
Thanks for any help!
EDIT
We found several things that helped us build a workaround and might help someone answer the question itself:
- the
$scope.invitation
object is not undefined in thesendInvitationController
but holds the correct data, while$scope.invitationForm
remains undefined. - inside the send-invitation.html we can access
$scope.invitationForm.$invalid
and do the validation right there:<button type="button" ng-click="sendInvitation()" ng-disabled="invitationForm.$invalid">Invite</button>
So the question is: why does the binding of the invitationForm
object to the $scope
fail on submit while the form model binds correcetly?
I got mine to work like this:
No form name, no
$parent
. I'm using AngularUI Bootstrap version 0.12.1.I was tipped off to this solution by this.
I had the same issue and could solve it by defining the form object in the scope of the modals controller. To get your code working put, for example,
$scope.form = {};
in the beginning of your controller and change your form tag to<form name="form.invitation">
. Afterwards$scope.form.invitation.$invalid
should be filled.The answer to the question of "Why?" is "scoping". tl;dr you created a new scope with the modal dialog which hid the scope's form object from your controller.
If we simplify your code, we roughly get the following:
(This is a very simplified version which should still have all of the core components.) Now, let's look at where scopes are created and what is injected in them.
Here, you can see that there is a new child scope created at the
<modal-dialog>
element and that is where theinvitationForm
object is actually added. That is why you can't see the object in thesendInvitationController
but you can see it on the buttons forng-disabled
. If you want to be able to access the form construct outside of the<modal-dialog>
element (e.g. in thesendInvitationController
) you will need to pass that in the function call:With the controller accepting the invitation form as a parameter to the
sendInvitation
function:@Robin identified the other solution, specifically to create an object rooted in the scope of the
sendInvitationController
and then attach the form directly to that object, relying on Angular's scope traversal mechanism to find theform
object on the scope outside of the<modal-dialog>
and attach the form object to that. Note that if you did not specify$scope.form = {}
in thesendInvitationController
, Angular would have created a new object forform
on the scope for the<modal-dialog>
and you still would not have been able to access it in thesendInvitationController
.Hopefully this helps you or other people learning about Angular scoping.
Update Nov 2014: starting from angular-ui-bootstrap
0.12.0
transclusion scope is merged with the controller's scope. There is no need to do anything.Before 0.12.0:
To put
invitationForm
directly in your parent controller scope you need to bypass transcluded scope this way:Above will automaticaly create form object in your parent controller. No need for pre-initialization stuff, long object paths or passing by event. Just access it with
$scope.invitationForm
once modal is opened.