I'm pretty new to Angular and I'm using firebase as my backend. I was hoping someone could debug this issue. When I first go to my page www.mywebsite.com/#defaultHash the data doesn't load into the DOM, it does after visiting another hash link and coming back though.
My controller is like this:
/* initialize data */
var fb = new Firebase('https://asdf.firebaseio.com/');
/* set data to automatically update on change */
fb.on('value', function(snapshot) {
var data = snapshot.val();
$scope.propertyConfiguration = data.products;
console.log($scope.propertyConfiguration);
console.log("Data retrieved");
});
/* save data on button submit */
$scope.saveConfigs = function(){
var setFBref = new Firebase('https://asdf.firebaseio.com/products');
setFBref.update($scope.propertyConfiguration);
console.log("configurations saved!");
};
I have 3 hash routes say "Shared", "Registration", and "Home" with otherwise.redirectTo set to "Shared".(They all use this controller) Here's the error that occurs: (all "links" are href="#hashWhereever")
1) Go to website.com/#Shared or just refresh. Console logs $scope.propertyConfiguration and "Data Retrieved". DOM shows nothing.
2) Click to website.com/#Registration, console logs $scope data properly, DOM is loaded correctly.
3) Click back to website.com/#Shared, console logs $scope data properly yet this time DOM loads correctly.
4) Refresh currently correctly loaded website.com/#Shared. DOM elements disappear.
Since $scope.data is correct in all the cases here, shouldn't Angular make sure the DOM reflects the model properly? Why is it that the DOM loads correctly only when I am clicking to the page from another link.
I can "fix" it by adding window.location.hash = "Shared" but it throws a huge amount of errors in the console.
FIXED:(sorta)
The function $scope.$apply() forces the view to sync with the model. I'd answer this question myself and close it but I'm still wondering why the view doesn't load correctly when I correctly assign a value to $scope. If Angular's "dirty checking" checks whenever there is a possibility the model has changed, doesn't assigning a value to $scope overqualify?
Angular has no way to know you've assigned a value to $scope.variable. There's no magic here. When you run a directive (ng-click/ng-submit) or Angular internal functions, they all call $apply() and trigger a digest (a check of the dirty flags and update routine).
A possibly safer approach than $apply would be to use $timeout. Currently, if you call a write op in Firebase, it could synchronously trigger an event listener (child_added, child_changed, value, etc). This could cause you to call $apply while still within a $apply scope. If you do this, an Error is thrown. $timeout bypasses this.
See this SO Question for a bit more on the topic of digest and $timeout.
This doc in the Angular Developer Guide covers how compile works; very great background read for any serious Angular dev.
Also, you can save yourself a good deal of energy by using the official Firebase bindings for Angular, which already take all of these implementation details into account.
Vaguely Related Note: In the not-too-distant future, Angular will be able to take advantage of Object.observe magic to handle these updates.
Related
I have code like this:
someRequestToServer().then(response => {
console.log('phase before nav = ' + $rootScope.$$phase);
$location.path('/somepath');
console.log('phase post nav = ' + $rootScope.$$phase);
return response;
});
both of the print lines return with '$digest' which implies I am in a digest cycle ... yet the $location.path call is simply not working -- specifically browser URL browser doesn't change. In fact this is the only part that does work.
Also, after the call, location.path() returns the proper URL, what is failing is its ability to propagate to the browser and automatically route the page. While I am able to force the routing with a $route.reload() (as suggested in comments), I still can't get the browser URL to change which puts the page in bad place where the URL and the state of the page don't match.
I was sort of able to work around this by using window.location.href directly, but for various reasons this is not a great solution.
there is a lot about $location.path not working because it is called outside of an angular scope (therefore you have to force it with an apply), but any ideas when it seems like it is in digest cycle?
I currently have a Chat application which opens a new Chatwindow for every Chat (just like on facebook & co). To get this working so far I did some weird hacks since all of the n open chatwindows use the same $scope Variables. This is neither good programming nor does it help with bugfixing later on.
Therefore I'd like to redesign the chat part of my application and use one MessageCtrl instance for every Chatwindow. Is this possible in angular.js and if yes, how could I implement it?
If not, can you give me some guidelines on how to implement it "the angular way"?
edit: What I'm currently doing:
I create the Chatwindow from the MessageCtrl and save the necessary data into a MessageService. Since the next time a user writes a message in one of the Chatwindows I dont know if the $scope Variables are set correctly I check the MessageService again to find the correct Chat.
The problem is currently the only way of knowing what the correct chat is for me by saving the chat id in the parent <div id=<chatId> of the chatwindow. Thats far from good, but the only solution I got working so far
edit2: my code:
When a user starts a new Chat the following happens in some Ctrl:
`$rootScope.newChat = {roomId: roomId};
the MessageCtrl listens on this:
$rootScope.$watch('newChat', function (newVal, oldVal) {
startChatWindow();
// other preperation like setting $scope.roomId
}
startChatWindow() just appends the following html:
var $el = "<div id='" + $scope.roomId + "'class='bottomChat'>";
<!-- other things, like displaying the old messages -->
</div>";
$("#messageTab").append($compile($el)($scope));
PS: by chatwindow I just mean a visually appearing window, in reality it's just a styled like a window. This also means that every chatwindow uses the same messageCtrl. Which also means that I loose reference to e.g. $scope.roomId
edit3: SOLUTION
after removing the jQuery code and creating a directive every chatWindow has it's own Ctrl.
I'm trying to use the Angular-UI TinyMCE directive in my Angular app.
What happens is, I query an endpoint, it returns an array of objects. I have a function that then converts that to a long string with HTML tags in it. Then that data is set to a $scope.tinymceModel
This all works fine. I can console.log($scope.tinymceModel) and its the proper data.
The problem is the HTML parse function needs to run after the endpoint query is returned. So I've called the function inside the .success() callback. For some reason when I set the $scope.tinymceModel inside of the callback the TinyMCE directive ignores it. Even if I make it $scope.tinymceModel = 'test' but if I place $scope.tinymceModel = 'test' outside of the callback it shows up in tinymce just fine.
This tells me that for some reason when the TinyMCE directive is loaded it needs the tinymceModel to already be populated with data. I'm not sure how I get around this.
This also tells me that I may have another problem after this. The next task with TinyMCE is the user can then edit the text, click a button and the app will send a POST with the updated info inside tinymceModel If this was a regular text box it would be simple because of the data-binding. However it seems TinyMCE doesn't play well with databinding.
Any ideas?
I've attempted to recreate what you're describing (substituting $http with $timeout) to no avail. Here's my solution and it seems to be working just fine.
HTML:
<div ng-controller="MainCtrl">
<textarea ui-tinymce="" class="form-control" ng-model="someHtml"></textarea>
</div>
JavaScript:
angular.module('testTinymceApp')
.controller('MainCtrl', function ($scope, $timeout) {
$timeout(function() {
$scope.someHtml = '<h1>HELLO THERE</h1>'
}, 7000);
// This does the same thing with an XHR request instead of timeout
// $http.get('/some/data/').success(function(result) {
// $scope.someHtml = '<h1>HELLO THERE</h1>'
// });
});
I thought maybe you could compare with your own application? I know for a fact that this works with XHR requests. I'm building a CMS at work that uses what I assume is an identical workflow.
The someHtml attribute in this snippet will also be valid HTML under the covers, so sending it back in a POST request should be extremely easy.
If this is not sufficient, please provide further explanation.
Figured it out!, the issue has to do with a bug in the TinyMCE Directive. By default there is no priority set. Setting it to a value of 1 or higher fixes it. It seems that the current version of Ui-TinyMCE Directive has this fixed, but the version I pulled down less than a month ago didn't have it fixed.
I'm trying to work out where the best place to run a long-running load operation is using Durandal.
From what I can tell, the general recommendation for loading data is in the ViewModel's activate method, which is what I usually do - something like:
viewModel.activate = function () {
var loadPromise = myService.loadData();
return $.when(loadPromise).then(function (loadedData) {
viewModel.data(data);
});
};
I know that if I don't return the promise here, then there's usually problems with the bindings - as this question and answer indicates.
However, executing a long running load operation in the activate method makes the app "freeze" while the load operation completes. For example, what if my load was now something like this?
viewModel.activate = function () {
// All loads return a promise
var firstLoad = myService.loadFirstData();
var secondLoad = myService.loadSecondData();
var thirdLoad = myService.loadThirdDataWhichTakesAges();
return $.when(firstLoad, secondLoad, thirdLoad).then(function (one, two, three) {
viewModel.one(one);
viewModel.two(two);
viewModel.three(three);
});
};
In this scenario, the URL is updated to reflect the page which is being loaded, but the page content still shows the previous page (which is what I mean by "freezes").
Ideally, it would be good if the URL should change to the new page, and the page content should show the new page too (even though the data for that page has not yet been returned). Then, as each load operation returns, the relevant part of the page should be updated when the data is bound into the view model.
Is there a recommended way for achieving this inside Durandal?
My current solution is to kick-off the load in the activate method, and then populate the data in the viewAttached method:
var loadPromise;
viewModel.activate = function () {
// All loads return a promise
var firstLoad = myService.loadFirstData();
var secondLoad = myService.loadSecondData();
var thirdLoad = myService.loadThirdDataWhichTakesAges();
loadPromise = $.when(firstLoad, secondLoad, thirdLoad);
// Don't return the promise - let activation proceed.
};
viewModel.viewAttached = function () {
$.when(loadPromise).then(function (one, two, three) {
viewModel.one(one);
viewModel.two(two);
viewModel.three(three);
});
};
It seems to work, but I remember reading somewhere that relying on viewAttached wasn't a good solution. I'm also not sure if there is potential for a race condition since I'm allowing the activate to proceed.
Any other recommendations?
You don't have to return a promise but in that case you must handle this in you knockout bindings so you woun't bind to elements that are undefined. You can try to get rid of that 'return' in activate but add a property indicating if model is still loading. Something like this:
viewModel.isLoading = ko.observable(false);
viewModel.activate = function () {
isLoading(true);
var loadPromise = myService.loadData();
$.when(loadPromise).then(function (loadedData) {
viewModel.data(data);
isLoading(false);
});
};
And then, in your view, you can have a section that shows up when view is still loading and one that shows up when loading is done. Sometinhg like:
<div data-bind:"visible: isLoading()">Loading Data....</div>
<div data-bind:"visible: !isLoading()">Put your regular view with bindings here. Loading is done so bindings will work.</div>
Which version of Durandal are you using? In Durandal 2.0.0pre you would be allowed NOT returning a promise in activate so that the composition of the view (without data) could happen immediately.
You might consider refactoring viewModel.one etc. into a module that returns a constructor function, so that each one, two, three would be responsible for retrieving their own data. That way you first two calls wouldn't have to wait on loadThirdDataWhichTakesAges. That would make sense in scenarios where one, two, three are not heavily depend on each other.
For reference; I posted a similar question on the Durandal Google Group (effectively asking if using activate and viewAttached in this manner is an OK idea) and got this reply from Rob Eisenberg:
That will probably work. The problem is that Knockout will destroy
databindings on elements if the properties are updated and the element
isn't currently in the document. This can happen depending on the
timing of the async code. Because of the way composition worked in
1.x, this would cause problems if you didn't return the promise from your activate function. It should work better in viewAttached, but
depending on the nature of your composition, the view may be attached
to its parent, but still not in the document. It depends on the depth
of the composition. So, you could encounter issues with this too if
you have this in a deeply composed module. Unfortunately, there isn't
a clean way about it in Durandal 1.x due to the knockout behavior. In
Durandal 2.x we have reworked composition so that this problem is
non-existent and returning the promise is no longer necessary (though
you can still do it). Durandal 2.0 will be releasing in about two
weeks.
I have a dojo button bar which is bound to a csjs function. This function does a partialrefreshget() on a datable control. The datatable control contains a view as its datasource.
In the this.keys property I have defined some logic to see if the partialrefresh was triggered by checking for the context.getSubmittedValue(). While experimenting with this technique I noticed that the following code is triggered twice.
<xp:this.keys><![CDATA[#{javascript:
var vec = new java.util.Vector()
vec.add("category");
if(context.getSubmittedValue()!=null){
var x = context.getSubmittedValue().trim();
print("--")
}
return vec;}]]></xp:this.keys>
the print statement is printed twice to the console and the logic is therefore triggered twice. Can someone explain to me why this happens and what I can do about it? Should i check for submittedvalues somewhere else or?
I think if you implement a phase listener to print out each phase step, you'll see that this.keys is evaluated twice during the LifeCycle. Probably once during Render Response, and the other during Restore View or something. I would avoid putting application logic within property calculations as it can be triggered at times you would not think it should be unless you are very in tuned with the application lifecycle.
I actually see the submit two or three times on some controls. I have heard that it is an anomalie in the JSP engine that has not been resolved.
What I do is write the vec to a request scope variable after it is computed. then add logic before it is computed to fetch the request scope variable and if it exisits, return it instead of recomputing the value.
After a bit of testing i gave up calling my own partialrefreshget method.the extlib dojo toolbar contains a onclick event which is triggerd when on a node the submitvalue is set. In this onclik event i added code like
Var v = context.getsubmittedvaleu();
If("action".equals(v)){
// do stuff that changes the dataset..
}
The event handler is set to partial refresh a datatable wich receives the new data. This is a much cleaner implementation than checking the submittedvalue in the datasource ( as stated by (jeremy hodge).
This way the datasource is only refreshed once.
As a sidenote i would like add that it would be nice to add such an event directly to the treenode(s) as I would do in standard java swing /awt dev by adding a controllistener to a button.