I'm struggling to find an elegant way of passing an external/out-of-scope/non angular function into a directive. So far the only way I could see to do this was by passing the function name as a string into angular and then using eval which does not seem so nice. Here is an example of that on Plunker
http://plnkr.co/edit/L9GGDkxwh4IGNufXB8yg?p=preview
Using extFunc:'&' in the scope only work for functions in the scope so that does not work for me.
Is there a better way of doing this? I realise in my example one would just include the function inside the directive or controller but this is not always practical.
<script>
function nonAngularFunction( someText ) {
alert( someText );
}
<script>
<my-directive ext-func="nonAngularFunction('Hi there')" ></my-directive>
Sorry if this question has already been asked but I could not find a solution anywhere.
I'd import this function in Angular's world as a service:
/* global nonAngularFunction:false */
angular.module('aModuleName', [])
.value('nonAngularFunction', nonAngularFunction);
Then you can use injection and import the function to your scope:
// ... in your directive or controller
angular.module('maybeAnotherModule', ['aModuleName'])
.controller('YourController', ['$scope', 'nonAngularFunction',
function ($scope, nonAngularFunction) {
$scope.nonAngularFunction = nonAngularFunction;
}]);
Should work (I haven't tested it).
Edit: Alternative when there are dozens of external functions
eval is not a practical solution, since it doesn't have access to your scopes.
You could either import lots of your functions into Angular, e.g.
angular.module('aModuleName', [])
.value('myHorribleLib', {
'oneFunction': oneFunction,
'Constructor1': Constructor1,
...
});
Then you have to make them available by $scope.legacyLib = myHorribleLib and use it with a prefix: ng-click="legacyLib.oneFunction('hello')". A bit verbose, but you keep your eyes of what is legacy.
Or you just extend $rootScope (or a top-level scope) with the methods:
angular.module('aModuleName', [])
.run(['$rootScope', '$window', function ($rootScope, $window) {
'oneFunction Constructor1 anotherFunction ...'
.split(' ').forEach(function (f) {
$rootScope[f] = $window[f];
});
}]);
I'm not saying this is a super-clean solution, but this way you could expose the functions of your legacy library to Angular scope. They should now work in normal ng-click and the like and have access to your scope.
Related
I'm pretty new to Angular and am trying to figure out what's wrong here. There is a controller defined like this:
(function(){
function myController($scope, CommsFactory) {
$scope.doSomething = function() {
var x = $scope; // <- Doesn't work because $scope is not defined
}
}
angular
.module('aModule')
.controller('myController', myController);
})();
The doSomething() method is then called by a button click like:
<input type="button" ng-click="doSomething()" class="btn--link" value="do it"/>
This seems straightforward to me but the problem is that, when I break within the method, $scope is not defined. This is different from most of the examples I've seen, and I can't figure out why. Shouldn't it be visible here? Obviously a lot of code is missing - I've tried to show only the relevant bits - could I be missing something somewhere else?
You're declaring a module then you need to add [].
Something like this:
angular.module('aModule', [])
.controller('myController', myController);
Usage
angular.module(name, [requires], [configFn]);
Arguments
name.- The name of the module to create or retrieve.
requires (optional).- If specified then new module is being created. If unspecified then the module is being retrieved for further
configuration.
configFn (optional).- Optional configuration function for the module. Same as Module#config().
Please, I would to recommend to read this guide about Angular Module:
angular.module
(function() {
function myController($scope) {
$scope.doSomething = function() {
var x = $scope;
console.log(x);
}
}
angular
.module('aModule', [])
.controller('myController', myController);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-app="aModule">
<div data-ng-controller="myController">
<input type="button" ng-click="doSomething()" class="btn--link" value="do it" />
</div>
</div>
Your code is generally working fine as demonstrated in this fiddle.
Your main problem seems to be in the usage of $scope. $scope is an object containing all variables and methods which should be available in the corresponding template. For this reason, you would always reference a member of $scope, instead of the whole object.
Furthermore, John Papas AngularJS style guide recommends the usage of controllerAs in favor of $scope for multiple reasons as stated in Y030
By convention, you should also give your controllers uppercase names and use explicit Dependency Injection
A typical use case would rather look like:
(function(){
angular
.module('aModule', [])
.controller('myController', MyController);
MyController.$inject = ['$scope', 'CommsFactory'];
function MyController($scope, CommsFactory) {
var vm = this;
vm.doSomething = doSomething;
function doSomething() {
var $scope.x = "Did it!";
}
}
})();
SOLVED: It turns out that what I was experiencing had something to do with the way in which the Chrome debugger works. It appears to do some kind of lazy loading of variables defined outside of the function in which you break (or at least this as far as I've characterized it). What this means, at least in my case, is that if I break inside of the method, and $scope isn't actually used within that method (which, unfortunately, I was doing a lot because I was trying to verify that $scope was visible), then the debugger will report that $scope is unavailable.
On a page, I have several Angular modules.
For each module I define a constant which contains the version of the module.
var module1 = angular.module('module1').constant('version', '1.2.3');
var module2 = angular.module('module2').constant('version', '2.0.0');
...
I though a constant was defined inside a module. But when I use the constant inside module1, the value I get is '2.0.0'...
Is there a way to define a constant (or anything else) which is proper to a module ?
Edit: for alternative solutions, could you please explain how to use it, for example in a controller declaration ?
module2.controller('myCtrl', function( $scope, $http, $q, ..., version ){
// Here I can use the constant 'version'
}
A very good question. I could think of a quick fix for this:
angular.module('module1').constant('module1.version', '1.2.3');
angular.module('module2').constant('module2.version', '2.0.0');
I don't know how much it suits your needs. But I hope it helps.
The main problem is the naming in Angular. You cannot have the same name for services/constants/values etc. They will be overwritten. I solved this problem with "namespaces" like I showed you above.
Example of injecting namespace like names: http://codepaste.net/5kzfx3
Edit:
For your example, you could use it like so:
module2.controller('myCtrl', ['$scope', '$http', '$q', ... , 'module2.version'], function( $scope, $http, $q, ..., version ){
// Here I can use the constant 'version'
}
That happens because module2 is overriding module1 constant.
You can use moduleName.version as a name, but it's not possible to use the same name.
I'm writing a directive and trying to stick to the John Papa style guide. So I've also decided to jump on the ControllerAs syntax wagon and I've got a tiny directive like below:
(function() {
angular
.module('htApp')
.directive('newsletterSignup', newsletter_signup);
function newsletter_signup($http) {
var directive = {
templateUrl: '../whatever.tpl.html',
restrict: 'EA',
controller : controller,
controllerAs: 'vm',
bindToController: true
};
return directive;
controller.$inject = ['$http'];
function controller($http) {
var vm = this;
// $http is here ... all is good, but I don't need it
function doSubmit(form) {
// I need $http here, but it is null
debugger;
};
vm.doSubmit = doSubmit;
}
}
})();
This is a newsletter signup service. I'm going to have to do an HTTP request, therefore I'm injecting it into the controller. All is fine - but calling the vm.doSubmit(--formname-here--) function from the template results in me not being able to find the $http service.
So my question: how can I access the injected $http from the doSubmit() function?
EDIT
I'll include the view code - but no worries - the plumbing works:
<button class="btn btn-yellow" ng-click="vm.doSubmit(newsletterform)" translate>
footer.ok_button_text
</button>
EDIT 2
As it turns out, #Tek was right - the code works. I think the reason I didn't see it was because (I think) the JS runtime in Chrome optimizes the $http away when it knows it's not going to be called.
This code works fine. I think this is because the runtime aniticipated the usage of $http in the console.log() function call. However - if I remove that line I get this ( which was why I had this problem in the first place ):
Notice that I commented out the console.log - thus the doSubmit() call never uses $http. Now - when I call $http in the console - it's not defined.
The problem is here:
return directive;
controller.$inject = ['$http'];
function controller($http) {
...
controller function is defined when return statement is executed. But controller.$inject will never be defined. Also, newsletter_signup function misses the corresponding $inject.
This won't be minified properly. While this will be minified.
Your example works just fine: example.
But as for me John Papa has pretty strange vision of angular style, I prefer this style guide.
I am trying to re-write an application using angular.js, but I still do not see how it works (more or less).
For instance. In my previous code, I had a function that was executed once everything was loaded that initialised variables, accessed for everyone in a window.variable style.
Now I want the same here.
My idea was to build a factory to return an object with all the variables, and then make that everyone had access to this object (somehow).
The questions are:
1- Am I right? I should initialise variables through a factory?
2- And how can I "call" this factory-method at the beginning of the code? with the module.run function?
Cheers,
I'd probably put the variables in a service, and then inject that into a wrapping angular controller. Any other controllers that you would want to have access to these 'global' variables would be nested under the wrapping controller, thus inheriting the variables.
var app = angular.module("app", []);
app.service("globVars", function () {
var vars = {};
vars.someVar = "a";
vars.someOtherVar = "b";
return vars;
});
app.controller("WrappingCtrl", function ($scope, globVars) {
$scope.globVars = globVars;
});
app.controller("NestedCtrl", function ($scope) {
console.log($scope.globVars.someVar); // => "a"
});
<html ng-app="app">
<div id="wrapper" ng-controller="WrappingCtrl">
<div class="nested" ng-controller="NestedCtrl">
{{globVars.someVar}}
</div>
</div>
</html>
I think you should avoid using global variables as much as you could
But if you have to use them, you are right, you can initialize them in module.run and add the module in the app dependencies
If you want to access some variables, you must see to $rootScope, because you can access $rootScope everywhere.
Also you must use angular config and run functions.
I advice to you don't use global scope, because it affects on app speed, memory leaks etc.
I'm starting with Angular.js and have a question; What's wrong with the second way to express the controller? Take a look at the jsfiddle below
http://jsfiddle.net/yDhv8/
function HelloCtrl($scope, testFactory, testFactory2)
{
$scope.fromFactory = testFactory.sayHello("World");
$scope.fromFactory2 = testFactory2.sayHello("World");
}
myApp.controller('GoodbyeCtrl', ['$scope', 'testFactory', 'testFactory2', function($scope, testFactory, testFactory2) {
$scope.fromFactory = testFactory.sayGoodbye("World");
$scope.fromFactory2 = testFactory2.sayGoodbye("World");
}]);
Any references that may be useful to understand what's going on will be appreciated,
Cheers,
Everything is fine. you just got confused. use app.controller
myApp is the module name, not a variable name.
app.controller(......)
If you run this in a javascript debugger, you'll find that the variable 'myApp' is not defined. You can either use the 'app' reference that you assigned to the original module call, or use the following syntax:
angular.module('myApp').controller(...)