angular view not reflecting controller - angularjs

This is an angular 1 app.
I have a directive that looks like this:
function addLiquidity() {
return {
restrict: 'E',
scope: {
expanded: '='
},
templateUrl: 'liquidity.html',
controller: 'addLiquidityCtrl'
};
}
And the html that looks like this:
<add-excess-liquidity expanded="addExpanded"></add-excess-liquidity>
In another html I have this:
expanded: {expanded}
This is false by default thanks to the controller that in its relevant part contains this:
$scope.addExpanded = false;
Now, when clicking a button to show form for adding new stuff, "addExpanded" is set to true.
When then the button to actually save the form is clicked "addExpanded" is set to false again:
$scope.addExpanded = false;
I can see in the controller that that function is called and that addExpanded behaves correctly in the controller.
Actually, it behaves correctly in the view too. So the value in html:
expanded: {expanded}
reflects the value in the controller.
However, as soon as I switch to another tab (this app is using nav-tabs) and come back to the tab I was before, then the value in html is not reflecting the controller anymore(which is still doing its job like before, going through the same steps as before, included setting the "expanded" to false).
I tried forcing view update with both timeout and "apply" but didn't help. Also, it seems somwehere else an apply is fired at the end of every function.
I really don't understand what's wrong here. Where should I look? Something happen (I guess) when leaving the tab the first time. But so many things happen so it is not easy to debug. Any clue anybody?
EDIT
I added a watch like this:
$scope.$watch( 'addExpanded',
function(newValue, oldValue){
console.log('addExpanded Changed');
console.log(newValue);
console.log(oldValue);
}
);
First time I go to the tab then it will print to console. I then leave the tab, then come back to it. And this time, when clicking on button, nothing is printed to the console.
Roughly, what happens when I change tab is that the div containing the tab-content is set to "display:none/block" (I guess) as it is pure css that is showing tab content upon selection.
In the main controller a few things are done depending on which tab was selected, but I cannot see things that would "destroy" the connection between the GUI and the controller/directive.
Also, each time I go to that tab, I see that the javascript connected to the directive is initiating. And the variable "expanded" is reset to false.
The issue is that, for some reason, the connection between javascript and the GUI is in some way "lost" when visiting the tab the second time (and for all the future until I refresh the page).

Related

How can I prevent ng-model from being changed in select based on result of function?

I have a select drop down menu populated with ng-options. I want to create a pop-up that will require the user to confirm they want to change the selected option before continuing.
So far, I have tried using an ng-change, but that doesn't work because you can't prevent the change (for the purposes of my app, I cannot simply put the value back to what it was before the change in an ng-change function). I have tried ng-click, but this fires when the user clicks on the select box, whereas I want this to fire when the user actually selects one of the options from the select box for UI purposes. I have investigated ng-model-options, but my problem is I cannot seem to get an event to fire when an option is selected.
Code below demonstrating what this select looks like:
<select ng-model='uiConfig.selectedInteractionType' ng-options='interactionType for interactionType in uiConfig.interactionTypes'></select>
I had this exact same requirement, albeit for a different element. I had a button that was really an anchor tag where I passed in some values, including a URL when the user clicked the button. I then needed to open an $mddialog modal and let the user cancel or click continue to send them to a redirected site.
What you need is a directive here. Put the value(s) in $scope. I used attributes for this:
restrict: 'A',
and then in my link function I used
attributes.$observe
to pull in my values.
Then use something like this in your directive function:
element.on('click' function ($event) {
$event.preventDefault(); //To stop the event flow.
$mdDialog.show({
templateUrl: "path to your modal html document",
controller: myController,
clickOutsideToClose: true,
locals:{
href: $scope.href,
myValue: $scope.myValue
}
Using $locals allows you to pass in the values to the directive.

ng-repeat not updating when coming back from a modal call

I have the following code in my html:
<li class="col-md-4 my-show-hide-animation" ng-hide="sr.hidden" ng-repeat="sr in gmCtrl.gmSRs" ...
The above html page has a button that makes a modal call.
I have another html/js page that can be accessed modally and non-modally.
I have this after coming back from the modal call:
if (!$rootScope.$$listenerCount['sr.CreateDone']) {
$rootScope.$on('sr.CreateDone', function(event, data) {
ctrl.gmSRs.push(data.sr);
$uibModalStack.dismissAll();
});
}
Now... this actually works over and over again. However, the moment I go into the target html/js in a non-modal way (which also works), the modal way no longer works. In the debugger, I can see the data.sr object being added to the array, and I can see the array being changed, but it's not reflected in the view. Any ideas??? I assume that either I have some sort of scoping issue or its some weird angularjs bug.
Here's the call:
ctrl.modalInstance = $uibModal.open({templateUrl: "standardresponse/standardresponse.html",
controller: "StandardresponseController",
controllerAs: "standardresponseCtrl",
size: "lg",
keyboard: true,
backdrop: 'static',
windowClass: "app-modal-window",
});
I also added a line of code that shows I only have the 1 listener running.
If the array is changing, but it isn't showing up in the view, this means that the digest cycle isn't properly running on the $scope. You could fix this manually by calling $scope.apply() but you might want to reconsider your design.

Run code when link is clicked, even if I stay on the same page

I have a collapsible sidebar navigation, and any time users click a link on it, I have the sidebar hide automatically. I achieve this in the run method by using the following code:
app.run(function($rootScope) {
$rootScope.$on('$routeChangeSuccess', function () {
$rootScope.closeNavigation();
})
});
Where app is my Angularjs module.
The problem is that it gets a little unintuitive to use, when you click a link in the navigation for the page you're already on, and then nothing happens. What I would like is to have the sidebar close anyway, so that the users still get focus on the content, even if it's the same content.
But Angularjs doesn't execute the $routeChangeSucess event, if there is no route change happening. So what can I use instead?
You simply need to use ng-show = isPanelVisible and ng-click = closePanel() on the html sidepanel tag (e.g. a <div>).
Then, define $scope.closePanel() in the controller associated to the actual view. For instance
$scope.closePanel = function() {
$scope.isPanelVisible = false;
}
See an example here. Code is here. Note that it doesn't use ng-show, but the behaviour is the same.

Dialog preventing DOM from redrawing

I have a page built in angular. The page has a set of data displayed that's paginated. I have the page searchable with a search box. The pagination is build by hand due to some custom rules about what comprises each page. But building the pagination is set in a buildPages() method, and from there it's a simple call to build the pages that directly updates the scope the page draws from.
So typically a search works like so:
Type in search.
Watcher catches the change on search.
Watcher sets search var.
Watcher calls buildPages();
buildPages rebuilds pagination and updates scope.
Scope is update, pagination and displayed data is redrawn.
Now all of this works just fine. But I needed to add in a dialog to ask the user if they want to change the search or leave it, and this dialog only appears sometimes based on certain criteria.
My problem is that if I open a dialog and call buildPages() from a button click in the dialog, it properly updates the scope, but it doesn't redraw the page.
So, for example, you have 50 pages and you're displaying page 1 of 50. You then type in a search. The search filters out 25 of those 50 pages, leaving you with 25 pages left. The page you were initially on is also filtered out. Without the dialog, typing in the search will result in you appearing on page 1/25 on the first filtered page. However, triggering the search from the dialog will still show you on page 1 of the unfiltered page, at page 1/50. Clicking next on the pagination control will properly take you to the second filtered page 2/25, and clicking back from there will properly show you on page 1/25 of the filtered pages.
Even doing something as simple as triggering a hover will cause the page to properly redraw correctly.
What's going on with this? Why is the dialog preventing the redraw?
Here's the code I'm using in my watcher (slightly simplified).
$scope.$watch('searchText', function(newVal, oldVal) {
currentSearch = newVal;
if(newVal !== oldVal){
if(displayFlag){
j$( "#dialog-confirm").dialog({
resizable: false,
width:450,
height:240,
modal: true,
buttons: {
"Search": function() {
j$( this ).dialog( "close" );
buildPages();
},
Cancel: function() {
j$( this ).dialog( "close" );
return false;
}
}});
}
else
{
buildPages();
}
}
});
I've tried including a return of either true or false in the search click. I've also tried switching the order of the dialog close and buildPages in the search click.
I am not sure if j$() is an angular library. It appears that it may not be.
Remember that Angular does not know about any type of events or changes except its own, therefore, if you are performing an action with jQuery or another library like that .dialog, after you performe the call, you may need to call $scope.$digest(); or $scope.$apply() to trigger the angular digest cycle.
scope.$digest() will only fire watchers on current scope, scope.$apply (recommended) will evaluate passed function and run $rootScope.$digest().
buttons: {
"Search":
function() {
j$( this ).dialog( "close" );
buildPages();
$scope.$apply(); //After function is executed a cycle will occur.
},
Cancel: function() {
j$( this ).dialog( "close" );
return false;
}
}
I believe in your situation that the view may not being updated because angular isn't being told to perform a digest cycle.

Webshims - Show invalid form fields on initial load

(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});

Resources