So, I'm fairly new to AngularJS, and I may be approaching this problem incorrectly, especially in the way I'm using directives.
I have a main app that I would like to place some common code in, like a directive to display some messages on an html page. I have a controller that makes a rest API call to validate credentials, which then assigns some data to $scope. I'd like the directive to use that scope data to display the messages in html and do some formatting. From what I've researched, doing this from the controller is not the best practice. I know I could do this directly in the controller and just put a div in the html like:
<div>{{validation.message}}</div>
The simplified version of the html is:
<html data-ng-app="myApp" data-ng-strict-di>
<body>
<div data-ng-view data-ng-controller="MainController">
<div data-ng-app="StoredAccountsModule" data-ng-controller="AccountController">
<button class="btn btn-default" data-ng-click="modalOptions.verify(newAccount)">Verify Credentials</button>
<div data-myappmessage></div>
</div>
</div>
</body>
</html>
The directive is:
angular.module("myApp").directive("myappmessage", function () {
//✅ : ✖
return {
link: function postLink(scope, element, attrs) {
element.html('<span>' + scope.validation.message + '</span>');
}
};
});
I'm sure I'm missing something on how controllers, modules, and directives all connect together.
UPDATE
Ok, so with everyone's comments, I've worked out a lot of the kinks in what I'm trying to do. Right now I am trying to initiate an account validation from a bootstrap modal using a button click that initiates an API call to return the validation information. The modal is being initiated as a service as seen here: http://weblogs.asp.net/dwahlin/building-an-angularjs-modal-service. The modal window part that matters is:
<div data-ng-controller="MainController">
<div data-simplevalidation></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" data-ng-click="modalOptions.ok(newAccount)">{{modalOptions.actionButtonText}}</button>
<button class="btn btn-warning" data-ng-click="modalOptions.close('cancel')">{{modalOptions.closeButtonText}}</button>
<button data-ng-controller="MainController" class="btn btn-default" data-ng-click="verifyAccount(newAccount)">Verify Credentials</button>
</div>
And the modal is being launched from:
<div data-ng-controller="StoredAccountsIndexController" style="width:auto;text-align:center;">
<table style="border:1px solid black;margin:auto;">
<tfoot>
<tr>
<td colspan="2"><button type="button" class="btn btn-primary" data-ng-click="open()">Add New Stored Account</button></td>
</tr>
</tfoot>
</table>
</div>
The directive is now
angular.module("myApp").directive("simplevalidation", function () {
return {
template: '<button type="button" class="btn btn-default" data-ng-click=verifyAccount(newAccount)>Verify</button><span></span>',
link: function ($scope, element, attrs) {
$scope.$watchGroup(["validation.message", "validation.success"], function () {
if ($scope.validation.success != null) {
if ($scope.validation.success) {
element.children("span").html($scope.validation.message + " " + $scope.validation.symbol);
element.children("span").css("color", "green")
}
else {
element.children("span").html($scope.validation.message + " " + $scope.validation.symbol);
element.children("span").css("color", "red")
}
}
}, true);
}
};
});
Only one app is being declared on the page, myApp. I believe the issue I'm experiencing is with the scope of the modal. If I use the button placed by the directive template, everything works the way it's supposed to. If I use the button in the modal footer, the API call fires, but I never see an update on my scope. The controller that is handling that call is:
angular.module("myApp").controller("MainController", ['$scope', '$timeout', "StoredAccountsFactory", function ($scope, $timeout, StoredAccountsFactory) {
$scope.validation = {}
$scope.verifyAccount = function (account) {
$scope.validation = {};
StoredAccountsFactory.validateStoredAccount(account).success(function (data, status, headers, config) {
$scope.validation.message = "Account credentials verified";
$scope.validation.success = true;
$scope.validation.symbol = "✅"
}).error(function (data, status, headers, config) {
$scope.validation.message = data;
$scope.validation.success = false;
$scope.validation.symbol = "✖"
});
}
}]);
Any other insight as to what I'm doing wrong here?
I've also tried a directive in this format as I believe is the preferred way to handle directive scope, without any change in outcome.
angular.module("myApp").directive("simplevalidation", function () {
//add these attributes to the directive element
//data-validate="verifyAccount(newAccount)" data-validationmessage="validation.message" data-validationsuccess="validation.success"
return {
scope: {
validate: '&',
validationmessage: '=',
validationsuccess: '='
},
template: '<button type="button" data-ng-click=validate()>Verify</button><span></span>',
link: function ($scope, element, attrs) {
$scope.$watchGroup(["validationmessage", "validationsuccess"], function () {
if ($scope.validationsuccess != null) {
if ($scope.validationsuccess) {
element.children("span").html($scope.validationmessage + " " + " ✅");
element.children("span").css("color", "green");
}
else {
element.children("span").html($scope.validationmessage + " " + " ✖");
element.children("span").css("color", "red");
}
}
}, true);
}
};
});
You are misunderstanding some concepts here, and I think it overcomplicates what you are trying to do.
1. Modules: Modules are just a way to organize code using dependencies. Once loaded, it doesn't really matter which module hosted which service/controller/directive.
2. App and ng-app
:
There really should be a single app per page. You can have multiple, but you'd need to manually angular.bootstrap
them, and I'm fairly certain that they cannot be nested. Also, apps don't share the same instances of services or scope, so an app is really an isolated execution unit (from Angular's point of view).
So, this nesting of StoredAccountsModule
app withing myApp
should not be happening:
<html ng-app="myApp" >
<body>
<div data-ng-view data-ng-controller="MainController">
<div data-ng-app="StoredAccountsModule">
...
</div>
</div>
</body>
</html>
3. Controllers:
Controllers define and set the View Model (via $scope
or with Controller As approach). That statement you made:
I know I could do this directly in the controller and just put a div in the html like:
<div>{{validation.message}}</div>
implying that it is against best practices (as opposed to creating a directive) is plainly wrong.
What is discouraged (and frown upon) is manipulating, accessing, or otherwise making any assumptions about the View (i.e. DOM) in the controller. That is because controller only deals with translation and marshaling of data between backend Models and ViewModels.
In other words, the controller deals with the architecture and the logic of the app - not the manifestation of it in the View.
That characteristic of the controller and Dependency Injection (DI) is what makes controllers highly testable.
4. Directives: A directive is meant to be (for the most part) a reusable piece of functionality that deals directly with the DOM. Directives should not make any assumptions (ideally) about it surrounding HTML or the controller.
Angular does allow for directives to use the outer scope, but it makes them less re-usable. In that regard, your directive myappmessage
doesn't provide much value.
Even when authoring a directive, one should not forget about MVVM principles that Angular supports. And as such, directives also have a scope and a controller, and can reuse other and built-in directives for its functionality.
5. Scope: Scope is independent of Modules or Controllers or Directives. So the title of this question "accessing scope from another module" is meaningless. Angular create root scope for an App.
Controllers and Directives share the scope (unless a directive creates an isolate scope), and so a parent controller publishing a variable onto the scope makes the variable available to its subtree via scope inheritance.
Scope inheritance is just one way to communicate between controllers (and some consider it a bad practice), but it's there and can be used (if used properly considering all the constraints of prototypical inheritance nature of the scope).
app.controller("ParentCtrl", function($scope){
var VM = $scope.VM = ($scope.VM || {});
VM.isBusy = false;
})
.controller("ChildCtrl", function($scope, $http){
var VM = $scope.VM = ($scope.VM || {});
$scope.loadSomething = function(){
VM.isBusy = true;
$http.get("something").then(function(d){
VM.isBusy = false;
});
}
});
And in the View:
<div ng-controller="ParentCtrl">
<div ng-show="VM.isBusy">loading...</div>
<div ng-controller="ChildCtrl">
<button ng-click="loadSomething()">load</button>
</div>
</div>
(These controllers, btw, could have just as easily come from different modules)