variable not found / undefined after grunt build (uglify) - javascript

I am using AngularJS for building a simple app with a map. As the main ctrl had too many logic I build a second controller for the navbar. Until here everything worked fine. Now I outsourced the map.on('zoomend' ... ) function when refactoring the main controller.
The problem now is, that when the navbar controller file is minified (through grunt build uglify) I get the following error:
Cannot read on of undefined
That means, map is undefined even though it is declared at the top of the file AND I do not have the problem on localhost (grunt serve).
Navbar Ctrl:
'use strict';
angular.module('angularMapApp').controller('navbarController', navbarController);
navbarController.$inject = ['$scope', '$mdSidenav', 'helper', 'RespondService', 'shipTypes'];
function navbarController($scope, $mdSidenav, helper, RespondService, shipTypes) {
var map = RespondService.getMap();
map.on('zoomend', function() {
timestamp = RespondService.getTimestamp();
selectedShipTypes = RespondService.getSelectedShipTypes();
selectedShipState = RespondService.getSelectedShipState();
showGrid = RespondService.getShowGrid();
helper.loadAndShowShipMarkers(timestamp, selectedShipTypes, selectedShipState, showGrid, map).then(function(results) {
$scope.numberOfShips = results;
RespondService.setNumberOfShips($scope.numberOfShips);
});
});
So this is a short version of my controller. The grunt file is still the same as created with yeoman. I too logged the map value at the top of the file, and there it has a value. However using 'map.on' might not work.
Maybe anyone can help me with that.

Your $inject seems to be correct, so minify should work fine for angular injection. It looks like var map is loading data from RespondService.getMap(). What do you expect to get from that function? You might want to put a break point and see if what you expecting is being returned.

Related

AngularJS 1.7 Module Injection For Tests Error

My AngularJS (1.7.x) application has a custom filter that I want to write tests around it, and I decided to use Jest to perform this task.
My main issue is that as I follow this tutorial by Curt, I am struggling to properly get the filter (which has no outside dependencies so I thought it was the prime target to introduce unit tests with) to load within the test harness.
For starters, here is a simplified version of the filter for the purpose of this question:
angular.module('app.module').filter('takeTheThing', () =>
function (parameter1) {
return `${parameter1} thing!`;
}
);
And, after following the tutorial above, as well as reading up on another SO question specific to testing AngularJS filters, I have attempted every conceivable version of my simple test file as follows but receive a cryptic error message from Angular about it:
require('../node_modules/angular/angular.min.js');
require('../node_modules/angular-mocks/angular-mocks.js');
//Commenting or un-commenting this, results in module-related injector errors:
//require('./takeTheThing.filter.js');
describe('The thingerizer', () => {
let $filter;
beforeEach(angular.mock.module('app.module'));
//Injecting this specifically, or _$filter_ still errors:
beforeEach(inject(takeTheThingFilter => {
$filter = takeTheThingFilter;
}));
//Injecting here instead of in the beforeEach, same issue
it('should give me something', () => {
//Calling the specific filter or thru the Angular $filter... Same
const actual = $filter(1);
expect(actual).toEqual('1 thing!');
});
});
I'm pulling my hair out, but there's something quite basic I'm missing in regards to the test setup (specifically, how to load the app "correctly" without whole-hog loading my entire application). What gives?
AngularJS: 1.7.5
Angular Mocks: 1.7.6
Jest: 23.6.0 (I am using gulp-jest but even when I directly call jest from within the bin folder, I get the exact same errors, so I omitted most of those details here)

The correct order to load Google Client api gapi and angular js

It's kind of tricky how to load js files in an Angular project together with Google Client js api.
This question is talking about the correct order of doing this.
Angular Js and google api client.js (gapi)
And there is an Official Doc talking about this,
https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/.
One thing in the doc is that it uses window.init() inside of init which will be causing an infinite loop.
As willlma pointed out, we should use a different name for the function.
But I have met an error of
Uncaught TypeError: window.initGAPI is not a function
The project is created by using yeoman generator for angular.
The order of loading js in index.html
<script src="scripts/app.js"></script>
<script src="scripts/controllers/main.js"></script>
<script src="scripts/controllers/about.js"></script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
app.js(only the latter part of this file):
var init = function(){
console.log("gapi init");
window.initGAPI();
}
main.js:
angular.module('testApp')
.controller('MainCtrl', function ($scope, $window) {
$window.initGAPI = function(){
console.log("controller inti");
}
$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];
});
I have put some logs to test and found that the definition for $window.initGAPI is executed before loading the cliet.js which calls window.init, but inside of window.init, window.initGAPI is not defined. It seems in the main controller, defining a function called initGAPI to object window failed.
I think is not so easy to correctly load both libraries each in its way, because both are asynchronous in a different way.
Instead, is easier to let one load the other.
The first and simplier approach is to put the client.js lib at the end of all scripts, and then do a manual Angular bootstrap when the Google library is fully loaded.
Other method could be the opposite, so let Angular create the Google client.js script tag and load the library.
Check this project: https://github.com/canemacchina/angular-google-client
I've create this library to help me using Google client library in Angular, and it use the second method...
I had a similar issue. Perhaps you can try something like this for the init function so that it retries again when the controller is not loaded yet.
function init() {
if (window.initGapi != undefined) {
window.initGapi();
}
else {
setTimeout(init, 500); // wait for 500 ms
}
}

Javascript \ Angular function help - mental block

I don't know what it is about JS, but I have a mental block. I apologize for the dumb question, but I'm at a loss because no matter how much I read I cannot get the academics into practice. Especially when it comes to nested functions.
I have a controller, lets say FileCtrl. Inside of it I have the the following that listens for file added to an input field via a directive. I'm attempting to inject an Angular JS factory service service called fileReader (a queue service for HTML5 FileReader).
However,I keep getting a undefined error on fileReader. I know why because, it cannot see fileReader, but injecting it at $scope.$on and then again on $scope.$apply doesn't work. Also, adding fileReader as a closure at the end of $scope.$on doesn't work either.
I should add that I can see the args.file and if I remove the fileReader code it will push the file no problem, but I then have no thumbnail. So I it works, just not with the fileReader and that is because Im doing something wrong with injection.
Side note, to Vals comment below I use apply as I found there was a image render sync issue without it which works fine for smaller images, but with larger images it freezes which is why I'm attempting to create and use a $q fileReader service. I suppose another way to solve for it would be to create a watch / directive on the array entry and when img comes back with the 64 encode string populate the html element ... like I said JS mental block :)
myApp.controller('FileController', ['$scope', 'FileReaderService', function($scope, FileReaderService ){
$scope.$on("fileSelected", function (event, args) {
$scope.$apply(function () {
$scope.progress = 0;
fileReader.readAsDataUrl(args.file, $scope)
.then(function(result) {
$scope.imageSrc = result;
});
$scope.files.push(args.file);
});
});
});
In AngularJS not all functions are been processed by Dependency Injection. In Controllers, Directives (in definition of directive and in controller, not on link or compile), Servicies AngularJS inject requested instances, but in some other functions (like event listeners) arguments are passed by position.
In your case you need to put fileReader into definition on controller, not on event listener.
Also you need to remove apply because event listeners added via $on are included into digest loop.
Thanks to all for your replies. Val you made me go back and do a little more research and I found the answer with a little debugging. Not sure I understand why yet, but I have an idea.
If there is an error in your factory service, in my case, FileReaderService angular won't always explode when bootstrapping the service, will only explode when you call the service, which makes kind of makes sense. If something is wrong in the service the entire service will not boot. Also, you won't get any error message when injecting it into the controller. I had to place a watch on the module and noticed there was a reference error. I found I had a missing function.
Purely inexperience on my end, but I kept trying to capture the results form the $q service, which is was doing fine, but then attempting to inject to outside the $q return i.e. I was attempting to capture $scope.imageSrc = result and insert it post the .then, which doesn't work as you have a sync issue. I could see the value in the $scope.files, but it would not console.log or show up in HTML. So I moved all the file manipulation into the .then and it works perfectly. Logical when you think about it :) why have a $q if you not going to use it ... lol.
// problem code
fileReader.readAsDataUrl(args.file, $scope)
.then(function(result) {
$scope.imageSrc = result;
});
// cannot and should not try to work the results outside the return promise
$scope.files.imgSource = $scope.imageSrc;
$scope.files.push(args.file);
//Fixed and working code
myApp.controller('FileController', ['$scope', 'FileReaderService', function($scope, FileReaderService ){
var reader;
$scope.files = [];
//listen for the file selected event
$scope.$on("fileSelected", function (event, args) {
$scope.progress = 0;
var file = args.file;
FileReaderService.readAsDataUrl(file, $scope)
.then(function(result) {
file.imgSource = result;
$scope.files.push(file);
});
});
});

How to fix injector error after Angular minification build?

Before speaking, I read about it made ​​recommendations but still causing error. Look the short code:
function IndexController($scope, $route, $routeParams, $location){
$scope.sfv = project.version.name;
}
angular.module("TkwebMobile", ['ngRoute', 'ngCookies'])
.controller('IndexController', ['$scope', '$route', '$routeParams', '$location', IndexController]);
Only this and the error persists. I'm using grunt to "uglify", and I'm also using the "concat" to unite the codes in a "lib". Even I using "injection" recommended in the Angular documentation.
Uncaught Error: [$injector:modulerr] Failed to instantiate module TkwebMobile due to:
Error: [$injector:unpr] Unknown provider: a
Is it problem of grunt concat? (grunt-contrib-concat)
This is due to your minification, specifically options to minify and mangle your variable names.
Angular determines what value to inject into your functions from the name of the parameters. For example...
angular.factory('MyFactory', function($location) {...});
...will cause angular to look for whatever dependency is named '$location' and then call your function with the $location value passed as it's parameter.
When you minify your javascript, with an option called mangle turned on, then the variable names get mangled. The previous function will turn into this...
angular.factory('MyFactory', function(a) {...});
Angular no longer has the correct parameter name in your source code, as $location is now a. This saves on size of your javascript but totally destroys Angular's implicit dependency resolution. You can solve this in one of two ways.
The first is a feature that angular provides for you.
angular.factory('MyFactory', ['$location', function(a) {...}]);
You provide the names of the parameters in an array, with the last element of the array being the function to inject the parameters into. This way, it doesn't matter what you call your parameters in the code, and the minifier will never change a string literal so Angular always knows what you're wanting.
The other way if you don't want to lose the convenience of not having to use the array notation is to turn off the mangle setting on your minifier. This obviously means you don't minify to the same degree, but ask yourself if it's really worth those extra bytes.
A halfway house is to use something like ngMin, to allow annotation of the array notation into your code and then continue with the minification. This is the best of both world's imo, but increases the complexity of deploying your clientside js.
EDIT
The correct settings to turn off the mangle behaviour in grunt would be this...
uglify: {
options: {
report: 'min',
mangle: false
}
}
But the ngAnnotate package can avoid this. See here for more info. (ngAnnotate is the package that has taken over ngMin)
I've had a similar problem. It turned out that I was not properly identifying and formatting the dependencies for controllers and services, etc. I believe I discovered this by looking at the minification output. (It was rough, let me tell you.)
Basically I had to look through all my files and verify that the dependency list matched what I was using in my controllers and services. It's strange because it worked without the changes.
Here is an example of what I had to do:
Original:
angular.module('FootCtrl', []).controller('FooterController', function($scope) {
$scope.footer = 'Copyright \u00A9 ' + new Date().getFullYear() + name;
});
Fixed
angular.module('FootCtrl', []).controller('FooterController', ["$scope", function($scope) {
$scope.footer = 'Copyright \u00A9 ' + new Date().getFullYear() + name;
}]);
Maybe take note of where I use single quotes vs. double quotes as well.
Hopefully this helps a bit. I'm not sure if you have more code than what is shown- if not, I'm not too sure.
I had this problem and it took me a lot of time to figure out what the issue was because I tried disabling mangling of variable names, using $inject array instead of just passing the services and provider names to function definitions while relying on angular implicit dependency injection but still the problem persisted. It turned out that in one of my controller which uses IIFE, was missing semicolon at the end. Look at the code below.
Before:
(function(){
})()
The above code works okay before minification due to automatic semicolon insertion but it breaks after minification because the absence of semicolon screw things up. So after correction it looked like below.
Correct:
(function(){
})();
This fixed my problem. I hope this might help some one
Note: I use grunt useminPrepare, usemin, copy, uglify and ngAnnotate.

How to inject an entire module dynamically in AngularJS?

Alright, so I'm trying to set-up $httpBackend to use as a mock server for local development against an API. I have two services in separate modules: search and searchMock:
search returns a $resource object that exposes the API verbs when in staging or production environments, and works as expected
searchMock exposes $httpBackend which, in turn, responds with mock JSON objects, and works as expected by itself
I have another service, APIInjector, that determines what the current environment is based on a config file that's included dynamically by Grunt when the app is built and injects either search or searchMock accordingly.
My problem is, as far as I can tell from searching high and low, $httpBackend needs to be set-up within a module's run method. The problem with this is I can't inject the run method within my APIInjector's conditional logic.
How can I expose $httpBackend if the dev environment condition is met, and my $resource service otherwise? Note that I didn't include the calling controller's or the searchService's code, but I can if needed for clarification.
searchMock:
var searchMockService = angular.module('searchMockService', []);
searchMockService.run([
'$httpBackend',
function($httpBackend) {
results = [{name: 'John'}, {name: 'Jane'}];
$httpBackend.whenGET('/search').respond(results);
}]);
APIInjector:
var APIInjectorService = angular.module('APIInjectorService', [
'searchService',
'searchMockService'
]);
APIInjectorService.factory('APIInjector', [
'$injector',
'ENV_CONF',
function($injector, ENV_CONF) {
var isDevelopment = ENV_CONF.IS_DEVELOPMENT;
// Check to see if we're in dev and, if so, inject the local API services
if (isDevelopment) {
return {
Search: // Not sure what to do here to expose searchMock's run method???
};
} else {
return {
Search: $injector.get('Search') // This returns $resource from the search service, as expected
};
}
}]);
Just to put a final point on this question, I ultimately pulled this out to my build script (Grunt) and abandoned the above approach completely. Instead, I'm passing in the environment I want to build for (development, staging, production, etc) as a flag to Grunt and then loading the appropriate API service during the build.
Hope that helps someone else trying to implement backendless development!
As bad as it looks at first place, i'd use the document.write method,if you are not using requirejs(it shouldnt be a problem with requirejs).
1 - bootstrap your ENV_CONF variable in a script tag as first script in HEAD tag.
2 - load all the common scripts
3 - test the ENV_CONF.IS_DEVELOPMENT in another SCRIPT tag.
4 - if true document.write('<script src="angular-mocks.js"></script><script src="main-dev-module.js"></script>')
5 - if false document.write('<script src="main-prod-module.js"></script>')
6 - profit
That way you dont need to deal with ENV_CONF.IS_DEVELOPMENT inside your modules.
html5 boilerplate uses this technique to either fetch jquery from a cdn or locally
https://github.com/h5bp/html5-boilerplate/blob/master/index.html
Edit: I'm sure there are cleaner ways to inject services dynamically in angularjs,but that's what i'd do.I'd like someone to propose a better alternative.

Categories

Resources