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

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

Related

Angular scope variable update not reflected in UI

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

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.

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>

JQueryMobile select element not working within AngularJs ng-switch directive

I have this HTML:
<div ng-switch on="MyViewType">
<div ng-switch-when="B">
<select id="selectCatagoryFood2" data-role="listview" data-native-menu="true" ng-options="foodCatagory as foodCatagory.Description for foodCatagory in foodCatagories" ng-model="foodCatagory" ng-change="changeFoodCatagory(foodCatagory)">
</select>
</div>
</div>
The select appears empty and do nothing if I select another value.
Example (the first "select" works correctly, but the second doesn’t show selected value): http://plnkr.co/edit/yrXa70?p=preview
The problem is similar to this other post, but this solution doesn’t work (the “Refresh” launches an exception).
Select Value not Init in AngularJs and JQueryMobile
I think the problem is that this directive (ng-switch) removes and adds elements from the DOM and JQuery Mobile loses some objects created in the initializations of the elements.
Code from your example:
var unbindWatcher = scope.$watch(attrs.ngModel, function(newValue, oldValue) {
if (newValue && oldValue === undefined) {
element.selectmenu('refresh');
unbindWatcher();
}
});
The element.selectmenu('refresh'); will be called when ngModel (which is the selected option) gets a new value and the old value is undefined.
This will work for the first select because it will:
First get rendered and initialized as a select menu by jQuery Mobile
Later get new data and be refreshed
However, in the second case, the HTML inside <div ng-switch-when="B"> will not get rendered until MyViewType is actually B, which is at the same time the data for the select is ready.
This means two things:
When the code in the $watch gets executed for this element, the ngModel will already be available, both newValue and oldValue will be Object {Description: "1"}, and the if statement will not be executed.
If you tried to call element.selectmenu('refresh'); it would throw
an error, as the element has not yet been initialized as a
select menu by jQuery Mobile.
If you want to populate the select menu with data from the $scope and the data is available at time of initialization, I would:
Tell jQuery Mobile not to automatically turn the select into a select menu by setting data-role="none"
Turn in into a select menu manually by calling element.selectmenu(); from the directive
Otherwise you would need a good way to know when the element has been rendered and initialized by jQuery Mobile, then call refresh.
Modified directive for both cases:
var unbindInitializationWatch = scope.$watch(attrs.ngModel, function(newValue, oldValue) {
if (newValue && oldValue === undefined) {
element.selectmenu('refresh');
unbindInitializationWatch();
} else if (newValue && oldValue) {
element.selectmenu();
unbindInitializationWatch();
}
});
Working example: http://plnkr.co/edit/9jR7ko3j8ugUr1suB8ws?p=preview
Note that ngSwitch creates a new scope, and in order for the both select menus to share selected option I have in the example moved the model for the selected option into an object (More on this here if not familiar with how it works).
Also added an additional $watch to sync the both select menus.

Linking MVC In AngularJS

I have a basic application in AngularJS. The model contains a number of items and associated tags of those items. What I'm trying to achieve is the ability to filter the items displayed so that only those with one or more active tags are displayed, however I'm not having a lot of luck with figuring out how to manipulate the model from the view.
The JS is available at http://jsfiddle.net/Qxbka/2 . This contains the state I have managed to reach so far, but I have two problems. First off, the directive attempts to call a method toggleTag() in the controller:
template: "<button class='btn' ng-repeat='datum in data' ng-click='toggleTag(datum.id)'>{{datum.name}}</button>"
but the method is not called. Second, I'm not sure how to alter the output section's ng-repeat so that it only shows items with one or more active tags.
Any pointers on what I'm doing wrong and how to get this working would be much appreciated.
Update
I updated the method in the directive to pass the data items directly, i.e.
template: "<button class='btn' ng-repeat='datum in data' ng-click='toggle(data, datum.id)'>{{datum.name}}</button>"
and also created a toggle() method in the directive. By doing this I can manipulate data and it is reflected in the state HTML, however I would appreciate any feedback as to if this is the correct way to do this (it doesn't feel quite right to me).
Still stuck on how to re-evaluate the output when a tag's value is updated.
You can use a filter (docs) on the ng-repeat:
<li ng-repeat="item in items | filter:tagfilter">...</li>
The argument to the filter expression can be many things, including a function on the scope that will get called once for each element in the array. If it returns true, the element will show up, if it returns false, it won't.
One way you could do this is to set up a selectedTags array on your scope, which you populate by watching the tags array:
$scope.$watch('tags', function() {
$scope.selectedTags = $scope.tags.reduce(function(selected, tag) {
if (tag._active) selected.push(tag.name);
return selected;
}, []);
}, true);
The extra true in there at the end makes angular compare the elements by equality vs reference (which we want, because we need it to watch the _active attribute on each tag.
Next you can set up a filter function:
$scope.tagfilter = function(item) {
// If no tags are selected, show all the items.
if ($scope.selectedTags.length === 0) return true;
return intersects($scope.selectedTags, item.tags);
}
With a quick and dirty helper function intersects that returns the intersection of two arrays:
function intersects(a, b) {
var i = 0, len = a.length, inboth = [];
for (i; i < len; i++) {
if (b.indexOf(a[i]) !== -1) inboth.push(a[i]);
}
return inboth.length > 0;
}
I forked your fiddle here to show this in action.
One small issue with the way you've gone about this is items have an array of tag "names" and not ids. So this example just works with arrays of tag names (I had to edit some of the initial data to make it consistent).

Resources