I recently 'discovered' that Javascript is a "Call by sharing" (wikipedia explanation) language, meaning that in essence, everything is passed by value however the original contents of an object can still be changed. A quick example:
function changeObjectProperties(obj) {
obj.p = 20;
}
var obj = { p: 10; }
changeObjectProperties(obj);
console.log(obj.p); // will print 20; it's changed!
This made me wonder if this can be used within Angular to 'watch' variables without using $scope.$watch. The following works.
controllers.js:
.controller('Listener', function($scope, UserService) {
$scope.user = UserService.getUser();
})
.controller('Changer', function($scope, UserService) {
// let's imagine the UI has some button that changes the e-mailadres
$scope.buttonClick = function() {
UserService.setEmail('foo#bar.com');
}
});
services.js:
.factory('UserService', function() {
var user = {
name: 'Foo',
email: 'example#example.com'
};
return {
getUser: function() { return user; }
setEmail: function(email) { user.email = email; }
};
});
The $scope.user variable within the Listener controller is updated when the user clicks the button within the Changer controller. This change is visible would this variable be displayed in the HTML.
The most obvious pitfall of course is that the object itself can not be changed, as the object reference is then changed and the Listener controller is listening to the wrong reference.
I've searched around to find if this is being done and if its considered good coding. I haven't been able to find anything, possibly because I don't know the correct terms to use. So, is this technique actively being used? Is it considered a good practice or is there some pitfall I'm not aware of?
You can certainly employ the base language to avoid excessive watches. Keep in mind there is no way to avoid watch altogether, because watch is fundamentally how Angular tracks the model. In other words, if you do nothing else, putting something on scope is going to cause Angular to watch it so they it knows when to update the rendered UI. This is the essence of the digest loop. To the extent you can manage your internal communication around the way this digest loop operates, you can reduce or eliminate the overhead of extraneous watchers.
So in essence you can use a JavaScript-first approach to coordinate state and internal object transitions and avoid explicit watchers because Angular will pick up the changes via its own watches that are part of the digest loop.
I think what you are talking about is in line with what I wrote about in my 2nd most common mistake AngularJS developers make, which is abusing $watch: http://csharperimage.jeremylikness.com/2014/11/the-top-5-mistakes-angularjs-developers_28.html
Related
Now, I know this is an off-the-wall question and that there is probably not going to be any API level access for doing this. I also understand that doing this is completely unnecessary. However, the implementation that I am aiming for requires for me to be able to make {{ variable }} look inside of the $scope.object instead of the $scope itself.
For example:
Controller($scope) {
$scope.active = ...;
}
In this case you can get the active object through {{active}} and any child elements through {{active.broken}} however for the sake of humoring me, lets assume that all of the variables I'm ever going to have to obtain is going to be part of that active object. So I'll be typing things like.. (Data not related)
{{active.title}}
{{active.author}}
{{active.content}}
You could just say "Well why not just move the title/author/content into the $scope and keep it outside of the active object, as that would achieve the desired result of this:
{{title}}
{{author}}
{{content}}
Well, that's where the problem comes in. This is a controller that is not exposed to my end-user, however the end-user does have a completely mutable object (in this example: active) that they can modify. This object has many [optional] listeners and callbacks that are invoked by the application controller when necessary.
The user's that have used my application during testing have commented on what a drag it is to have to type in active. before everything in order to get the data they wanted. Considering data from the $scope is never rendered to the screen, and only data from active is, I was wondering if perhaps there was a way to change where AngularJS looks when parsing/binding data.
If the goal is to evaluate expressions in a different context than $scope, that can be done with the $parse service.
app.controller("myVm", function($scope, $parse) {
var vm = $scope;
vm.active = { a: 5,
b: 3
};
var.myFn = $parse("a+b");
vm.total = myFn(vm.active);
console.log(vm.total);
});
The above example shows how to evaluate the expression a+b using the properties of the $scope.active object.
The DEMO on JSFiddle
I currently have an application with a rather complex wizard to create a data record. The wizard consists of 3 steps, each associated with a nested view and a controller. Only the data record itself is shared among all three scopes and each controller contributes additional data to the main record.
But they also have scope specific data, that will be used to render additional fields which are only relevant to that nested scope.
I want to be able to go back and forth between the wizard steps but currently it looks like the nested scopes get discarded as soon as I move on to another nested view. I looked up the scope lifecycle in the developer guide: https://docs.angularjs.org/guide/scope#scope-life-cycle
But I does not really tell me how the scope lifecycle applies to nested scopes and how I can prevent these scopes from being discarded. Of course I could move all the data of the nested scopes into the parent scope, but to me that would just feel like a workaround, because actually that data is only relevant to the individual scopes.
I'll try to give a short example:
angular.module('app').controller('ParentCtrl', function ($scope) {
...
$scope.dataRecord = {};
}
angular.module('app').controller('Child1Ctrl', function ($scope) {
...
$scope.dataRecord.test = 'a';
$scope.childScope1SpecificData = '123';
}
angular.module('app').controller('Child2Ctrl', function ($scope) {
...
$scope.dataRecord.test2 = 'b';
$scope.childScope2SpecificData = '456';
}
When I now switch back and forth between the two childscopes, the dataRecord will be adjusted properly, but changes to childScope1SpecificData (via an input field from the template) will be discarded as soon as I switch to Child2Ctrl and back.
Is there a way to persist this data which switching the scope or is it meant to be discarded and I am simply using it wrong?
Thanks
EDIT:
Ok I looked into the factory approach. Maybe to make it more plastic: The additional data, that belongs to each child scope, is a fileuploader with its associated upload queue. Only in a later validation step these pictures actually become part of the datarecord, but until then I don't want the uploaded images to get lost upon switching views.
So what I could do is to externalize the whole fileupload logic into a factory that returns fileuploaders associated to IDs. Whenever a child scope requests the same id the factory will return the same fileuploader. Different Ids will return different uploaders or new ones. That would pretty much solve the problem but would also mean that the data never gets discarded at all unless I really close the browser, because the factory now is absolutely independent of any scope. Since I only want to retain the data in the context of that wizard, I want the data to be discarded, as soon as I leave the wizard.
So after having looked into these other approaches, it seems like I have to go with the original idea: I have to attach the uploaders to the parent scope. So they will continue to exist when switching to other child views, but they will also be discarded as soon as I leave the wizard.
I hope that was correctly summarized
If you are using 'controller as' syntax, you can use this variant.
angular.module('app').controller('ParentCtrl', function ($scope) {
...
$scope.dataRecord = {};
}
angular.module('app').controller('Child1Ctrl', function ($scope) {
...
$scope.ParentCtrl.dataRecord.test = 'a';
$scope.ParentCtrl.childScope1SpecificData = '123';
}
angular.module('app').controller('Child2Ctrl', function ($scope) {
...
$scope.ParentCtrl.dataRecord.test2 = 'b';
$scope.ParentCtrl.childScope2SpecificData = '456';
}
So, you are changing ParentCtrl object in you parent scope, not for every instance.
Sorry, if it was no understandable
A few questions regarding structuring Angular code and the behavior of JavaScript when using variable bound vs private method naming function conventions. Is there a performance or stylistic reason for using variable bound functions / first class functions in AngularJS over private method naming? How is hoisting affected in each method? Would the second method below reduce the amount of hoisting performed and would this have a noticeable affect on application performance?
An example of private method naming. Is this a recommended way to structure Angular code?
(function () {
'use strict'
function modalController(dataItemsService, $scope) {
var vm = this;
function _getSelectedItems() {
return dataItemsService.SelectedItems();
}
function _deleteSelectedItems() {
dataItemService.DeleteItem();
$("#existConfirmDialog").modal('hide');
}
vm.SelectedItems = _getSelectedItems;
vm.deleteItemRecord = _deleteItemRecord;
}
angular.module('app').controller('modalController', ['dataItemService', '$scope', modalController]
})();
An example of variable bound functions. What about this method of structuring angular code within the controller - is there any disadvantage or advantage in terms of performance/style?
angular.module('appname').controller("NameCtrl", ["$scope", "$log", "$window", "$http", "$timeout", "SomeService",
function ($scope, $log, $window, $http, $timeout, TabService) {
//your controller code
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}
]);
The first method, using "private" methods and exposing them via public aliases, is referred to as the Revealing Module Pattern, although in the example the methods aren't actually private.
The latter is a pretty standard Constructor Pattern, using $scope as context.
Is there a performance or stylistic reason for using variable bound functions / first class functions in AngularJS over private method naming?
Is [there] a recommended way to structure Angular code?
TL;DR
Fundamentally, there isn't much difference between the two styles
above. One uses $scope, the other this. One Constructor function is defined in a closure, one is defined inline.
There are scenarios where you may want a private method or value.
There are also stylistic and (probably insignificant) performance
reasons for using the variable this/vm over $scope. These are not mutually exclusive.
You'll probably want to use a
basic, bare bones, old school Constructor Pattern, and a lot of
people are exposing state and behavior via this instead of $scope.
You can allow yourself data privacy in the Controller, but most of
the time this should be leveraged by a Service/Factory. The main exception is data representative of the state of the View.
Don't use jQuery in your Controller, please.
References:
AngularJS Style Guide by Todd Motto.
AngularJS Up & Running
To answer your question thoroughly, I think it important to understand the responsibility of the Controller. Every controller's job is to expose a strict set of state and behavior to a View. Put simply, only assign to this or $scope the things you don't mind your user seeing or playing with in your View.
The variable in question (vm, $scope) is the context (this) of the instance being created by the Controller function.
$scope is Angular's "special" context; it has some behaviors already defined on it for you to use (e.g. $scope.$watch). $scopes also follow an inheritance chain, i.e. a $scope inherits the state and behaviors assigned to its parent $scope.
Take these two controllers:
angular.module("Module")
.controller("Controller", ["$scope", function($scope) {
$scope.tab = 0;
$scope.incrementTab = function() {
$scope.tab++;
};
}])
.controller("OtherController", ["$scope", function($scope) {
// nothing
}]);
And a view
<div ng-controller="Controller">
<p>{{ tab }}</p>
<button ng-click="incrementTab();">Increment</button>
<div ng-controller="OtherController">
<p>{{ tab }}</p>
<button ng-click="incrementTab();">Increment</button>
</div>
</div>
Example here
What you'll notice is that even though we didn't define $scope.tab in OtherController, it inherits it from Controller because Controller is it's parent in the DOM. In both places where tab is displayed, you should see "0". This may be the "hoisting" you're referring to, although that is an entirely different concept.
What's going to happen when you click on the first button? In both places we've exposed "tab", they will now display "1". Both will also update and increment when you press the second button.
Of course, I may very well not want my child tab to be the same tab value as the parent. If you change OtherController to this:
.controller("OtherController", ["$scope", function($scope) {
$scope.tab = 42;
}]);
You'll notice that this behavior has changed - the values for tab are no longer in sync.
But now it's confusing: I have two things called "tab" that aren't the same. Someone else may write some code later down the line using "tab" and break my code inadvertently.
We used to resolve this by using a namespace on the $scope, e.g. $scope.vm and assign everything to the namespace: $scope.vm.tab = 0;
<div ng-controller="OtherController">
<p>{{ vm.tab }}</p>
<button ng-click="vm.incrementTab();">Increment</button>
</div>
Another approach is to use the simplicity and brevity of this and take advantage of the controllerAs syntax.
.controller("OtherController", function() {
this.tab = 0;
});
<div ng-controller="OtherController as oc">
<p>{{ oc.tab }}</p>
</div>
This may be more comfortable for people who are used to using plain JS, and it's also easier to avoid conflicts with other Angular sources this way. You can always change the namespace on the fly. It's also a bit "lighter" on performance since you're not creating a new $scope instance, but I'm not sure there's much gain.
In order to achieve privacy, I would recommend encapsulating your data in a Service or Factory. Remember, Controllers aren't always singletons; there is a 1:1 relationship between a View and a Controller and you may instantiate the same controller more than once! Factories and Service objects are, however, singletons. They're really good at storing shared data.
Let all Controllers get a copy of the state from the singleton, and make sure all Controllers are modifying the singleton state using behaviors defined on the Service/Factory.
function modalController(dataItemsService) {
var vm = this;
vm.selectedItems = dataItemsService.SelectedItems(); // get a copy of my data
vm.updateItem = dataItemService.UpdateItem; // update the source
}
But wait, how do I know when another part of my app has changed my private data? How do I know when to get a new copy of SelectedItems? This is where $scope.$watch comes into play:
function modalController(dataItemsService, $scope) {
var vm = this;
vm.updateItem = dataItemService.UpdateItem; // update the source
// periodically check the selectedItems and get a fresh copy.
$scope.$watch(dataItemsService.SelectedItems, function(items) {
vm.items = items;
});
// thanks $scope!
}
If your data is not shared, or if your private data is representative of the View layer and not the Model layer, then it's totally OK to keep that in the controller.
function Controller() {
var buttonClicked = false;
this.click = function() {
buttonClicked = true; // User can not lie and say they didn't.
};
}
Lastly, DO NOT USE JQUERY IN YOUR CONTROLLER, as your reference did!
$("#existConfirmDialog").modal('hide');
This example might not be purely evil, but avoid accessing and modifying the DOM outside a Directive, you don't want to break other parts of your app by modifying the DOM underneath it.
I'm building a growl like UI in angular. I'd like to expose it as a factory (or service) to make it available in my controllers. Calling growl.add will result in a change in the DOM, so it seems like I should have a directive take care of that, rather than doing direct DOM manipulation in the factory. Assuming that a factory-directive combo is the best option (and please correct me if that is not a good assumption), the question is:
How best to communicate between the factory and the directive?
Specifically, how best to send messages from the factory to the directive? Other questions have well covered sending information the other way, with onetime callback.
See below the working example. I suspect there is a better way though..
For reference, I have played with other options:
A) have the directive watch the service, e.g.
$scope.$watch(function(){
growl.someFunctionThatGetsNewData()},
function(newValue){
//update scope
})
But this means that someFunctionThatGetsNewData gets called in every digest cycle, which seem wasteful, since we know that the data only gets changed on growl.add
B) send an 'event', either via routescope, or via event bindings on the dom/window. Seem un-angular
Since neither of those options seem good, I'm using the one below, but it still feels hacky. The register function means that the directive and the factory are tightly coupled. But then again from usage perspective they are tightly bound - one is no good w/o the other.
It seem like the ideal solution would involve declaring a factory (or service) that includes the directive in its declaration (and perhaps functional scope) so that it exposes a single public interface. It seems icky to have two separate publicly declared components that entirely depend on each other, and which have tight coupling in the interfaces.
Working example - but there must be a better way..
vpModule.directive('vpGrowl',['$timeout', 'growl', function ($timeout, growl) {
return {
template: '<div>[[msg]]</div.',
link: function($scope, elm, attrs) {
growl.register(function(){
$scope.msg = growl.msg;
});
$scope.msg = growl.msg;
}
};
}]);
vpModule.factory('growl', ['$rootScope', '$sce', function($rootScope, $sce) {
var growl = {};
growl.msg = '';
var updateCallback = function(){};
growl.add = function(msg){
growl.msg = msg;
updateCallback();
};
growl.register = function(callback){
updateCallback = callback;
};
return growl;
}]);
I would have your growl service decide what to show, not the directive. So, the service handles any timers, state, etc. to decide when to hide/show messages. The service then exposes a collection of messages which the directive simply binds to.
The directive can inject the service and simply place it in scope, and then bind an ng-repeat to the service's collection. Yes, this does involve a watch, but you really don't need to worry about the performance of a single watch like this.
link: function(scope, elm, attrs) {
scope.growl = growl; // where 'growl' is the injected service
}
and then in the directive template:
<div ng-repeat="msg in growl.messages">
...
</div>
I would implement following logic:
Service growl defines some property growlProp on $rootScope & update it on each call of growl.add
Directive set watcher on $rootScope.growlProp
So directive knows nothing about service & service knows nothing about directve.
And additional overhead related to watcher is minimum.
I have my angular controller setup like most of the examples shown in the docs such that it is a global function. I assume that the controller class is being called when the angular engine sees the controller tag in the html.
My issue is that i want to pass in a parameter to my controller and i don't know how to do that because I'm not initializing it. I see some answers suggesting the use of ng-init. But my parameter is not a trivial string - it is a complex object that is being loaded by another (non-angular) part of my js. It is also not available right on load but takes a while to come along.
So i need a way to pass this object, when it finally finishes loading, into the controller (or scope) so that the controller can interact with it.
Is this possible?
You can use a service or a factory for this, combined with promises:
You can setup a factory that returns a promise, and create a global function (accessible from 3rd-party JS) to resolve the promise.
Note the $rootScope.$apply() call. Angular won't call the then function of a promise until an $apply cycle. See the $q docs.
app.factory('fromoutside', function($window, $q, $rootScope) {
var deferred = $q.defer();
$window.injectIntoAngularWorld = function(obj) {
deferred.resolve(obj);
$rootScope.$apply();
};
return deferred.promise;
});
And then in your controller, you can ask for the fromoutside service and bind to the data when it arrives:
app.controller('main', function($scope, fromoutside) {
fromoutside.then(function(obj) {
$scope.data = obj;
});
});
And then somewhere outside of Angular:
setTimeout(function() {
window.injectIntoAngularWorld({
A: 1,
B: 2,
C: 3
});
}, 2000);
Here's a fiddle of this.
Personally, I feel this is a little bit cleaner than reaching into an Angular controller via the DOM.
EDIT: Another approach
Mark Rajcok asked in a comment if this could be modified to allow getting data more than once.
Now, getting data more than once could mean incremental updates, or changing the object itself, or other things. But the main things that need to happen are getting the data into the Angular world and then getting the right angular scopes to run their $digests.
In this fiddle, I've shown one way, when you might just be getting updates to an Array from outside of angular.
It uses a similar trick as the promise example above.
Here's the main factory:
app.factory('data', function($window) {
var items = [];
var scopes = [];
$window.addItem = function(item) {
items.push(item);
angular.forEach(scopes, function(scope) {
scope.$digest();
});
};
return {
items: items,
register: function(scope) { scopes.push(scope); }
};
Like the previous example, we attach a function to the $window service (exposing it globally). The new bit is exposing a register function, which controllers that want updates to data should use to register themselves.
When the external JS calls into angular, we just loop over all the registered scopes and run a digest on each to make sure they're updated.
In your non-angular JavaScript, you can get access to the scope associated with a DOM element as follows:
angular.element(someDomElement).scope().someControllerFunction(delayedData);
I assume you can find someDomElement with a jQuery selector or something.