Are JavaScript anonymous function parameter names significant? - javascript

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

Related

Difference between javascript modularisation and dependency Injection

What 's the difference with the modularisation of a javascript code (with browserify by example) and the dependency injection?
Are they synonymes? Are the two going together? Or Am I missing some point?
Modularisation refers to breaking code into individual, independent "packages".
Dependency injection refers to not hardcoding references to other modules.
As a practical example, you can write modules which are not using dependency injection:
import { Foo } from 'foo';
export function Bar() {
return Foo.baz();
}
Here you have two modules, but this module imports a specific other hardcoded module.
The same module written using dependency injection:
export function Bar(foo) {
return foo.baz();
}
Then somebody else can use this as:
import { Foo } from 'foo';
import { Bar } from 'bar';
Bar(Foo());
You inject the Foo dependency at call time, instead of hardcoding the dependency.
You can refer this article:
Modules are code fragments that implement certain functionality and
are written by using specific techniques. There is no out-of-the box
modularization scheme in the JavaScript language. The upcoming
ECMAScript 6 specification tends to resolve this by introducing the
module concept in the JavaScript language itself. This is the future.
and Dependency injection in JavaScript
The goal
Let's say that we have two modules. The first one is a service which
makes Ajax requests and the second one is a router.
var service = function() {
return { name: 'Service' };
}
var router = function() {
return { name: 'Router' };
}
We have another function which needs these modules.
var doSomething = function(other) {
var s = service();
var r = router();
};
And to make the things a little bit more interesting the function
needs to accept one more parameter. Sure, we could use the above code,
but that's not really flexible. What if we want to use ServiceXML or
ServiceJSON. Or what if we want to mockup some of the modules for
testing purposes. We can't just edit the body of the function. The
first thing which we all come up with is to pass the dependencies as
parameters to the function. I.e.:
var doSomething = function(service, router, other) {
var s = service();
var r = router();
};
By doing this we are passing the exact implementation of the module
which we want. However this brings a new problem. Imagine if we have
doSomething all over our code. What will happen if we need a third
dependency. We can't edit all the function's calls. So, we need an
instrument which will do that for us. That's what dependency injectors
are trying to solve. Let's write down few goals which we want to
achieve:
we should be able to register dependencies
the injector should accept a function and should return a function which somehow gets the needed resources
we should not write a lot, we need short and nice syntax
the injector should keep the scope of the passed function
the passed function should be able to accept custom arguments, not only the described dependencies
A nice list isn't it. Let's dive in.

Is it possible to use a constructor function as a type of an argument of another function in TypeScript?

A few days ago I've found a TypeScript language. According to video reviews it looks pretty promising for me as it brings to JavaScript an appropriate code completion, implicit code documentation and, probably, makes it more type safety.
I'm trying to rewrite a simple AngularJS application in TypeScript. I'd like to specify types of parameters, which are injected to controllers. Let's assume the following JS code:
// service.js
angular.module('app').service('MyService', MyService);
function MyService() {
this.test = function test() { console.log('test'); };
}
// controller.js
angular.module('app').controller('MyController', MyController);
function MyController($scope, MyService) {
$scope.test = function test() { MyService.test(); };
}
I'm trying to achieve something like this:
// controller.ts
angular.module('app').controller('MyController', MyController);
function MyController($scope, MyService:MyService) {
// ...
}
So it'd be easier to write code of the controller with code completion for MyService parameter. However, this code makes a compiler to emit error TS2304: Cannot find name 'MyService'.
I've tried a couple to things and found at least 2 possible solutions. However, non of them seems to be ideal.
1) Creation of an additional interface, which declares all methods of the service. But this approach requires a lot of additional typing (actually, we define the same set of methods with full signature twice and also have to update several things in case of changes). This way is totally okay when the interface is implemented multiple times, but looks redundant for a single AngularJS service.
2) Constructor function can be replaced with a class. This way it is possible to use a name of the class as a type of the parameter. However, this also leads to a couple of problems:
typescript compiler transforms class definitions into closures which are assigned to variables declared with var keyword (at least, dealing with ES5). This requires the AngularJS service declaration to be placed after the class definition at the very bottom of a file. It increases a possibility to forget to do it.
angular.module('app').service('MyService', MyService);
class MyService {
test() { console.log('test'); }
}
// becomes
angular.module('app').service('MyService', MyService);
var MyService = (function () {
function MyService() {
}
MyService.prototype.test = function () { console.log('test'); };
return MyService;
}());
going this way, we have to inject dependencies to a constructor function of the service. As a result, it is necessary to specify an excess this. string each time a dependency is used.
So, are there any other ways to pass a constructor function as a type of a parameter of another function? Thanks.
P.S.: I don't know whether it's necessary or not, but I'm using Sublime Text 3 with an official TypeScipt plugin and gulp-typescript for compilation.
Yes, you can do this. See working CodePen
class MyController
{
static $inject = ['MyService','$scope'];
constructor(private myService:MyService, private $scope:any){
this.$scope.test = this.myService.test;
}
}
class MyService{
test(){
console.log('test');
}
}

how to reference an angular value so that reference does not get destroyed on minification

The following code is working fine before minifiaction. Essentially, editableOptions is an angular value provided by the angular-xeditable library.
angular.module('common').run(function(editableOptions) {
editableOptions.theme = 'bs3';
}
)
However, when I minify, I get an injection error. I believe this is because editableOptions is getting minfied. How can I reference it differently so that this does not occur? Is there some way of referencing it starting with angular.module('xeditable') ?
Use the minification-safe method of defining injected objects:
angular.module('common').run(
['editableOptions',
function(editableOptions) {
editableOptions.theme = 'bs3';
}
]);
Angular team have a solution to this problem mentioned in one of their tutorials:
https://docs.angularjs.org/tutorial/step_05
A Note on Minification
Since Angular infers the controller's dependencies from the names of arguments to the controller's constructor function, if you were to minify the JavaScript code for PhoneListCtrl controller, all of its function arguments would be minified as well, and the dependency injector would not be able to identify services correctly.
We can overcome this problem by annotating the function with the names of the dependencies, provided as strings, which will not get minified.
This is another style to avoid minification and uglification problems:
function runModule(editableOptions) {
editableOptions.theme = 'bs3';
}
runModule.$inject = ['editableOptions'];
angular.module('common').run(runModule);
Todd Motto Angular Style
Guide
This aids with readability and reduces the volume of code "wrapped"
inside the Angular framework

Problems with circular dependency and OOP in AngularJS

AngularJS + OOP is kinda sexy feature to use
Hi, I'm successfully using OOP with AngularJs for some time already (first started with angularjs with oop inheritance in action), the provided approach allows you define your classes as angular services, which you can later extend or inherit from like that:
Application.factory('AbstractObject', [function () {
var AbstractObject = Class.extend({
virtualMethod: function() {
alert("Hello world");
},
abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
throw new Error("Pure abstract call");
}
});
return AbstractObject; // You return class definition instead of it's instance
}]);
Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
var DerivedObject = AbstractObject.extend({
virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
alert("Hey!");
this._super();
},
abstractMethod: function() {
alert("Now I'm not abstract");
}
});
return DerivedObject;
}]);
Plunker: http://plnkr.co/edit/rAtVGAsNYggBhNADMeoT
using the described approach gives you the ability to define classes that beautifully integrate into angular infrastructure. You get all sort of nifty features from two worlds - OOP and AngularJs. Dependency injection is free for your classes, and it makes your classes simple, allows putting a lot of boilerplate controller code into some base class that can be later reused.
However
AngularJs infrastructure blocks previously described approach from spreading it's wings on all 100%. The problem occurs when you try to define recursive class definitions (i.e. recursive aggregation), say you have two class definitions like Blog and Tag
Application.factory('Blog', ['Tag', function (Tag) {
var Blog = Class.extend({
tags: function() {
return this.tags;
}
});
return Blog;
}]);
Application.factory('Tag', ['Blog', function (Blog) {
var Tag = Class.extend({
Blogs: function() {
return this.blogs;
}
});
return Tag;
}]);
It won't work because both Blog and Tag are self-referencing themselves causing circular dependency.
P.S
The last thing, I have found kinda ugly solution that solves my problem in my specific case but doesn't work in general and as I said, it isn't pretty:
Application.factory('BlogNamespace', [function () {
var Blog = Class.extend({
tags: function() {
return this.tags;
}
});
var Tag = Class.extend({
Blogs: function() {
return this.blogs;
}
});
return {
Tag: Tag,
Blog: Blog
};
}]);
Question
The above fix won't work because namespaces may also be a subject of circular dependency. This means that it isn't solution to described problem but rather one level deeper problem now.
Any suggestions on how it is possible to solve described problem in general case?
A circular dependency is always the sign of mixing of concerns, which is a really bad thing. Miško Hevery, one of the authors of AngularJS, explains a nice solution on his awesome blog. In short, you probably have a third service hidden somewhere, which is the only part of your code really needed by the two others.
I'm answering my own question just because I've found a technical way of resolving the issue that I have originally posted about. But before that, I strongly encourage you to use Blackhole's suggestion since it allows solving a broader set of problems which are usually caused by bad architecture. Please prefer using his approach first, and return to current one in case that you know what you are doing.
So here goes:
You can use $injector service and inject required definitions at run-time, which is legal from technical point of view, but again according to this post (hard to imagine that it is written in 2008), this is like a black magic, do that and it will strike you back:
Application.factory('Blog', ['$injector', function ($injector) {
var Tag = $injector.get('Tag'); // Here is your tag
...
}]);
Application.factory('Tag', ['Blog', function (Blog) {
...
}]);
Edit
It turned out that current approach is an example of Service Locator pattern, which is IoC Antipattern.
LAST RESORT: NOT ENCOURAGED
In my case the best way to get around a circular-dependency-problem like this in angular, is to trigger function-calls via $rootScope-broadcasts. The other service can then listen to this broadcast and react with the desired function-call. It may not be the most elegant solution but in some cases where the interaction between the services is mainly one-directional anyways, it may be a reasonable alternative. (note that this also allows return-values to be passed back to the broadcasting function via callbacks only)
A pseudo-example of this would be:
angular.module('myApp').factory('service1', ["$rootScope",
function($rootScope) {
function func1() {
// do something
}
$rootScope.$broadcast("callFunc2"); // calls func2 from service 1
return {
func1: func1
}
}
]);
angular.module('myApp').factory('service2', ["service1", "$rootScope",
function(service1, $rootScope) {
function func2() {
// do something
}
service1.func1(); // calls func1 from service 2
$rootScope.on("callFunc2", func2);
}
]);

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