When you initialize a controller, service, factory in Angular, you pass in an anonymous function as the the second parameter in the following format:
myApp.controller('myController', function($scope, $q){
});
How does Angular determine from the above that it needs to initialize an instance of $scopeand $q for our new controller?
Does it grab the parameter names you pass in and find the corresponding objects from the Angular library?
Here's the critical part of Injector (source):
var ARROW_ARG = /^([^\(]+?)=>/;
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function extractArgs(fn) {
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
return args;
}
As you see, the key is analyzing the function's source(returned by Function.prototype.toString()). First, all the comments are removed. It's necessary, as one can define a function like this:
function (el /* DOMElement */, b) {...}
Next, the meaningful parts are matched both against the arrow syntax (ES6) and the traditional one. Essentially, both patterns are matching all the things within the first pair of parentheses in the function source.
This code is used, among other things, in annotate function - filling up the $inject collection:
argDecl = extractArgs(fn);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
You also need to know that the Angular devs recommend to avoid the shown approach (so called Implicit Annotation), as it's not compatible with minifiers/obfuscators without tools like ng-annotate.
Related
Feel free to clear up any misunderstandings you see here; I'm not a front end guy.
Now, I've read that much of the logic shouldn't exist in the controller, and that it should be put somewhere else. But most places I look that show code don't specify which file it belongs in. On this project that I've inherited I have 4 files that deal with the main functionality:
A controller - autoDeploy.js
A service - autoDeploy.service.js
A module - autoDeploy.module.js
Directives - directives.js
directives.js just contains the templates that I want to inject into the DOM upon the click of a button, the directives will be referenced later.
autoDeploy.module.js does all of the module.config and $stateProvider routing stuff; I don't touch it beyond my initial modification to point to the new page I'm making so it can be properly routed to.
autoDeploy.service.js: In the examples I've seen, the .factory()'s (or .service()) last parameter usually opens up as a function, and all of the functionality in the file happens inside there. Mine isn't like that, it's an iife with the factory being a standalone command followed by what looks like a constructor. Here's what I have:
(function () { //start iife
'use strict';
angular.module('gms.autoDeploy')
.factory('AutoDeployService', ["$http", "$q", "$log", "$cookies", "APP_CONFIGS", "SweetAlert", "$timeout", "GridSettingsService", "APP_USER", AutoDeployService]);
function AutoDeployService($http, $q, $log, $cookies, APP_CONFIGS, $timeout, SweetAlert, GridSettingsService, APP_USER) {
//return service object at bottom of function
function returnOne() {
return "one";
}
var service = {
returnOne: returnOne
};
return service;
} //end AutoDeployService
}()); //end iife
Why did the original developer...
Use as an iife?
Return the service variable after making it a function mapping?
Put all of the functionality in the constructor like function?
My guess to the above 2nd and 3rd answers is that it is the constructor for the service and the controller can some how know it's a usable object because we pass it in as a parameter to the controller as you can see on the top line of the code below. I also don't know much about the parameters for the "constructor", but I can look those up later.
Now for the controller. Unlike the service above, the controller declaration does open up as a function in the parameters of .controller() as I've seen other places. Here it is, simplified (methods with similar functionality are cut out):
angular.module("gms.autoDeploy").controller('AutoDeployController', ['$scope', '$compile', 'AutoDeployService',
function ($scope, $compile, AutoDeployService) {
var vm = this;
init();
function init() {
vm.isInstantiated = true;
vm.data = {
"parameter": []
};
}
// calling a function from the service to show that we can pass
// data from controller to service
vm.change = function () {
vm.message = AutoDeployService.returnOne("not one");
};
//function assigned to button click on the DOM allowing
// the directive to inject the template where the <tib-copy> tag is
vm.addCopy = function (ev, attrs) {
var copy = angular.element(document.createElement('copy'));
var el = $compile(copy)(vm);
angular.element(document.getElementsByTagName("tib-copy")).append(copy);
vm.insertHere = el;
};
// This method extracts data from the following fields dynamically added by the click of a button:
// - TIBCO Server(s)
// - TIBCO Domain(s)
// - TIBCO Application(s)
vm.getTibPromotionData = function () {
// Add all TIBCO servers
var servers = document.getElementsByName("tibServer");
var tibSvrList = [];
for (var i = 0; i < servers.length; i++) {
tibSvrList.push(servers[i].value);
}
// Add all TIBCO domains
var domains = document.getElementsByName("tibDomain");
var tibDomainList = [];
for (i = 0; i < domains.length; i++) {
tibDomainList.push(domains[i].value);
}
// Add all applications
var tibApps = document.getElementsByName("tibApp");
var tibAppList = [];
for (i = 0; i < tibApps.length; i++) {
tibAppList.push(tibApps[i].value);
}
// Add the processed data to the final JSON
vm.data.parameter.push({
"name": "TIBCO_Promotion",
"value": JSON.stringify("[{\"server\":[" + tibSvrList + "]},{\"domain\":[" + tibDomainList + "]},{\"application\":[" + tibAppList + "]}]")
});
};
}
]);
Please, let me know if you see anything in the controller that should belong in the autoDeploy.service.js file. Furthermore, if anyone has experience with this file naming convention, I'd love to hear an explanation as to why there are files named *.service.js and *.module.js, and if the *.service.js file has anything to do with the concept of services and factories, or if it's intended to be conceptual, as if it's just a reference to being the back end services component.
My team wants to move to a style of dependency injection that is closer to the CommonJS/Node JS syntax for out Angular codebase:
var myDependency = require('myDependency');
I've started using $injector.get() directly inside at the top of my functions, so far with no obvious trouble. That means I've converted this:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['serviceA', 'serviceB', 'serviceC', function(serviceA, serviceB, serviceC) {
//My code here
}])
Into:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['$injector', function($injector) {
var serviceA = $injector.get('serviceA');
var serviceB = $injector.get('serviceB');
var serviceC = $injector.get('serviceC');
//My code here
}]);
Is there something I'm missing. Does not declaring the required dependencies outside of the function definition cause any sort of performance issues?
Note: this answer has not been tested.
After digging into the angular's code, I find this:
// in function createInjector
function provider(name, provider_) {
...
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
...
return providerCache[name + providerSuffix] = provider_;
}
...
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
...
// in function createInternalInjector
function invoke (...){
...
for (i = 0, length = $inject.length; i < length; i++) {
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
...
return fn.apply(self, args);
}
function instantiate(...){
...
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
...
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
Its calls seems like:
service invoked factory invoked instantiate
factory invoked provider invoked instantiate
So I think your questions is equal to Is calling $injector.get() one by one has the same performance as calling $injector.instantiate() once?
As the code shows, instantiate invoked invoke which actually invoked getService for each services injected by you. And $injector.get is just binding to getService.
So the answer to my equal question is True.
And the answer to your question is No, their performance is very close.
Please correct me if I'm wrong, thank you!
There are no significant difference between both pieces of code in the case of a service. $injector.get(...) call doesn't provide any overhead. But it provides a significant amount of extra characters per dependency.
There is a difference when the same thing is done with injectables that have local dependencies - controllers and route/state resolvers.
When these dependencies
app.controller('SomeCtrl', function ($scope, service) { ... });
are replaced with $injector.get(...), it will choke on $scope - it is local dependency. And with other dependencies being retrieved like that, controller loses its testability. The dependencies cannot be mocked with
$controller('SomeCtrl', { service: mockedService });
I personally don't see how $injector.get(...) may benefit the style of the project (and haven't seen a good style guide that would suggest it).
Node uses require function because it works for it, not because it is better or cooler. The alternative would be to pack each Node script into into AMD-like wrappers, which would be painful. Fortunately, we already have wrappers around Angular units!
I am trying to use slice() method on an object array in AngularJS but its says 'slice is not a method'.
Here is the code
.controller('AppCtrl', function ($scope, Faq) {
$scope.filteredFaqData = [];
$scope.currentPage = 1;
$scope.numPerPage = 5;
$scope.maxSize = 3;
$scope.faqData = {};
Faq.get().then(function (msg) {
$scope.faqData = msg.data;
});
$scope.$watch('currentPage + numPerPage', function () {
var begin = (($scope.currentPage - 1) * $scope.numPerPage)
, end = begin + $scope.numPerPage;
$scope.filteredFaqData = $scope.faqData.slice(begin, end);
});
});
I am getting the data from json file in $scope.faqData using a service and it is working.
But slice method is not working in the console it is giving error "$scope.faqData.slice" is not a method.
.slice is a method available from the array prototype.
Your faqData is defined as a plain 'ol object. As such it does not have access to .slice.
Your service is mutating faqData into an array however giving you access to .slice, this is only when it resolves.
So, the issue is your watcher may fire before your promise resolves meaning your trying to call slice from a plain object.
You should define your objects to the type you will be using them with, avoid mutating objects where possible.
Your watch may also need to handle the fact your promise has not resolved yet, that depends on what you intend to do in the watcher.
slice docs
I am working with AngularJS and have created a Module, which has a Factory and a Filter. The Factory gets a local json file translations) and the filter provides a function that returns a translated version of the text. So the code looks like the following;
angular
.module('i18n', [])
.factory('translationDataFact', ['$http', function($http){
var t = {};
var user = {};
t.defaultLanguage = 'en-GB';
t.languageFile = null;
t.init = function(){
t.setLanguage();
if(!t.languageFile){
$http
.get('translations/' + t.defaultLanguage + '.json')
.success(function(data){
t.languageFile = data.strings;
})
.error(function(error){
console.log(error);
});
}
}
t.setLanguage = function(){
/* change default language to User language here */
if(user.id){
t.defaultLanguage = user.language;
}
return t.defaultLanguage;
}
t.init();
return t.languageFile;
}])
.filter('t', ['translationDataFact', function (translationDataFact) {
var translate = function (stringIdenitfier) {
var translation = translationDataFact.languageFile[stringIdenitfier];
if(translation){
return translation;
}
return "translate me!!";
};
return translate(stringIdenitfier);
}]);
Then I wish to use the filter to translate variables and names like this
{{"string" | t }}
The problem I am having is that I have no idea how to make sure
The return of the Factory is set before the Filter runs this.
Also I am confused by how I prevent the whole application rendering until this filter is ready?
Any help would be amazing as I am lost :(
Is there a reason why you don't use an existing angularjs translation library like angular-translate?
In factory method u need to return Service itself, not result of operation. (I am not sure what exactly u want from this serivce)
when you return t.language it is always null and it will remain null in your filter... because your http call is asynchronious.
I would make this like:
app.module('translationDataFact', ['$resource', function($resource) {
var t = {};
t.init = function() {
t.result = $resource('...');
}
t.init()
return t;
}]);
In controller you have:
$scope.language = translationDataFact.result;
You make filter with parameter, inside filter you can check whether language is undefined or not.
So later you write:
{{ "string" | t:language}
And after language 'arrives' you see translation.
To answer your concerns:
Your factory should return something that can be asked for a specific translation. If the translations are not ready just return something basic like an empty string or null. e.g.
return translations.t(languageFile, translationKey);
Where t() would be a function that inspects the internal data structure of translations and can return either the result of the translation or the value mentioned earlier if the translations haven't been loaded yet.
You can do something like ng-show="translations.isLoaded()" on your top level element, but you'd need to set up a reference to the translations service on the $scope of your highest level controller. You may want to do this on the $rootScope so your translation service is always available in controllers as well.
I've started looking into using Knockout for my team's use; I've been very pleased with the framework so far, with the exception of its ties to the jQuery Templates plugin, whose syntax I hate. Additionally (and more importantly), we've been using jQote2 for our client-side templating, so I wanted to look into creating a template engine that uses it.
I'm having difficulty with the renderTemplate function; the jQote2 library doesn't seem to know how to locate the variables passed in through the data argument. Has anyone dealt with this sort of thing before? I'm assuming it's more of a jQote2 quirk than anything with how Knockout renders its templates...
The code:
jqoteTemplateEngine = function () {
var templates = {};
this.createJavaScriptEvaluatorBlock = function (script) {
return '<%= ' + script + ' %>';
}
this.getTemplateNode = function (template) {
var templateNode = document.getElementById(template);
if (templateNode == null)
throw new Error("Cannot find template with ID of " + template);
return templateNode;
}
this.renderTemplate = function(templateId, data, options) {
var renderedMarkup = jQuery.jqote(templates[templateId], data);
return ko.utils.parseHtmlFragment(renderedMarkup);
}
this.rewriteTemplate = function (template, rewriterCallback) {
var templateNode = this.getTemplateNode(template);
templates[template] = rewriterCallback(templateNode.text);
}
this.isTemplateRewritten = function (templateId) {
return templateId in templates;
}
};
jqoteTemplateEngine.prototype = new ko.templateEngine();
ko.setTemplateEngine(new jqoteTemplateEngine());
The Gist: http://gist.github.com/1341737
I would take a look at KO 1.3. It is in beta, but is quite stable. Two important things in 1.3. There is now a native template engine, so you could choose to avoid having any dependency on an external template engine (and even jQuery). The control-flow bindings with anonymous templates make this much simpler option.
Secondly, it simplified the way that custom template engines are defined. So, if you still want to use an external engine, then it is a bit easier to get it working.
Basically, you only need to define a renderTemplate method and a createJavaScriptEvaluatorBlock if you want to be able to data-bind to variables available to the template engine. The rest is defined on a ko.templateEngine base "class".
So, your engine might simply look like:
ko.jqoteTemplateEngine = function() {};
ko.jqoteTemplateEngine.prototype = new ko.templateEngine();
ko.jqoteTemplateEngine.prototype.createJavaScriptEvaluatorBlock = function(script) {
return '<%= ' + script + ' %>';
};
ko.jqoteTemplateEngine.prototype.renderTemplateSource = function(templateSource, bindingContext, options) {
var templateText = templateSource.text();
return ko.utils.parseHtmlFragment($.jqote(templateText, bindingContext.$data));
};
ko.setTemplateEngine(new ko.jqoteTemplateEngine());
Here is a sample in use: http://jsfiddle.net/rniemeyer/yTzcF/