AngularJS function declaration, positioning and nesting - javascript

I'm new to AngularJS and I read you can declare function in 2 different ways (perhaps more...):
First:
var myApp = angular.module('myApp', []);
myApp.controller('mainCtrl', function($scope){
$scope.message = 'Yes';
})
myApp.controller('anotherCtrl', function($scope){
$scope.message = 'No';
})
Second:
var myApp = angular.module('myApp', []);
myApp
.controller('mainCtrl', mainCtrl)
.controller('anotherCtrl', anotherCtrl)
function mainCtrl($scope){
$scope.message = 'Yes';
}
function anotherCtrl($scope){
$scope.message = 'No';
}
Using the first method I was able to use different files (i.e.: controllers.js with all the Controllers, directives.js with all directives, etc...).
I tried using the second method and gives error if functions are declared in different files, which make sense because they are called in one file but . On the other hand it's more readable to me as there is less nesting and so forth.
What is the difference?

What is the difference?
Your First Example
In the first example, you're creating the functions via function expressions as part of your calls to myApp.controller.
It also happens that in your example, the functions are anonymous (they don't have names), but you could make them named if you wanted (unless you need to support IE8 or IE legacy modes that equate to IE8 or earlier):
myApp.controller('mainCtrl', function mainCtrl($scope){
// Gives it a name -------------------^
$scope.message = 'Yes';
});
(This article on my anemic little blog explains why there are issues with that on IE8 and earlier.)
Since the functions don't have anything referring to them except whatever .controller hooks up, you can't use them elsewhere unless you can get references to them back from myApp, or if you declared a variable and assigned it within the expression making the call:
var mainCtrl;
// ...
myApp.controller('mainCtrl', mainCtrl = function mainCtrl($scope){
$scope.message = 'Yes';
});
// ...you could use the `mainCtrl` variable here if you needed
// to reuse the function
Your Second Example
In the second example, you're creating the functions via function declarations, and then referring to those functions in your calls to myApp.controller. The functions have names (they're not anonymous). You could use those functions in more than one place, if it made sense to, without doing the variable thing shown above.
In your second example, you could declare the functions in separate files, but in order to use them in your call to myApp.controller, you need to get a reference to them somehow. There are a large number of ways you can do that, from RequireJS to SystemJS to ES2015 modules to Angular modules (I think) or any other AMD mechanism.

Related

Scope variable not visible within ng-click handler

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.

AngularJS - split controller functions in multiple files

I'm new in AngularJS and I'm doing a refactor of an AngularJS application and I noticed that there is a single controller file with a lot of functions that manipulate and set scope variables.
Following an example:
test.controller('testCtrl', function testCtrl($scope) {
$scope.init_filters = function() {
$scope.filter_1 = [];
$scope.filter_2 = [];
$scope.filter_3 = [];
$scope.filter_4 = [];
$scope.filter_5 = [];
};
$scope.showChanges = function() {
if ($scope.item_list.length > 0) {
$scope.messages = [];
for (var i = 0; i < $scope.item_list.length; i++) {
$scope.messages.push($scope.item_list[i].message);
}
$scope.another_function();
}else{
// other stuff
}
};
//other functions like these
}
So, I would like to split these functions in multiple JS files. I searched about this problem and I found that in a lot of case is used a service. But I think that this is not my case, because I need to working on directly on the controller's scope.
I mean, I don't want a separated function that get as parameters some scope variables and return the variable.
So, what is the best practices for doing something like this? is it possible?
If you want to use multiple files then split the definition to multiple files by passing the scope to another method and then define the rest of methods there.
File1
app.controller('CtrlOne', function($scope){
app.expandControllerCtrlOne($scope);
});
File2
app.expandControllerCtrlOne = function($scope)
{
}
Check this video
As you said the code you found for controller is large one so there are multiple ways in angular js that you can implemented the separation of code.
I will suggest you to go with following approach:
Use service to add those code in it which you need in other places as well and you know that this code does not require scope object..
Use factory to add some Utility kind of functions. The collection of logic which does not require scope object again...
As controller code is too large, I think View/UI of same also being as per wrote...
So for this you can go with creating directives for section in view..
Where-ever you think this peace of view section can be separate and standalone logical functionality that you can move into directive.
There are three ways to create directive with scopes:
A. Shared Scope B. Isolated Scope C: shared and Isolated scope
In this ways may you can at-least make your controller code readable and looks modular.
Let say::
module.controller('longRunController', function() {
#TYPE 1 code
// some code which fetch dat from API
// some code which save variable and objects which can used in another controller or directives
// some code which need to passed to other controller even after route changes
#TYPE 2
// some code which is only related this controller but has some bussiness logic
// some code which is a kind of utility functino
#TYPE 3
// all $scope related variable $watch, $scope and related variables
// some code of perticular section of which VIEW which handle by this controller
});
Consider in above patter your controller code has:
So type 1 code can be moved to Service
type 2 code can be moved to factory
type 3 code can be move to directives
you can pass $scope as a parameter to the external function.
And because you just use the objectreference, all changes you made in your external functions are made on the $scope object from your controller.
test.controller('testCtrl', function testCtrl($scope)
{
showChanges($scope);
});
...
function showChanges(scope)
{
scope.param1 = "Hello World";
}

Are JavaScript anonymous function parameter names significant?

I'm going through this Angular tutorial and have noticed that variables seem to be freely added to functions as needed. I figured up to this point that there were just conventions but am I mistaken? Consider the last file:
app.controller('PostsCtrl', function ($scope, $location, Post) {
$scope.posts = Post.all;
$scope.post = {url: 'http://'};
$scope.submitPost = function () {
Post.create($scope.post).then(function (ref) {
$location.path('/posts/' + ref.name());
});
};
Here "$location" was added to function() between $scope and Post. Is $location the only option for the 2-nd parameter in an anonymous function here with 3 parameters or is angular somehow looking at the name of the 2nd parameter and deducing that it needs to inject $location there? Where in the documentation can I see all the conventions for 1, 2, 3, etc parameter versions of this function?
This code doesn't appear to work btw. Post is undefined.
With angular, the names are significant; in plain javascript, not really.
With that, however, if you wanted them to be insignificant in the example above, you could do:
app.controller('PostsCtrl', ['$scope','$location', 'Post',
function (foo, bar, foobar) {
....
}
]);
In which case you're mapping the first, second, and third parameters to $scope, $location, and Post respectively. This is actually a better way to do this, as when you minify with angular, it is going to change the names of those parameters, and it is going to break it.
In this case, you could order those parameters however you'd like. Angular uses Dependency Injection to supply dependencies to your controller. In this case, it will infer those dependencies based on the names so you can order them however you'd like.
Unfortunately, using this method with minification is impossible. In that case, you need to specify your dependencies. Your controller function must then take them in the order that they are defined:
app.controller('PostsCtrl',
['$scope','$location','Post', function($scope, $location, Post) {
}]);
Ahh I was confused by this at first as well. Angular has a nice feature called Dependency Injection. The names do matter because angular looks at the names of the parameters and decides what object to pass to the function. In this rare case in JavaScript, names matter but order does not.
Since you already have other answers with Angular specific instructions, I'll try to explain in simple code how Angular achieves dependency injection in JavaScript and how one might go about doing something similar in plain JS.
First it turns the function into a string, then reads the parameters, and extracts the property from the dependency container, and finally calls the function with the parameters that were extracted. Here's a very simple example of how one might do this:
// parses function and extracts params
function di(app, f) {
var args = f.toString()
.match(/function\s+\w+\s*?\((.*?)\)/)[1] // weak regex...
.split(/\s*,\s*/)
.map(function(x){return app[x.replace(/^\$/,'')]})
return function() {
return f.apply(this, args)
}
}
// Example
// A dependency container
var app = {
foo: 'this is foo',
baz: 'this is baz'
}
// this is like adding a function with the Angular ecosystem
// the order of arguments doesn't matter
var test = di(app, function test($foo, $baz) {
console.log($foo) //=> this is foo
console.log($baz) //=> this is baz
})
// No need to pass arguments, they were injected
test()
//$ this is foo
//$ this is baz
But handling DI by stringifying the function has drawbacks; old browsers don't support Function.prototype.toString very well, and minifiers will shorten the variable names making this technique useless. But Angular can get around this problem by injecting strings and parameters that match those strings; minifiers won't touch the strings.
Obviously AngularJS does much more than this, but hopefully it will clear your mind on "how the hell is this possible?!" -- Well, it's kind of a hack.
The answer here is really Angular specific. You want to read about Dependency Injection here:
https://docs.angularjs.org/guide/di
The Implicit Dependencies section should be good reading. In short, the name does matter for Angular but the order does not. This is NOT the case for raw JavaScript however.
From the Angular site:
There are three equivalent ways of annotating your code with service name information:
Implicitly from the function parameter names
Using the $inject property annotation
Using the inline array annotation

Angularjs -> how to initialise variables in angular-style

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.

Understanding dependency injection in AngularJS controllers

Just learning dependency injection, and I think I'm starting to understand it.
Please tell me if I'm on the right track...
E.g.: Are these two equivalent?
/* injection method */
function <controller_name>($scope) {}
<controller_name>.$inject = ['$scope'];
/* other method */
var app = angular.module('myApp');
app.controller(<controller_name>, function($scope) {});
First a little clarification:
For dependency injection, it doesn't matter whether you declare a controller using a global function or as the argument of module.controller(...) method. Dependency injector is only concerned about the function itself. So what you're actually asking about is the equivalence of those two:
// First
function MyController($scope) {}
MyController.$inject = [ '$scope '];
// Second
function($scope) {}
And because whether the controller function is anonymous or not also doesn't matter for the injector, the above two can just as well be:
// First
function MyController($scope) {}
MyController.$inject = [ '$scope '];
// Second
function MyController($scope) {}
Now it's clear that the only difference between your two controllers is the presence of the $inject property in one of them.
And here's the actual answer to your question:
These two controllers are almost the same. Both will receive the $scope as the argument and will function the same. However, if you decide to minify your code later, only the version with $inject array set on it will work properly. This is because if you don't specify the $inject array nor use the inline annotation approach (http://docs.angularjs.org/guide/di#inlineannotation), the only way for the injector to find out which dependencies you were interested in is to check the names of your function arguments (treating them as service IDs). But minification would name those arguments randomly thus removing the chance to recognize dependencies this way.
So if you're going to minify your code, you have to specify the dependencies explicitly using $inject array or inline annotation, otherwise, any version will work just as good.
If you're going to use the module.controller method, the equivalent to your first example would be:
var app = angular.module('myApp');
app.controller(<controller_name>, ['$scope', function($scope) {}]);
Notice that this way we're passing the $inject string along with the function, so that if it later gets minimized it will still work.

Categories

Resources