angularjs share data between controllers (service approach) - watcher is no called - javascript

I'm struggling with sharing data between controllers with the service approach in angularjs. From somewhere here on stackoverflow I got this working code (sorry, I miss the original source) and I extendet it.
jsbin sourcecode
(updatet version: http://jsbin.com/hadegec/5/edit?html,js,output)
All is working fine with the original source, adding items to cart and the total price is updated in the CartCtrl.
I tried to extend it with a very simple message. But while the items watcher works very fine, the watcher to my new messageCart is not working. I really cannot see the difference between the items update and my new message test. It must be the watcher, because if I set the $scope.messageCart inside the item-watcher (console.log("w-Items:"+Order.getmessage()); I allways get the correct string...
Any idea?

The solution is:
not watch on (unchagend) message string property of scope
$scope.ordernewmessage = Order.getOrderMessage();
$scope.$watch('ordernewmessage ', function() {
but watch a function return the service property
$scope.$watch(function(){return Order.message;}, function(){
The last thing I don't understand is the difference betweeen the array property 'items' and the string propery 'message'. I don't understand, why the '$scope.itemsCart' reference to 'Order.items' is detected as changed while the '$scope.ordernewmessage' set like this; '$scope.ordernewmessage = Order.getOrderMessage()' is not changed...

Related

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.

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);
});
});
});

knockout.js, breeze and dynatree/fancytree

This is really tricky to get my head around as I'm not used to this style of programming/data management.
All I'm trying to do at the moment is pass a json object returned via breeze into a dynatree or fancytree.
The examples that exist online all assume that the tree will do the ajax call via "initajax" or that some weirdly convoluted custom binding handler is needed into which various objects are passed:
ko.bindingHandlers.dynatree = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
setTimeout(function () {
$(element).dynatree({
noLink: true, // noLink is required to 'unlock' the checkboxes
minExpandLevel: 2
})
// the timeout value shows the correct knockout bindings BEFORE dynatree kicks in.
}, 1000);
}
}
This all seems too complicated to me, surely? I already have the json object, I know that's working. If I use knockout to "foreach" bind it to some plain html then all data is displayed just fine. In my mind all I need to do is initialize the tree div and pass it the json object... It's just that I have no idea how to do that!
I've tried using the jsfiddle here: http://jsfiddle.net/Ebram/UhA3m/5/ but chrome developer tools complain about the element having no "dynatree" method when the custom binding handler fires. It's passing in a "ul" element and that could be the problem - surely it should be passing in the tree div, not the ul element?
Anyhow, if anyone could point me in the right direction I'd hugely appreciate it. As I'm using John Papa's SPA methodology, I'm also unsure as to where I would put any separate js initialization code as the underlying viewmodel isn't the right place for me to be doing a $(#tree).dynatree initialization type call, is it? I must admit I've not got my head around this yet.
I suppose all I'm looking for is something along the lines of "once the viewmodel for this view has finished loading and the knockout binding is being done, initialize the dynatree div and pass this json object to the tree" if that makes sense in pseudocode?
I can hopefully point you in the approximate right direction.
It seems dynatree can also take JSON from a file as well as an AJAX request. In this example Lazy Loading, if you look in the source code, there's:
// In real life we would call a URL on the server like this:
...
// .. but here we use a local file instead:
Storing your data in a file to get it in seems awfully wasteful. Now that we know it's a little flexible in what it gets, let's see where it uses the data and maybe we can get it to use a local variable instead. let see where it loads it
Looking in the dynatree source, there's a function associated with appendAjax. (line 1774 in my source.) A little short on time at the moment, but I'd find where it gets the JSON and what it does with it. Perhaps you can do the same thing outside or mod the handling of ajaxOptions to take a variable with the JSON.

Querying the DOM in Windows 8 Apps from within a method

I'm struggling with this even after reading the MSDN documentation and the following online guides:
Codefoster
Stephen Walter
I think my problem is easy to fix and that I just am thinking about something in the wrong way. Basically I am querying my web service and on success running the following method. I am then trying to bind the result to my listview. For now I am using a hardcoded value publicMembers.itemlistwhich has been declared at the top of the document just to make sure I can actually bind to the list before doing it with my query results. Ignore line 2 for now.
Success Method:
_lookUpSuccess: function xhrSucceed(Result) {
var response = JSON.parse(Result.responseText);
listView = document.querySelector("#termTest");
ui.setOptions(listView, {
itemDataSource: publicMembers.itemList,
itemTemplate: document.querySelector(".itemtemplate"),
})
},
Now, instead of using document.querySelector, I have also tried with WinJS.Utilities.id and WinJS.Utilities.query, neither of which worked either. This doesn't break my code and introduce an error but it doesn't bind to the listview either so I think I have an issue in querying the right DOM element. However exactly the same code does work if I add it to the top of the script, it is only when I place it in this method that it stops working.
EDIT: Also to clarify, when I debug publicMembers.itemList is storing the values I expect them to be.
Please point out if I have explained things poorly and I will try and improve my question.
Thanks.
I haven't used WinJS.UI.setOptions, but rather this other way of setting the data source. Can you see if it works?
_lookUpSuccess: function xhrSucceed(result) {
var response = JSON.parse(result.responseText);
listView = document.querySelector("#termTest");
listView.winControl.itemDataSource = publicMembers.itemList;
},
This would assume you're defining the itemTemplate as part of the data-win-options attribute of your ListView's HTML element. You could also probably just do listView.winControl.itemTemplate = document.querySelector(".itemtemplate") if you prefer to set it programmatically.

Monkey patch not firing. (Guess this Monkey cant punch ducks.)

I am trying to patch the JsonRest query method. Nothing I do seams to have an effect. Below I would expect the query method to no longer work and just write "monkey punching a duck." out to the console. But alas the entire app keeps on working ignoring my blatant attempt to break it. Do I need to patch a specific instance?
dojo.require("dojo.store.JsonRest");
(function(query, options){dojo.store.JsonRest.query=function(){console.info("monkey punching a duck.");};})();
aprStore = new dojo.store.JsonRest({"target":"/web/rest/apr/","idProperty":"ID"});
var sqry = "?nq=aquerytorun";
aprStore.query(sqry).then(function(result){});
The main goal is I want at the underlying xhrGet so I can attach a callback to the error property. The .query(function,function) is ignoring my error function passed in as the second parameter. http 302 is what is driving me nuts at the moment.
It's not 100% clear what you're trying to do, but I suspect you need to put your function on the prototype object of that JsonRest thing:
dojo.store.JsonRest.prototype.query = function() { ... };
Then instances made from that constructor will have access to your "query" function.

Categories

Resources