Angular newbie here.
I have the following div:
<div id="mainfr" data-curpos="dy[ {{curPosObj.dy}} ]" ...>blah blah </div>
And in my controller I have:
var nxtest = angular.module('nxtest', []);
var appController = nxtest.controller('AppCtrl', ['$scope', function ($scope) {
$scope.curPosObj = { dir: "down", dy:5 };
$scope.clr = window.setTimeout(function(){ $scope.curPosObj.dy = 777;
window.clearTimeout($scope.clr); }, 5000); //see if the attr responds to a random change
}])
In firebug, inspecting the scope object shows that it is indeed modified. I want to understand why the bound attribute {{curPosObj.dy}} is not 'bound' and the view does not respond to the changing values? Thanks very much in advance.
Update: added link to plunker as suggested - the red text never changes:
http://plnkr.co/edit/HJxEpgR8VepxuT47zJDJ?p=preview
Update 2: OK so there may be a separate issue here - the red text is in a pseudo element whose contrent attribute depends on the main divs attribute... and I'm not calling setAttribute anywhere... but regardless: in firebug, the 'data-curpos' attribute itself is NOT updating, never mind the pseudo elem that depends on it...
That's because angular doesn't tracking scope changes out of the dygest cycle and window.setTimeout that case. You should use the $timeout service instead of window.setTimeout or put code which chenge scope into $scope.$apply call
angularjs API reference - $timeout service
angularjs API reference - scope guide
try this:
var nxtest = angular.module('nxtest', []);
var appController = nxtest.controller('AppCtrl', ['$scope', '$timeout',
function($scope, $timeout) {
$scope.curPosObj = {
dir: "down",
dy: 5
};
$scope.clrPromise = $timeout(function() {
$scope.curPosObj.dy = 777;
}, 5000); //see if the attr responds to a random change
}
])
Related
I have the following code : http://codepen.io/Andarius/pen/Ggryge .
When the user draws a 'crop_area', the crop button should not be disabled anymore.
Why is the value no_crop_area (from the Image factory) not updated when drawing ?
Is it a scope problem ?
Also, I'm pretty new to AngularJS and was wondering what is the best practice when passing a factory to a controller (if there is one)
Given a factory :
myApp.factory('myFactory', function () {
return {foo:{bar:2}};
});
Is it better to do :
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = myFactory.bar;
}]);
or
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = self.foo.bar;
}]);
I have forked your code. Here is a working one.
http://codepen.io/anon/pen/qERpew?editors=101
The button is not getting enabled because, you are doing all the processing related to creating crop area by using javascript event handlers, so the code related to $scope (ie.angular related), will not come into effect. To have them in effect, you have to wrap the code related to $scope into $scope.$apply(function(){ // Your $scope variable update code.}).
PS: As per convention you should not use $scope name inside directive.Instead of that use scope.
I have seen a few exmaples on stack overflow about this ng-init issue, although I cant seem to find one which references it with the use of a controller.
I have called the function in the controller by having the following in the html file
<div class="tab-container" ng-controller = "ExampleController" ng-init = "init()" >
In the controller:
$scope.init = function(){
alert("do something");
};
It does run, but it runs before the components have loaded on the screen.
Am i missing something?
Thanks
ng-init is supposed to work like this, because it's used to initialize data.
A very simple example:
<ul ng-init="list = [1,2,3,4]">
<li ng-repeat="l in list"></li>
</ul>
If you are trying to run something while your controller loads, it's actually much simpler than you thought:
app.controller('mainCtrl', function ($scope) {
var init = function ($scope) {
// do whatever you need to do to initialize your controller
$scope.someData = ["Hey", "I'm", "Alive"]
$scope.otherData = localStorage.getItem('myBackup')
}
init()
})
Or even simpler, if you don't need the function (no closures or whatever)
app.controller('mainCtrl', function ($scope) {
// do whatever you need to do to initialize your controller
$scope.someData = ["Hey", "I'm", "Alive"]
$scope.otherData = localStorage.getItem('myBackup')
})
Edit - assuming you're using ngView:
To have the code run on when the page is fully loaded you should set a watcher on the event $viewContentLoaded, like this:
$scope.$on('$viewContentLoaded', function(){
//Here your view content is fully loaded !!
});
app.controller('mainCtrl', function ($scope) {
// This event is triggered when the view has finished loading
$scope.$on('$viewContentLoaded', function() {
$scope.someData = ["Hey", "I'm", "Alive"]
$scope.otherData = localStorage.getItem('myBackup')
})
})
another option is using jquery. It would fit if you depend on many elements. But make sure to load jquery with a version of your choice to project.
loading jquery (insert version where it's ...):
<script src="https://code.jquery.com/jquery-..."></script>
the js code:
$(document).ready(function() {
alert("do something");
});
My application initializes an object graph in $rootScope, like this ...
var myApp = angular.module('myApp', []);
myApp.run(function ($rootScope) {
$rootScope.myObject = { value: 1 };
});
... and then consumes data from that object graph (1-way binding only), like this ...
<p>The value is: {{myObject.value}}</p>
This works fine, but if I subsequently (after page rendering has completed) try to update the $rootScope and replace the original object with a new one, it is ignored. I initially assumed that this was because AngularJS keeps a reference to the original object, even though I have replaced it.
However, if I wrap the the consuming HTML in a controller, I am able to repeatedly update its scope in the intended manner and the modifications are correctly reflected in the page.
myApp.controller('MyController', function ($scope, $timeout) {
$scope.myObject = { value: 3 };
$timeout(function() {
$scope.myObject = { value: 4 };
$timeout(function () {
$scope.myObject = { value: 5 };
}, 1000);
}, 1000);
});
Is there any way to accomplish this via the $rootScope, or can it only be done inside a controller? Also, is there a more recommended pattern for implementing such operations? Specifically, I need a way to replace complete object graphs that are consumed by AngularJS from outside of AngularJS code.
Thanks, in advance, for your suggestions,
Tim
Edit: As suggested in comments, I have tried executing the change inside $apply, but it doesn't help:
setTimeout(function() {
var injector = angular.injector(["ng", "myApp"]);
var rootScope = injector.get("$rootScope");
rootScope.$apply(function () {
rootScope.myObject = { value: 6 };
});
console.log("rootScope updated");
}, 5000);
Except for very, very rare cases or debugging purposes, doing this is just BAD practice (or an indication of BAD application design)!
For the very, very rare cases (or debugging), you can do it like this:
Access an element that you know is part of the app and wrap it as a jqLite/jQuery element.
Get the element's Scope and then the $rootScope by accessing .scope().$root. (There are other ways as well.)
Do whatever you do, but wrap it in $rootScope.$apply(), so Angular will know something is going on and do its magic.
E.g.:
function badPractice() {
var $body = angular.element(document.body); // 1
var $rootScope = $body.scope().$root; // 2
$rootScope.$apply(function () { // 3
$rootScope.someText = 'This is BAD practice :(';
});
}
See, also, this short demo.
EDIT
Angular 1.3.x introduced an option to disable debug-info from being attached to DOM elements (including the scope): $compileProvider.debugInfoEnabled()
It is advisable to disable debug-info in production (for performance's sake), which means that the above method would not work any more.
If you just want to debug a live (production) instance, you can call angular.reloadWithDebugInfo(), which will reload the page with debug-info enabled.
Alternatively, you can go with Plan B (accessing the $rootScope through an element's injector):
function badPracticePlanB() {
var $body = angular.element(document.body); // 1
var $rootScope = $body.injector().get('$rootScope'); // 2b
$rootScope.$apply(function () { // 3
$rootScope.someText = 'This is BAD practice too :(';
});
}
After you update the $rootScope call $rootScope.$apply() to update the bindings.
Think of modifying the scopes as an atomic operation and $apply() commits those changes.
If you want to update root scope's object, inject $rootScope into your controller:
myApp.controller('MyController', function ($scope, $timeout, $rootScope) {
$rootScope.myObject = { value: 3 };
$timeout(function() {
$rootScope.myObject = { value: 4 };
$timeout(function () {
$rootScope.myObject = { value: 5 };
}, 1000);
}, 1000);
});
Demo fiddle
I have the following service in my app:
uaInProgressApp.factory('uaProgressService',
function(uaApiInterface, $timeout, $rootScope){
var factory = {};
factory.taskResource = uaApiInterface.taskResource()
factory.taskList = [];
factory.cron = undefined;
factory.updateTaskList = function() {
factory.taskResource.query(function(data){
factory.taskList = data;
$rootScope.$digest
console.log(factory.taskList);
});
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.startCron = function () {
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.stopCron = function (){
$timeout.cancel(factory.cron);
}
return factory;
});
Then I use it in a controller like this:
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
$scope.taskList = uaProgressService.taskList;
});
}
);
So basically my service update factory.taskList every 5 seconds and I linked this factory.taskList to $scope.taskList. I then tried different methods like $apply, $digest but changes on factory.taskList are not reflected in my controller and view $scope.taskList.
It remains empty in my template. Do you know how I can propagate these changes ?
While using $watch may solve the problem, it is not the most efficient solution. You might want to change the way you are storing the data in the service.
The problem is that you are replacing the memory location that your taskList is associated to every time you assign it a new value while the scope is stuck pointing to the old location. You can see this happening in this plunk.
Take a heap snapshots with Chrome when you first load the plunk and, after you click the button, you will see that the memory location the scope points to is never updated while the list points to a different memory location.
You can easily fix this by having your service hold an object that contains the variable that may change (something like data:{task:[], x:[], z:[]}). In this case "data" should never be changed but any of its members may be changed whenever you need to. You then pass this data variable to the scope and, as long as you don't override it by trying to assign "data" to something else, whenever a field inside data changes the scope will know about it and will update correctly.
This plunk shows the same example running using the fix suggested above. No need to use any watchers in this situation and if it ever happens that something is not updated on the view you know that all you need to do is run a scope $apply to update the view.
This way you eliminate the need for watchers that frequently compare variables for changes and the ugly setup involved in cases when you need to watch many variables. The only issue with this approach is that on your view (html) you will have "data." prefixing everything where you used to just have the variable name.
Angular (unlike Ember and some other frameworks), does not provide special wrapped objects which semi-magically stay in sync. The objects you are manipulating are plain javascript objects and just like saying var a = b; does not link the variables a and b, saying $scope.taskList = uaProgressService.taskList does not link those two values.
For this kind of link-ing, angular provides $watch on $scope. You can watch the value of the uaProgressService.taskList and update the value on $scope when it changes:
$scope.$watch(function () { return uaProgressService.taskList }, function (newVal, oldVal) {
if (typeof newVal !== 'undefined') {
$scope.taskList = uaProgressService.taskList;
}
});
The first expression passed to the $watch function is executed on every $digest loop and the second argument is the function which is invoked with the new and the old value.
I'm not sure if thats help but what I am doing is bind the function to $scope.value. For example
angular
.module("testApp", [])
.service("myDataService", function(){
this.dataContainer = {
valA : "car",
valB : "bike"
}
})
.controller("testCtrl", [
"$scope",
"myDataService",
function($scope, myDataService){
$scope.data = function(){
return myDataService.dataContainer;
};
}]);
Then I just bind it in DOM as
<li ng-repeat="(key,value) in data() "></li>
This way you can avoid to using $watch in your code.
No $watch or etc. is required. You can simply define the following
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
});
$scope.getTaskList = function() {
return uaProgressService.taskList;
};
});
Because the function getTaskList belongs to $scope its return value will be evaluated (and updated) on every change of uaProgressService.taskList
Lightweight alternative is that during controller initialization you subscribe to a notifier pattern set up in the service.
Something like:
app.controller('YourCtrl'['yourSvc', function(yourSvc){
yourSvc.awaitUpdate('YourCtrl',function(){
$scope.someValue = yourSvc.someValue;
});
}]);
And the service has something like:
app.service('yourSvc', ['$http',function($http){
var self = this;
self.notificationSubscribers={};
self.awaitUpdate=function(key,callback){
self.notificationSubscribers[key]=callback;
};
self.notifySubscribers=function(){
angular.forEach(self.notificationSubscribers,
function(callback,key){
callback();
});
};
$http.get('someUrl').then(
function(response){
self.importantData=response.data;
self.notifySubscribers();
}
);
}]);
This can let you fine tune more carefully when your controllers refresh from a service.
Like Gabriel Piacenti said, no watches are needed if you wrap the changing data into an object.
BUT for updating the changed service data in the scope correctly, it is important that the scope value of the controller that uses the service data does not point directly to the changing data (field). Instead the scope value must point to the object that wraps the changing data.
The following code should explain this more clear. In my example i use an NLS Service for translating. The NLS Tokens are getting updated via http.
The Service:
app.factory('nlsService', ['$http', function($http) {
var data = {
get: {
ressources : "gdc.ressources",
maintenance : "gdc.mm.maintenance",
prewarning : "gdc.mobMaint.prewarning",
}
};
// ... asynchron change the data.get = ajaxResult.data...
return data;
}]);
Controller and scope expression
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.get.maintenance}}</span>
</div>
The above code works, but first i wanted to access my NLS Tokens directly (see the following snippet) and here the values did not become updated.
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService.get;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.maintenance}}</span>
</div>
I'm working on an app the uses multiple (but similar) controllers (controllerA) on a page and another controller (controllerB) that could change some properties, but ONLY if the element of controllerA was previously selected.
(in controllerA some properties could have default values, that are passed and applied by using data- attributes)
In this stage, I can only modify the last color property with controllerB (which makes sense, because that's the active scope. My question is, how can I change controllerB scope to be the "active" scope-copy of the selected controllerA?
// Code goes here
var webApp = angular.module("webApp", []);
webApp.controller("controllerA", function($scope, $rootScope, $element, styleFactory){
$scope.selected = false;
var color = angular.element($element[0]).attr("data-style-color");
styleFactory.setColor(color);
$scope.data = styleFactory.getData();
$scope.select = function(){
$scope.selected = !$scope.selected;
}
});
webApp.controller("controllerB", function($scope, $rootScope, $element, styleFactory){
$scope.data = styleFactory.getData();
});
webApp.factory("styleFactory", function(){
var data = {"style":"color:yellow"}
return {
setColor: function(color){
data = {"style":"color:"+color};
},
getData: function(){
return data;
}
};
});
for full example:
http://plnkr.co/edit/He2m7ArfRSur9Igq2eur?p=preview
thanks,
--iM
You can see a modified plnkr that works as you described here.
Personally, I would restructure the architecture of the app and rethink the styleFactory implementation.
Some points for thought:
data = {"style":"color:"+color} replaces the whole data object, thus $scope.data = styleFactory.getData() works only for the last controllerA as the reference is lost for all others.
Try creating a parent controller that will be on top of controllerA and controllerB.
Try solving this problem using directives.