Replace assignment with deferred assignment - javascript

In my angular.js page I extend $scope with all attributes I need.
Before, I simply extended $scope with
angular.extend($scope,{
myAttr : aService.getValues()
});
However, turns out I have to add a promise (angular file access) to aService.getValues. Hence, I cant keep the above syntax but have to replace it with
angular.extend($scope,{
myAttr : null
});
aService.getValues().then(function(values){$scope.myAttr = values;})
That's annoying because I have to replace quite a bit of code and am afraid to add bugs. Any alternatives?

The only thing I can suggest is a slightly prettier re-doing of your own solution:
aService.getValues().then(function(values) {
angular.extend($scope, {
myAttr: values
})
});
Is it critical that $scope is extended synchronously? If it is, I don't see any way of feeding asynchronous data into it any other way.
EDIT
If you're reworking your logic, consider using $stateProvider with a resolve property: https://github.com/angular-ui/ui-router/wiki (Scroll down to the section that talks about resolve). Long story short, everything in the resolve must run before the state controller is initialized. This way you can do all your async operations in the resolve, and only let it load after you have everything you need.

Related

Use directive as value for ng-if?

In my application, I need to be able to easily determine whether a user is authenticated within my HTML and in all templates.
My first thought on how to do this was to create a "global" controller and apply it to which simply set $scope.isAuthenticated = $auth.isAuthenticated.
However, after doing some reading, I discovered that this wasn't considered good practice. Instead, I created a directive, which would just return $auth.isAuthenticated().
angular.module('HordeWebClient')
.directive('isAuthenticated', function($auth) {
return $auth.isAuthenticated();
});
And then in my templates, I figured I could just use .... This doesn't work, the element isn't rendered regardless of the state of $auth.isAuthenticated.
The Safari error console doesn't show any problems, so I'm stuck on where to start in fixing this. Any pointers would be greatly appreciated.
In my opinion you should use .run on your main module.
angular.module('app').run(function(){
if(!isAuthenticated){
redirectToLoginView();
}
});
More: https://docs.angularjs.org/guide/module

restangular save ignores changes to restangular object

I'm trying to call save on a restangularized object, but the save method is completely ignoring any changes made to the object, it seems to have bound the original unmodified object.
When I run this in the debugger I see that when my saveSkill method (see below) is entered right before I call save on it the skill object will reflect the changes I made to it's name and description fields. If I then do a "step into" I go into Restangular.save method. However, the 'this' variable within the restangular.save method has my old skill, with the name and description equal to whatever they were when loaded. It's ignoring the changes I made to my skill.
The only way I could see this happening is if someone called bind on the save, though I can't why rectangular would do that? My only guess is it's due to my calling $object, but I can't find much in way of documentation to confirm this.
I'm afraid I can't copy and paste, all my code examples are typed by hand so forgive any obvious syntax issues as typos. I don't know who much I need to describe so here is the shortened version, I can retype more if needed:
state('skill.detail', {
url: '/:id',
data: {pageTitle: 'Skill Detail'},
tempalte: 'template.tpl.html'
controller: 'SkillFormController',
resolve: {
isCreate: (function(){ return false;},
skill: function(SkillService, $stateParams){
return SkillService.get($stateParams.id, {"$expand": "people"}).$object;
},
});
my SkillService looks like this:
angular.module('project.skill').('SkillService', ['Restangular, function(Retangular) {
var route="skills";
var SkillService= Restangular.all(route);
SkillService.restangularize= function(element, parent) {
var skill=Restangular.restangluarizeElement(parent, elment, route);
return skill;
};
return SkillService;
}];
Inside of my template.tpl.html I have your standard text boxes bound to name and description, and a save button. The save button calls saveSkill(skill) of my SkillFormController which looks like this:
$scope.saveSkill=function(skill) {
skill.save().then(function returnedSkill) {
toaster.pop('success', "YES!", returnedSkill.name + " saved.");
...(other irrelevant stuff)
};
If it matters I have an addElementTransformer hook that runs a method calling skilll.addRestangularMethod() to add a getPeople method to all skill objects. I don't include the code since I doubt it's relevant, but if needed to I can elaborate on it.
I got this to work, though I honestly still don't know entirely why it works I know the fix I used.
First, as stated in comments restangular does bind all of it's methods to the original restangularizedObject. This usually works since it's simply aliasing the restangularied object, so long as you use that object your modifications will work.
This can be an issue with Restangular.copy() vs angular.copy. Restangualar.copy() makes sure to restangularize the copied object properly, rebinding restangualr methods to the new copy objects. If you call only Angular.copy() instead of Restangualar.copy() you will get results like mine above.
However, I was not doing any copy of the object (okay, I saved a master copy to revert to if cancel was hit, but that used Restangular.copy() and besides which wasn't being used in my simple save scenario).
As far as I can tell my problem was using the .$object call on the restangular promise. I walked through restangular enough to see it was doing some extra logic restangularizing methods after a promise returns, but I didn't get to the point of following the $object's logic. However, replacing the $object call with a then() function that did nothing but save the returned result has fixed my issues. If someone can explain how I would love to update this question, but I can't justify using work time to try to further hunt down a fixed problem even if I really would like to understand the cause better.

How can you reset Knockout for each Jasmine test?

I have some feature tests that run with a fixture (loaded with jasmine-jquery) that has some Knockout bindings in the HTML. At the begin of each test I want to start with a viewModel in its initial state.
If I call applyBindings() in the beforeEach() with a new instance of the viewModel I get this error from Knockout
Error: You cannot apply bindings multiple times to the same element.
If I try to revert the properties of the existing viewModel to match its initial state I still get an error. I believe this is because the fixture's HTML is removed after each test - this probably breaks the bindings?
I've also tried a suggestion that came up when Googling which was to use the cleanNode function in Knockout. This isn't part of the API (it only designed to be used by Knockout internally) and no matter what I tried it didn't resolve the issue.
It feels like I'm taking the wrong approach to this. tl;dr; How does everybody else test Knockout with Jasmine?
Thanks for any help
I generally append an element in beforeEach, apply bindings to that element, and ko.removeNode on it in afterEach. Something like:
var fixture;
beforeEach(function() {
fixture = document.createElement("div");
document.body.appendChild(fixture);
});
afterEach(function() {
ko.removeNode(fixture);
});
Then use fixture as the second argument to any applyBindings calls like: ko.applyBindings(myTestViewModel, fixture);
The way I solved this was changing my js to check for jasmine e.g.:
if (!window.jasmine)
ko.applyBindings(viewModel);

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.

Should angular.module be set variable on AngularJS

I’ve read two AngularJS sample on Github. I’m confused to how to modularize controller. The first on is set variable(foodMeApp) and re-use it. Controllers need less arguments. It’s easier to read.
But, second one doesn’t use variable(foodMeApp) and has more arguments. I think this way is frequently used on AngularJS samples.
Is this any merit to use the second way?
1.https://github.com/IgorMinar/foodme
var foodMeApp = angular.module('foodMeApp', ['ngResource']);
foodMeApp.constant('CONFIG_A', {
baseUrl: '/databases/',
});
foodMeApp.controller('HogeController', function HogeController($scope, CONFIG_A) {
console.log(“less arguments");
});
2.https://github.com/angular-app/angular-app
angular.module('foodMeApp', ['ngResource']);
angular.module('foodMeApp').constant('CONFIG_B', {
baseUrl: '/databases/',
});
angular.module('foodMeApp').controller('HogeController', ['$scope', 'CONFIG_B', function($scope, CONFIG_B) {
console.log("more arguments");
}]);
since angular.module('...') ,constant,provide,controller,factory ... return the same mdoule you can chain module method calls if you want to or not... it's just javascript.
you can write
angular.module('foo',[])
.controller('bar',function(){})
.service('baz',functinon(){})
.constant('buzz',something)
.value('bizz',somethingelse);
it makes no different.
This example is using an array for your dependencies which are going to be injected into your controller. The array method is typically used when the code is going to minified at some time and allows angularjs to know exactly which items will be injected. If this array were not there, angular would only see 'a', 'b' as the items injected into the function and would not be able to figure out what those things were. Items in the array are strings and will not be changed when the minification happens.
angular.module('foodMeApp').controller('HogeController', ['$scope', 'CONFIG_B',
function($scope, CONFIG_B) {
console.log("more arguments");
}]);
Hope this helps explain the differences which you have seen.
There is no such 'merit' of going for 2nd option. 1st approach is more popular as it avoids repeating angular.module('foodMeApp') so many times.
If you give it a name "foodMeApp" you can directly use foodMeApp as you would have used in 2nd approach. Less repetition, simpler life.

Categories

Resources