Angular controller not accessible in production - angularjs

I have a Symfony project, and as the vast majority working on more than one environments: dev and production, using Angular.js.
At the moment, I have got an Angular controller which is accessible in dev environment, but not in production, throwing the message: "Error: [ng:areq] Argument 'xxxx' is not a function, got undefined".
I have seen the latter message in several threads but none of them helped me.
angular.module('MyApp').controller('MyController', function MyController($scope, MyMapper, _, moment, APP_URL, $location) {
$scope.APP_URL = APP_URL;
$scope.momentjs = moment;
$scope.isLoading = 1;
$scope.page = 1;
$scope.totalPagesNum = 1;
$scope.limit = 20;
// fill the table with data
MyMapper.find($location.search()).then(function(data) {
// ...
})();
}).then(function() {
$scope.isLoading = false;
});
});

If you are minimizing your code (as would be typical in production), you will want to annotate your dependencies since they may get renamed by the minimization script. In order to do this, use the following pattern for your code:
angular.module('MyApp').controller('MyController',
['$scope','MyMapper','_','momemt','APP_URL','$location',
function($scope, MyMapper, _, moment, APP_URL, $location) {
/* your code for the controller */
}
]
);
Please see https://docs.angularjs.org/guide/di for more information, particularly the "Dependency Annotation" section and the inline array notation subsection.

I think the issue is pretty much solved.
It turns out that the minified version of vendors.js (vendros.min.js), was not properly loaded in the production environment. Therefore, I had to point to the correct one in my Gruntfile.js.
Secondly, I refactored my controllers according to AngularJS online guide, as per Brad.
Thanks everyone involved.

Related

Angular: Get aware of spelling mistakes in function call with ng-click

My question is about discovering possible spelling mistakes in angular expressions, in particular spelling mistakes in the function name.
Consider the snippet bellow:
I have two buttons there, the first one with correct spelling, the second with a spelling mistake in the angular expression. Clicking the second button does nothing and gives no hints about a potential error.
My question is now: are there ways to detect erroneous calls to function that don't exist (while executing the application)?
I am not looking for some checking possibility in the build or unit test process but rather would like to see a way I could get aware of such a potential issue when running the erroneous expression in the browser when the application is executed.
angular.module("myApp", [])
.controller("TestController", function($scope){
$scope.myFunction = function() {
console.log("Hello World");
};
});
angular.element(document).ready(function () {
angular.bootstrap(document, ['myApp']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<section ng-controller="TestController">
<button ng-click="myFunction()">myFunction</button>
<button ng-click="myFunctio()">myFunctio</button>
</section>
I'm not familiar with a built in option in angular to do that (using binding to an "undefined" object is a legit UC as things may become "undefined" during program run) - but you may write your own "ng-click" directive which, in case not finding the function to bound to, raise an error (exception or better - console error / warning).
This is an extremely common complaint about Angular. Even when writing code for the Closure compiler, with all the type annotations and everything, these still fall right through the cracks.
You can kluge something together, I've seen things like bussing all events to a common broker and looking for the target handler in the bound scope, and so on. But it always appears to be more trouble than it's worth.
Your unit tests are where you catch this sort of thing. It's why being able to test template code via triggering events is such an important thing for an Angular developer to master. If you trigger that button click and your test fails (e.g. your spyOn the handler never gets called), check the template.
Protractor (and other end to end testing frameworks) will do that for you.
I'm not sure if this would work for function calls or not, but it would solve part of the problem of misspelling something. In Scott Allen's AngularJS Playbook course on Pluralsight, he suggests creating a decorator for the $interpolate service to see if any bindings are potentially incorrect. Here is the code for that:
(function(module) {
module.config(function ($provide) {
$provide.decorator("$interpolate", function ($delegate, $log) {
var serviceWrapper = function () {
var bindingFn = $delegate.apply(this, arguments);
if (angular.isFunction(bindingFn) && arguments[0]) {
return bindingWrapper(bindingFn, arguments[0].trim());
}
return bindingFn;
};
var bindingWrapper = function (bindingFn, bindingExpression) {
return function () {
var result = bindingFn.apply(this, arguments);
var trimmedResult = result.trim();
var log = trimmedResult ? $log.info : $log.warn;
log.call($log, bindingExpression + " = " + trimmedResult);
return result;
};
};
angular.extend(serviceWrapper, $delegate);
return serviceWrapper;
});
});
}(angular.module("common")));

Is Jasmine advise to reduce local variable or karma coverage report?

My Karma coverage report shows to cover the local variable. is that possible or its a karma-coverage report issue.
Please have a look at the Angular Controller Code.
'use strict';
angular.module('moduleName')
.controller('FormController', [ '$log',
function FormController($log) {
// Controller Local Variables.
var that = this;
that.hideLoader = function () {
that.isLoading = false;
};
}
]);
My Question: Is that possible to cover the local variables and function parameter conditions. for instance is below.
that.hideLoader = function (userObj) {
var id = userObj.id;
if(id) {
that.isLoading = false;
}
else {
that.isError = true;
}
};
In the above example, I have declared user object id attribute to local id variable. now its very tough to cover the code. in this case, jasmine advise to reduce local variable or its karma-coverage report suggestion ?
my karma coverage report wants to cover the local variable. is that
possible or its a karma-coverage report issue.
The coverage tool is working correctly by checking that your tests cover every line of code. That is the definition of code coverage.
On the other hand:
var that = this;
that.hideLoader = function() { that.isLoading = false};
are NOT local variables. As defined, they are properties of your controller. Word of caution: please use "use strict" and don't use undeclared properties such as that.isLoading. It is not very readable and it is bad practice even though the language allows for it.
Also, when asking questions please paste the code and not images of the code.
Update
The answer to your question is yes. Karma-coverage reports on every line of code touched (green), or untouched (red).

AngularJS two different $injectors

Today I found, that $injector injected to config or provider is different from $injector injected to service, factory or controller.
And get() function from this $injectors works differently.
$injector from config or provider, can't get() any service! $injector.get('myService') throws Error: [$injector:unpr] Unknown provider: myService, but $injector.has('myService') return true. That's very very strange.
$injector from service or controller works normally.
Here is a code sample for better understanding:
angular.module('app', [])
.provider('myProvider', function ($injector) {
this.$get = ['$injector', function (serviceInjector) {
return {
providerInjector: $injector,
serviceInjector: serviceInjector
};
}];
})
.service('myService', function () {})
.controller('myCtrl', function ($scope, myProvider) {
var providerInjector = myProvider.providerInjector;
var serviceInjector = myProvider.serviceInjector;
console.log(providerInjector === serviceInjector); // -> false
console.log(serviceInjector.has('myService')); // `serviceInjector` has `myService`
console.log(getMyService(serviceInjector)); // `serviceInjector` can get `myService`
console.log(providerInjector.has('myService')); // `providerInjector` has `myService` too!
console.log(getMyService(providerInjector)); // but `providerInjector` can't get `myService`! =(
function getMyService(injector) {
try {
injector.get('myService');
return "OK";
} catch (e) {
return e.toString();
}
}
});
Here is a plunker to play
Can anybody explain why there is two different injectors?
And how can I use $injector from provider/config to inject service(after service was initialized, of course)?
P.S. I use angular 1.3.13
I found this issue on github: https://github.com/angular/angular.js/issues/5559
In the config function, $injector is the provider injector, where in the run function, $injector is the instance injector.
One's the $injector at the config stage (only providers and constants accessible), and one's the $injector at the run stage. The confusion may be that you're thinking the $injector modifies itself to include the new stuff as it crosses the line from config to run, but that's not true. They're two separate (although related) objects, with their own caches of instances.
A more in-depth reason for this dichotomy will probably come from a deep learning of the $injector internals, but it seems like it's been DRY-ed pretty hardcore, and the two types of injectors share almost all the same behavior, except in how they deal with "cache misses" in their instance caches.
We are going to overhaul the injector in v2, so this will get fixed there (getting rid of the config phase is one of the objectives of the injector v2).
Seems like there is really two different injectors, and angular developers will not fix that behavior(in versions <2.0). And nobody added a note about that aspect to $injector docs for some reason.
I was unable to find a way how to really get instance injector inside a configuration block without hacky tricks. So, I write a cute provider to solve that kind of problems.
.provider('instanceInjector', function () {
var instanceInjector;
function get() {
return instanceInjector;
}
function exists() {
return !!instanceInjector;
}
angular.extend(this, {
get: get,
exists: exists
});
this.$get = function ($injector) {
instanceInjector = $injector;
return {
get: get,
exists: exists
};
}
})
// We need to inject service somewhere.
// Otherwise $get function will be never executed
.run(['instanceInjector', function(instanceInjector){}])
Ok. After reading your comments, here is my answer.
I edited code in plunk to make it work, when invoking the providerInjector.get() the code should be as follows:
$scope.getMyServiceFromProviderInjector = function () {
try {
myProvider.providerInjector.get('myServiceProvider');//here is change in provider name
return "OK";
} catch (e) {
return e.toString();
}
};
According to angular docs the following is quoted for config and run blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants
can be injected into configuration blocks. This is to prevent
accidental instantiation of services before they have been fully
configured.
Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be
injected into run blocks. This is to prevent further system
configuration during application run time.
This simply means, you cannot get instances of services inside config blocks.
I wrote this a while back, which explains the lifecycle of both injectors of AngularJS, i.e providerInjector and instanceInjector.
http://agiliq.com/blog/2017/04/angularjs-injectors-internals/

syncing the views of two angular apps

I have two angular applications in one page, and I need them to communicate. Specifically, I want one application to use a service of another application.
I am able to get the service of the other application using Injector.get(service), but when I change the data using the service in one application, it does not reflect in the view of the other, even though both are supposed to show the same data. You can see a simplified version of the problem in jsFiddle.
To save you the click, this is the relevant script:
//myAppLeft - an angular app with controller and service
var myAppLeft = angular.module('myAppLeft', []);
myAppLeft.factory('Service1',function(){
var serviceInstance = {};
serviceInstance.data = ['a','b','c','d','e'];
serviceInstance.remove = function(){
serviceInstance.data.pop();
console.log(serviceInstance.data);
};
return serviceInstance;
} );
myAppLeft.controller('Ctrl1', ['$scope', 'Service1', function($scope, service1) {
$scope.data = service1.data;
$scope.changeData =function(){
service1.remove();
}
}]);
var leftAppInjector = angular.bootstrap($("#leftPanel"), ['myAppLeft']);
//myAppRight = an angular app with controller which uses a service from myAppLeft
var myAppRight = angular.module('myAppRight', []);
myAppRight.controller('Ctrl2', ['$scope', function($scope) {
$scope.data = leftAppInjector.get('Service1').data;
$scope.changeData =function(){
leftAppInjector.get('Service1').remove();
}
}]);
var rightAppInjector = angular.bootstrap($("#rightPanel"), ['myAppRight']);
I'd be happy to know why my code does not work as expected, and would be even happier to know if and how such thing can work.
I understand that if instead of two angular-apps I would have used one angular-app with two modules this would have worked just as I wanted, but unfortunately I cannot adopt this approach because my application consists of a pure-js core with extensions, each extension can be written in a different library/platform and I want my extensions to be angular ones.
Thanks,
Nurit.
Angular apps are separate entities, even if you use the same service in both. the second app just initializes its own version off it.
What you want can be done using localStorage, and the storage events.
Ping me if you need additional help on this!

Angularjs and Meteor "Session" reactivity, is there a way?

I'm trying to work with Meteor and Angularjs. I'm using Meteor_angularjs package, which works OK with Collections.
Now I'm trying to use Session and my reactive data store:
TestCtrl = [
"$scope",
function($scope){
$scope.value = Session.get('someValue');
}
]
This does not work.
QUESTION: Any suggestions on how to tie down Meteor's Session and Angular?
As far as I understand, I can write directive that will be polling Session every so ofter, however I don't think that's a good choice.
Thanks
UPDATE:
I've tried the following:
TestCtrl = [
"$scope",
function($scope){
Meteor.autorun(function(){
$scope.config = Session.get('testsConfig');
if (!$scope.$$phase){
$scope.$digest();
}
});
}
]
and it sort of works, however I get the following error:
Error: INVALID_STATE_ERR: DOM Exception 11
Error: An attempt was made to use an object that is not, or is no longer, usable.
at derez (http://localhost:3000/test:95:41)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30)
at derez (http://localhost:3000/test:95:30) angular.js:5526
$get angular.js:5526
$get angular.js:4660
$get.Scope.$digest angular.js:7674
(anonymous function) controllers.js:46
Meteor.autorun.rerun deps-utils.js:78
_.extend.run deps.js:19
Meteor.autorun.rerun deps-utils.js:78
_.extend.flush deps.js:63
_.each._.forEach underscore.js:79
_.extend.flush deps.js:61
_.each._.forEach underscore.js:79
_.extend.flush deps.js:60
UPDATE 2:
I've tried the service like this (might be wrong usage), still nothing. Now it doesn't update at all on Session value's changes.
Meteor.autorun(function(){
app.factory('ssn', function(){ return{
get: function(val){
return Session.get(val);
}
}});
});
TestCtrl = [
"$scope","ssn",
function($scope, ssn){
$scope.config = ssn.get('testsConfig');
}
]
UPDATE 3: Angular has $apply() for
to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries)
At the same time Meteor has Meteor.render() for
Most of the time, though, you won't call these functions directly — you'll just use your favorite templating package, such as Handlebars or Jade. The render and renderList functions are intended for people that are implementing new templating systems.
However, it seems like I just cannot put 2 and 2 together. :(
this as an old question with old answers but I see people referring to it so here is the updated answer.
First - there is a new library for angular-meteor that handles those cases for you.
And this library gives you two possible solutions:
If you want to bind a Session variable to a scope variable, use the $meteorSession service.
What it does is that every time the scope variable will change, it will change to Session variable (and trigger an autorun if it's placed inside one).
and every time the Session variable will change, the scope variable will change as well (and change the view that it's placed upon).
If you are using the Session variable just to get a variable reactive (meaning trigger an autorun), you should use getReactively . this just returns the already existing scope variable but trigger an autorun every time it changes. a good example of this can be found it our tutorial.
Note: In anyway, when you use Tracker.autorun inside Angular, you need to connect it to a scope. this can be easily done if you replace Tracker.autorun with the $meteorUtils autorun function
Hi here is an option (might not be the best but it works I think)
app.service('Session',function($rootScope){
var self = this;
self.objects = {};
self.get = function(name){
self.objects[name] = {"value" : Session.get(name)};
Meteor.autorun(function() {
var i = Session.get(name);
if(self.objects[name].value != i){
if (!$rootScope.$$phase){
$rootScope.$apply(function(){
self.objects[name].value = i;
});
}
}
});
return self.objects[name];
}
self.set = function(name,value){
self.objects[name].value = value;
Session.set(name,value);
}
return self;
});
Call it in the $scope like this
$scope.test = Session.get("test");
In the view as {{test.value}}. Sorry for the late answer .
Happy new year!
try
angular.module('yourmod', [])
.controller('TestCtrl', ['$scope', function($scope) {
var c = Deps.autorun(function (comp) {
//... put reactive stuf on scope.....
if(!comp.firstRun) {
// only do not do aply at first run becaulse then apply is already running.
$scope.$apply()
}
});
// and to realy make it nice...
$scope.on('$destroy', function () {c.stop()});
}])

Resources