It's just a convention - but it's thought-out pretty well, hence been used throughout Angular. See, a function - a module - can have just a single dependency (as in your example), or many of them (starting from two, right), or no dependency at all. So we need some solution that answers all the cases.
The naive approach is specifying all the deps as function arguments (your second example). Now, it's possible to extract (and inject) them by analyzing the function source code. Pro: absolute minimum code to write (you'd have to specify all the deps' names anyway). Cons: 1) based on reflection (which is never fast), 2) breaks when the script is minified (and all the params' names are transformed).
These cons are bad enough so there just has to be another way. We wouldn't want to get rid of the arguments list, though (those deps still had to be addressed within the function somehow, right?). But now it's clear that the single list isn't enough - it has to be duplicated somewhere.
And this is where Array - an ordered sequence of elements - comes quite handy. Now the injector only has to separate the last element of that array to get the full deps list. Those are strings, not variables, so they won't be modified by minifier. What's even better, now we don't have to analyze the signature, so the injector works a bit faster.
From theory to practice: this is how those two approaches are implemented in Angular 1.x DI module:
function annotate(fn, strictDi, name) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn === 'function') {
// first approach: only function is passed, we need to analyze the args list
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
if (strictDi) {
if (!isString(name) || !name) {
name = fn.name || anonFn(fn);
}
throw $injectorMinErr('strictdi',
'{0} is not using explicit annotation and cannot be invoked in strict mode', name);
}
// get rid of comments, it's possible to have those inside `()`
fnText = fn.toString().replace(STRIP_COMMENTS, '');
// extract arguments
argDecl = fnText.match(FN_ARGS);
// push those into injector
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
// ... and that took some time
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
// second approach: an array is passed
last = fn.length - 1;
// make sure its last element is a function
assertArgFn(fn[last], 'fn');
// use all but the last element as list of deps
$inject = fn.slice(0, last);
// ... and that's all, believe it or not!
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
}
As you see, the first if branch is for the old way - deps expressed as function arguments. The second (much easier to read and execute) - for deps and function placed in an array (function being the last element).