Angular scope variable update not reflected in UI - angularjs

We are working on an HTML page which makes use of a Bootstrap tooltip on a certain <span> tag. For those who have not heard of tooltip, it is a popup of sorts which appears when hovering over the element to which it is attached. Here is a screenshot showing the <span> in question, and what happens on hover:
The premise behind adding the tooltip was that in the event that we truncate the text, the tooltip would provide an option for viewing the entire text.
However, we would now like to condtionally show the tooltip only when there is no ellipsis in the text. We defined the tooltip-enable property in the <span>:
<span uib-tooltip="{{someName}}" tooltip-placement="right" tooltip-enable="{{showToolTip}}">{{someNameShortened}}</span>
The key thing here is tooltip-enable="{{showToolTip}}", which binds the property to a scoped variable in the controller for this page. And here is the relevant (and abbreviated) controller code:
mainApp.controller('repoListController',['$scope', '$rootScope', ...,
function($scope,$rootScope, ...) {
$scope.showToolTip = false;
var repositoryList= function(){
repositoryService.getRepositoryList(function(data) {
var repoList = data;
repoList.shortenedDisplayName = repositoryService.getShortRepoName(repoList.repoName, DISPLAY_NAME_MAX_LENGTH);
// if the repository's name be sufficiently large (i.e. it has an ellipsis)
// then show the tooltip. Otherwise, the default value is false (see above)
if (repoList.repoName.length > DISPLAY_NAME_MAX_LENGTH) {
$scope.showTooltip = true;
}
});
}
repositoryList();
}]);
Based on the research I have done, the common solution for why a change to a scoped variable is not reflected in the UI is to run $scope.$apply(), or some variation on this. Running apply(), as I understand it, will tell Angular JS to do a digest cycle, which will propagate changes in the scope to the UI. However, trying to do an apply() from the code which toggles showToolTip resulted in errory. I inspected the value of $scope.$root.$$phase while running the code which updates the showToolTip variable, and the phase was digest.
So now I am at a loss to explain this. If the code is already in a digest, then why would changes not be reflected in the UI? Also, if the code is already in digest, then how could I force Angular to sync the changes to the UI?

Two things need fixing...
Don't use string interpolation for your boolean showToolTip
<span uib-tooltip="{{someName}}" tooltip-placement="right"
tooltip-enable="showToolTip">{{someNameShortened}}</span>
JavaScript variables / properties are case sensitive. In your getRepositoryList handler, you have $scope.showTooltip. It should be $scope.showToolTip (two capital "T"s)
Crappy Plunker demo ~ http://plnkr.co/edit/W7tgJmeQAJj0fmfT72PR?p=preview

Related

Angularjs form.$dirty

I'm able to find form data is changed or not using $dirty.
ex: I changed text box or drop down and then $dirty become true. If I reverted to old data still it is true. I need to know if my changes are reverted or not. Do we have any property in Angularjs? If property is true I want to enable save button otherwise it should be disable.
https://docs.angularjs.org/api/ng/type/form.FormController
I need to implement around 10 pages and each page has 10 text boxes and a couple of drop downs. So I don't want track each control manually in my pages.
You can try using this module: https://github.com/betsol/angular-input-modified
From the README file:
This Angular.js module adds additional properties and methods to the
ngModel and ngForm controllers, as well as CSS classes to the
underlying form elements to provide end-user with facilities to detect
and indicate changes in form data.
This extra functionality allows you to provide better usability with
forms. For example, you can add decorations to the form elements that
are actually changed. That way, user will see what values has changed
since last edit.
Also, you can reset an entire form or just a single field to it's
initial state (cancel all user edits) with just a single call to the
reset() method or lock new values (preserve new state) just by calling
overloaded $setPristine() method.
DISCLAIMER: I haven't tried it myself and I notice the author overwrites the ngModel directive instead of adding a decorator, which could be dangerous...but at the very least, you can look at the source and get an idea of how to write your own service or directive with similar functionality.
Even though it does not follow the usage of $dirty, but an implementation similar to this might be helpful for you in the case of a Save button on update.
Inside your html:
<form name="testForm" ng-controller="ExampleController" ng-submit=" save()">
<input ng-model="val" ng-change="change()"/>
<button ng-disabled="disableSave">Save</button>
</form>
Inside your controller:
.controller('ExampleController', ['$scope', function($scope) {
$scope.disableSave = true; // Keep save button disabled initially
$scope.val = 'Initial'; // Initial value of the variable
var copyVal = $scope.val; // Copy Initial value into a temp variable
$scope.change = function() {
$scope.disableSave = $scope.val === copyVal;
};
$scope.save = function() {
// Save the updated value (inside $scope.val)
console.log($scope.val);
// Re-disable the input box (on successful updation)
copyVal = $scope.val;
$scope.disableSave = true;
};
}]);
Here is a working plunkr for the same.

angular watch for angular-leaflet-directive

I am trying to place $watch on a value in angular to setup controls differently depending on instructions from the controller and I have tried to follow the lead in angular-leaflet-directive using leafletScope.$watch("variable").
I have added a new $watch with equality comparison as it's an object:
leafletScope.$watch("controls", function(controlOpts) {...}, true)
I then call it in the controller using:
angular.extend($scope, {
controls: {}
}
This is to initialise the controls as this seems to be required.
I then call it later on an event using:
$scope.controls = { new object }
If I log the change in controls and then also on the $watch event, I get the following sequence:
control $watch event logged
$scope.control change event logged
no further logs
The fact that the watch isn't called after the $scope is changed suggests I am doing this wrong.
Can anyone advise me where, or if I have reached the wrong conclusion in my simple test.
If I do the same and change my "center" model, I get:
center$watch event logged
$scope.center change event logged
center$watch event logged with new value
This turned out to be a clash between angular-leaflet-directive and the html minifier I was using html-minifier. The angular-leaflet-directive notation for leaflet controls is controls so in my html file I had
<leaflet center="centre" controls="controls" layers="layers" width="1200px" height="800px"></leaflet>
the html-minifier took controls="controls" to be a boolean attribute and with the collapse boolean attributes flag on reduced it to
<leaflet center="centre" controls layers="layers" width="1200px" height="800px"></leaflet>
Which wasn't good. I will request this is either better documented in angular-leaflet-directive or they change the name of controls to leaflet-controls or something to avoid this happening to unsuspecting folk like me in the future.
Incidently the true flag on the watch proved to be too resource intensive and I removed it and it still worked..

Foundation error/fade flashes with ngAnimate before ngModel loads

I've got a directive, with a field in it. The field is being passed a $scope with the model in it.
The field is ng-required="true".
Because there is a split second where the data isn't loaded into the template, there is an unsightly flash of red error text around the field.
I've tried template caching, changing the class colour, but none of it happens at the right time. What we've done in the interim is remove ng-animate. This stops the delay and flash and fade. However, this is a set back as animations would be swell in other places.
From here I will ask the concrete question:
As ngAnimate is injected in the app.js, is there a way to disable injection at a per element/controller/directive level?
As far as I know, injection cascades when it is included in the global app.js.
What I've done is set a flag to signal when the content has finished loading, and I don't show any content until that's been set to true.
On your controller
$scope.finishedLoading = function() {
... whatever logic you need to figure out if all your data is loaded ...
return $scope.myData != null;
}
Then in some part of your template that wraps the fields you want initially hidden:
<div ng-hide="finishedLoading">Please wait...</div>
<div ng-show="finishedLoading">
.. your page content here ..
</div>

Angularjs Bootstrap UI tooltip scope preventing watch from firing

I'm having an issue with a $watch that is really just boggling my brain. I'm almost certain that it is somehow a scope issue. Here's the basics. I'd like a tooltip on a span surrounding a checkbox and it's label so that the tooltip activates on hover over the checkbox or label. something like this.
(note:I've added the {{values.isChecked}} just to watch the values in the two different scopes)
HTML
{{values.isChecked}}
<span tooltip-placement="right" tooltip="This is my tooltip">
<input type="checkbox" id="myCheckbox" ng-model="values.isChecked">
<label for="myCheckbox"> My Checkbox</label> {{values.isChecked}}
</span>
from angular controller
$scope.values.isChecked = true;
$scope.watch("values.isChecked",function(newValue,oldValue){
alert("Made it to the watch");
}
The most odd behavior is that my watch catches the transition of the value from true to false. But does not catch the transition from false to true. I can click it several times and it will make it to the watch going from true to false, but not false to true. If it catches the true to false, and then catches true to false again, well, it HAD to have changed from false to true in order to trigger the watch again. I know the value is actually changing, the two places wehre I added it to the page via {{values.isChecked}}, both show the values change as expected, it's just not firing my watch when I CHECK the box. only when I UNCHECK it.
All my code is on a different box on an isolated network, so I can't actually copy and paste any, so tried to just type in the relevant stuff.
Also if I just take out the Span that has the tooltip on it, it works just fine. I'm aware that bootstrap UI's tooltip does create a new scope. so suspect that, but don't know why it works for one transition, but not the other.
I have even gone as far as capturing the scope inside the tooltip and adding my watch there such as...
myChildScope = angular.element('#myCheckBox').scope()
myChildScope.$watch("values.isChecked",function(newValue,oldValue){
...
It behaves incorrectly, the exact same way. Also behaves the exact same (bad) way if I try to add an ng-click or ng-change to the checkbox element.
Two things to try, I'm not sure how your code is setup but for issues of a watch not catching things when you think it should, generally one of these will work.
$scope.watch("values.isChecked",function(newValue,oldValue){
alert("Made it to the watch");
}, true);
The true tells it to compare for object equality using angular.equals instead of comparing for reference equality. Since it's a boolean primitive... probably more of what you want to use.
The other option, which may or may not help in your case, it to use
$scope.watchCollection("values",function(newValue,oldValue){
alert("Made it to the watch");
});
And see if anything in the values object changes.
Also, you could change isChecked to an object,
$scope.isChecked = { checked: false }
and $watch the object rather than the boolean itself.

angularJS directive not immediately honoring ng-show/ng-hide

I have the following section of HTML in an angularJS application. The <div/> tag for appointment-list is showing a listing of appointments. This directive is basically just a table.
<div ng-show="loading">Loading...</div>
<div ng-show="!loading && (appointments.length == 0)">No Appointments Found</div>
<div ng-hide="loading || (appointments.length == 0)">Test123</div>
<div ng-hide="loading || (appointments.length == 0)" appointment-list source="appointments" appointment-selected="appointmentSelected(appointment)"></div>
I then have the following in my controller. I am setting a loading variable while things are in-flight, and then I also filter the appointments on the page according to text in a text box.
$scope.$watch('selectedDate', function(newVal, oldVal) {
if (newVal) {
$scope.loading = true;
Appointment.query({year: newVal.getYear()+1900, month: newVal.getMonth()+1, day: newVal.getDate()}, function(data) {
$scope.allAppointments = data;
$scope.appointments = $scope.filterAppointments();
$scope.loading = false;
});
}
});
My issue is that the hiding of the div for my custom directive isn't happening properly. The table should be disappearing exactly along with the "Test123" string and its not. When I go from a selected date with the table populated to a date with nothing on there, the "Test123" will be replace with the loading (therefor its being hidden, and loading being shown) but the table remains until after the loading process is complete at which point the table will disappear
Can someone explain why the delay? Why is the directive not responding exactly like the div above it?
Edit
Here is a plnkr which shows the issue: http://plnkr.co/edit/khxQuaM6sxTx5RszvowX?p=preview
Basically click on the buttons at the top to load the two datasets. I have a timeout in there to simulate some of the think time on the server. Whenever you see "Loading..." the div for the appointmentList table should not be shown since ng-hide will evaluate to true because loading is true, yet is doesn't disappear.
You need to use $parent to access the model loading since the directive appointmentList creates an isolated scope. Make the following change to the last div containing the table and you will achieve the effect you want.
<div ng-hide="$parent.loading || (appointments.length == 0)" appointment-list source="appointments" ... ></div>
You don't need to use $parent to refer to appointments, since you pass this model to the directive. But there is no harm to add $parent like $parent.appointments.length == 0, since you have appointments defined anyway in the parent scope.
Btw, you should also set appointments to be empty in the watcher like this
if (newVal) {
$scope.loading = true;
$scope.appointments = []; //add this
to make the condition appointments.length == 0 useful.
Is $scope.appointments being set within the same tick as $scope.loading?
If $scope.filterAppointments is doing something asynchronous, you want to make sure $scope.loading is set to false at the end of that process.
In my case, angular didn't want to show/hide when applied to an ul > li element, in IE or Chrome. Changing it to a div works perfectly. I am on angular 1.2.14. Not sure if this is a bug or not, but it seems to be.
I had the same problem. I used ng-cloak, it keeps the browser from displaying the template while my application is loading.
Take a look at this:
http://weblog.west-wind.com/posts/2014/Jun/02/AngularJs-ngcloak-Problems-on-large-Pages

Resources