AngularJS: Update template binding via directive - javascript

I modified the AngularUI-Bootstrap direvtive for tooltips to help me display error messages, here is what I did so far: http://jsfiddle.net/distractedBySquirrels/TaAHZ/
But I have a problem with the dynamic messages. The first time you mouseover the first input field, the message is in the wrong place, this is because the model is not correctly updated yet. Of course, after the second mouseover the tooltip is in the right place. But if the message to display changes again the same failure occurs.
I am using $parse to update the template:
$parse('tt_content').assign( scope,
getErrorMessage( ctrl.$error ) || attrs.tooltip
);
Seems like not all the listeners have revceived and updated and "ttHeight" and "ttWidth" are still set to the "old" values. The correct update of the message/model only works with the $observe, which is illustrated by the third example.
I am stuck and don't know what I did wrong? :(

The problem is that when you modify your models with $apply, you need to manually activate the digest cycle in order to notify all watchers of such models. So basically, you need to add scope.$digest(); whenever you change the scope doing things like scope.$apply( show );
I modified your code with my suggestions here http://jsfiddle.net/TaAHZ/2/

Related

Not able to make two way data binding work on an array

I am trying to create kind of a drag and drop playground using AngularJS.
The code is on plunker. And to understand the question, mostly you'll need to see the plunker.
https://plnkr.co/edit/i82UOOqHRSJyEPN9293E
I am trying to create node like structures here which can be dragged over to the playground on the right side.
On drag, I am adding the node id to the nodeChain variable in $scope.
Now I am iterating the nodeChain array in UI with ng-repeat where I am drawing the node just to check that the nodes added to the chain are reflecting correctly.
However,
I do not see the nodeChain updating from watchCollection logs as well as the nodes drawn on the playground are not updating when new nodes are added.
Can someone please tell me why the binding is not taking effect?
Thanks in advance!
You need to trigger a $digest cycle to update the values. When the $digest cycle starts, it fires each of the watchers. These watchers check if the current value of the scope model is different from last calculated value. If yes, then the corresponding listener function executes. As a result if you have any expressions in the view they will be updated.
if (!Bounds.within(event.pageX, event.pageY, leftPane)) {
scope.$parent.nodeChain.push(NodeChain.getNodeFromId(attr.nid))
scope.$apply();
}
Working Plunker:https://plnkr.co/edit/SNNsVOZps5Fy35dAQIqk?p=preview
You are getting ng-repeat dupes error in your console because there are duplicate values in your array nodeChain.
If i understand your question correctly, you want to add the dropped nodes in the below white space. Please see the plunkr.
You should add $scope.apply() when you change the models outside the scope.

modalService cannot talk with controller and view

The code at this plnkr has a modal which pops up when a user clicks on a "Click to take quiz" button which calls a controller method that in turn calls a modal service. To get the plnkr to work, click anywhere in the code and press the space bar to add white space in a way that does not effect syntax. This will trigger plnkr to re-initialize the app and make the modal pop up after you click the button.
The problem is that the text printed in the modal does not update dynamically when timeLeft variable counts down. And also, the user's button click does not update the quizAnswer variable. In short, the modal is not able to talk interactively with the calling controller and view.
What specific changes need to be made to the plnkr to get the modal text to show the dynamic countdown, and to get the modal buttons to change the value of the $scope.quizAnswer variable?
Also, I have been carefully reading the documentation at this link. I think that the answer may be related to:
1.) $uibModal's options parameter passed in open(options) contains the parameter scope that defines the parent scope to be used for the modal's content, and also property bindToController which, when set to true, binds the scope property to a specific controller defined by controllerAs.
2.) The open(options) method returns a modal instance, which includes close(result) and dismiss(reason).
I suspect that the solution lies in these methods and parameters, but I am looking for good examples and would appreciate some experienced eyes looking at this problem.
NOTE: The solution to this came in the comments below the accepted answer, especially the link to another posting that contains 2 lines of code for emitting the modal button click's results back to the parent controller.
You have a number of issues.
First, takeQuiz at navigation.js - line 16, should be attached to $scope, not this, since this will mutate depending on context.
Second, $scope.$apply and $scope.$digst(); at navigation.js - lines 29/30 are unnecessary since you will already be in a digest cycle. They should be removed else they'll trigger an error.
Finally (and this is the meat of your issue), you are misunderstanding how modal options are bound across when creating a modal instance. It is NOT two-way binding; it is a single extends from one object to another. As a result, trying to bind to the options (or creating a concatenated string with the timeRemaining) will not update once it's bound across.
Instead, one possibility is to create an event handler inside of the modal and broadcast on each tick, updating the modal. In addition, if you pass the body text as prepend and append text, it is easier to insert your timestamp value:
You will need to inject (and broadcast from) $rootScope in your navigationController, since the modalService is registered somewhere very high in the scope chain.
On each tick, broadcast the time remaining navigation.js:
$rootScope.$broadcast('timeRemainingTick', $scope.timeRemaining);
In your modalService.js, register to receive the event inside of the controller assignment:
var timeRemainingUnbind = $scope.$on('timeRemainingTick', function(event, newTick) {
$scope.modalOptions.timeRemaining = newTick;
});
Finally, make sure that you unbind the event by calling timeRemainingUnbind() in the close events of your modal to prevent memory leaks:
$scope.modalOptions.ok = function (result) {
timeRemainingUnbind();
$modalInstance.close(result);
};
$scope.modalOptions.close = function (result) {
timeRemainingUnbind();
$modalInstance.dismiss('cancel');
};
See my working forked plunker here

Update scope from directive and watch with another one

Here is the problem :
I try to do things like this :
Click on a directive triggers an event
Event sends data to a rootScope function which sets a scope variable value with new data
Another directive has a ng-model in its template which is supposed to reflect the previously changed var
Data should be updated in the view AND model. Model is ok (update done), but view update does not seem to trigger automatically, it only updates when I click on the "show button" (see fiddle)
Here is the corresponding jsfiddle I made, with comments :
http://jsfiddle.net/wkmm576m/7/
Here is the code which I think is making trouble... :
app.directive('addEditInput', function() {
return {
restrict : 'E',
template : '<div id="editForm"><input type="text" ng-model="currentVar.name" /><br/></div>'
}
});
In fact, I return a template, with ng-model. But the ng-model is not updated when its value changes from anywhere else...
Tbh, I'm a bit confused with directives and scopes communications - data....
Thanks for reading / help
Short answer - you need to call $scope.$apply in top controller:
http://plnkr.co/edit/QEjzlsJj4fh1eeqBXSvt?p=preview
Long answer: you bind usual js function to usual js event, so when user clicks on element - your function executes. Thats all. When you use ng-click - it will trigger digest cycle and your bindings will be updated. So you need to trigger it manually, so you call $scope.$apply.
Note: better do not use jquery where you do not need to. I.e. use $element.bind('click',function(){ instead of $().on...

Variable defined on the $scope not updating until next cycle

Here is a plunker - http://plnkr.co/edit/PaG1k5N37BTOflObnN7K?p=preview
Scenario 1:
Step 1 - When "m" is entered in the tags input box, Marie is displayed as a suggestion.
Step 2 - When "j" is entered, John appears as a suggestion.
Step 3 - When "m" is entered again, there are no suggestions. I have implemented this functionality in script.js. This works perfectly as, Marie is being removed from $scope.to because it is already added to the tags input box in step 1.
Scenario 2:
Step 1 - When "m" is entered in the tags input box, Marie is displayed as a suggestion.
Step 2 - When "m" is entered again, Marie still appears as a suggestion.
So, until John or Ghita are entered in the tags input box, Marie keeps appearing as a suggestion. As soon as John is entered (e.g. the above scenario) Marie is not displayed as a suggestion anymore.
Does anyone have ideas as to why this behavior is caused?
I tried to use $scope.$apply(), but it gives me an error that says, a $digest cycle is already in progress. So, when $scope.$digest() is already executing, why is the $scope.to variable not getting updated?
You are running into an issue with the Typeahead library. You will want to wrap your tag added callback in a $timeout call.
$scope.$on("decipher.tags.added", function(info, obj) {
$timeout(function(){
tagAdded(info, obj);
});
});
Will fix the issue. You can see that the angular-tags directive itself does this when filtering the internal tag list:
https://github.com/boneskull/angular-tags/blob/master/src/tags.js#L69
Edit:
Here's a working fork of your original Plunker.
I think the problem is the angular-tags library itself:
https://github.com/boneskull/angular-tags/issues/28
Looks like it's not being updated for versions of angular greater than 1.2.0-rc.2
As stated by another user, it seems as though this is a bug with the current version of the tags you're using. A temporary workaround is to use $compile to update the tags in tagAdded. To use it, include $compile as a parameter in your emailViewController along with $scope and inject it as a dependency at the bottom along with $scope. After that, you can call
$compile(document.getElementsByTagName("tags")[0])($scope);
to update it. A side effect of this is that the input area loses focus each time a tag is added, which may or may not make a large impact in the usability of your app.
Refer to http://plnkr.co/edit/UVELXnrjOKasRnuGVA0e?p=preview for a demonstration of using $compile
You can try to use a $timeout $timeout(function(){//your code}); In angular $timeout forces your digest to update your variables, and it doesn't thrown a digest already in progress error.
try this:
if(!$scope.$$phase) {
$scope.$digest();
}
$scope.$$phase is a flag set to true while angular is in the $digest cycle

Knockoutjs ObservableArray Keeps Doubling in Size

I am trying to understand some slightly odd behavior I am seeing in a page I am making using KnockoutJS. An observable array seems to get duplicate items every time I clear and reapply bindings. The quickest way to understand the problem is to look at this JSFiddle demo. Just click any edit button several times, and watch this list grow!
The heart of the code for this demo is in the following method:
var _bindItemDetail = function (jsonData) {
//clear existing bindings
ko.cleanNode($("#itemdetails").get(0));
// observables in selected item.
_viewModel.SelectedItem(ko.mapping.fromJS(jsonData));
// Apply Bindings
ko.applyBindings(_viewModel.SelectedItem, $("#itemdetails").get(0));
};
The essence of what I am trying to achieve is to create a list and details page in one. The list JSON is fetch on initial page load, and the detail JSON is fetched and bound to the "detail" html whenever an edit link is clicked.
In addition to solving the problem, I am trying to understand the behavior, and learn some lessons about how to clean up stale resources properly when using knockout.
Thanks for any help
The problem is that in your _bindItemDetail function, you are reapplying the bindings on your modified view where you already had replicated the elements.
var _bindItemDetail = function (jsonData) {
//clear existing bindings
ko.cleanNode($("#itemdetails").get(0));
// observables in selected item.
_viewModel.SelectedItem(ko.mapping.fromJS(jsonData));
// Apply Bindings
ko.applyBindings(_viewModel.SelectedItem, $("#itemdetails").get(0));
};
ko.cleanNode() merely removes bindings from the elements, it doesn't revert the view back to its initial state. In general, you should only ever call ko.applyBindings on a set of nodes once. Doing it more than once, is just asking for problems.
Frankly you're not really making good use of knockout. The majority of your code is using jquery to handle all the low-level details. The point of using knockout is to not have to worry about those lower level details.
I've adjusted your fiddle a bit to make better use of knockout with less emphasis on using jquery.
In the view:
Used the click binding to handle your Edit click events.
Used the with binding to conditionally show the editor fields. The stopBindings handler is not needed.
In the view model:
Added the click handler editClicked to the view model.
Removed jquery event bindings.
Removed the ko.cleanNode/ko.applyBindings combo you had when binding items. You shouldn't do that and you just don't need it, knockout will handle all that for you.
Updated fiddle

Categories

Resources