ng-include changing behavior of angular-ui datepicker - javascript

I was using ng-include on a few of my pages, however I had to stop using ng-include because it was breaking the angular-ui datepicker. I opened this Github bug.
I am wondering if anyone else was having issues with directives not functioning the same way when being used as part of a ng-include.
Is there a way to make the datepicker work as expected as part of a ng-include?
Here is a plunker showing how it is broken. http://plnkr.co/edit/AboEJGxAK3Uz76CfpaZ0?p=preview
Here is the html that works when on the view, but does not work when part of a ng include.
<div class="row">
<div class="col-md-2">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="yyyy/MM/dd" ng-model="something.dt2" is-open="secondCal"
min-date="minDate" name="secondCal" max-date="'2015-06-22'" datepicker-options="dateOptions"
date-disabled="disabled(date, mode)" ng-required="true" close-text="Close"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default" style="line-height: 1.2em" ng-click="open($event, 'secondCal')">
<i class="ss-icon ss-calendar"></i>
</button>
</span>
</p>
</div>
</div>
Here is the JS from the controller.
$scope.open = function ($event, elementOpened) {
$event.preventDefault();
$event.stopPropagation();
$scope[elementOpened] = !$scope[elementOpened];
};
And two ways I was doing ng-include
<div ng-include src="'dist/partials/myPartial.html'"></div>
<div ng-include="'dist/partials/myPartial.html'"></div>
Update
I found that this is because the ng-include directive creates a new scope for each include. This SO post creates a new directive that does the include without creating a new scope. However there seems there "should" be a way to fix it without using a different include.

The datepicker will be unable to open as soon as the is-open is changed by the datepicker directive itself (e.g. click outside to close the datepicker).
This is a common issue regarding the "Prototypal Inheritance" characteristic of scope.
For a complete detail, you could read this: Understanding-Scopes
You could easily solve this by not storing any primitive values directly into $scope, but some object and using dot-notation like this:
<input type="text" class="form-control"
datepicker-popup="yyyy/MM/dd" ng-model="something.dt2"
is-open="model.secondCal"
and in your controller:
app.controller('MainCtrl', function($scope) {
$scope.model = {};
$scope.open = function($event, elementOpened) {
$event.preventDefault();
$event.stopPropagation();
$scope.model[elementOpened] = !$scope.model[elementOpened];
};
});
Example Plunker: http://plnkr.co/edit/dJNIwSz2Uot3McmIMhd4?p=preview

I've created Plunker to debug it but it works fine with your code
http://plnkr.co/edit/nxYCiwRqdWMOkfZoRhGY?p=preview
<body ng-controller="MainCtrl">
<div ng-include="'partial.html'"></div>
</body>
after clarification and further tests i see that calendar with ng-include lose the scope when triggering the change not by scope method, the easy workaround would be as per this plunker
http://plnkr.co/edit/nxYCiwRqdWMOkfZoRhGY?p=preview
Don't remember which one of the angular team said it but if you don't have a dot in your model you are doing it wrong.
a little explanation why it works:
if you do
$scope.valueName = value
it will assign value to the current scope immediately
but if you do
$scope.something.valueName = value
what will happen is that angular will first locate $scope.something, if it doesn't exists on current scope it will look in parent (as long as you don't work in isolated scope)
then when it finds it it will assign value or return something like 'cant find valueName of undefined'
from the angularjs documentation:
https://docs.angularjs.org/api/ng/directive/ngInclude
This directive creates new scope.
This directive executes at priority level 400.

Related

$scope and $watch is not updating/working

My ng-model is value not updating in controller bonded with $scope. There is no syntax error in my code that I can assure. Only one controller bonded with html.
$watch is only running once when page is loaded.
Note that I have 220 around total watchers in my file and controller file is very big like 1500+ lines of code. To cross check I also bind one div with different controller and it is working as expected both $watch and $scope.
I have verified that my variable is not having duplicate name in entire project. I think angular gives no performance issue till 2000 watchers per page. But here that is not the case. Can someone please shed some light on this strange behavior. Any help would be appreciated.
Note: I am not posting code here as it was working fine when I started working on controller initially and on separate prototype also code works well. This behavior has been introduced recently with more and more code.
HTML file:
<div id="studentSearch" class="form-group__text row studentmargin">
<input id="search" type="text" class="studentSearch" tabindex="2"
ng-keyup="$event.which === 13 && !disableSearch ? clickSearchButton(): ''"
placeholder="Search by Name or Email"
ng-model="searchString">
<label for="search">
<button type="button" class="link" tabindex="3"
ng-click="clickSearchButton()" ng-if="!searchResultFlag"
ng-disabled="disableSearch">
<span class="icon-search"></span>
</button>
</label>
<button type="button" class="link" ng-click="clearStudentsSearch()"
ng-if="searchResultFlag" tabindex="4">
<span class="icon-close"></span>
</button>
Controller:
angular.module('app.pages.course.details').controller("CourseDetailController", ['$scope', function($scope){
$scope.disableSearch = true;
$scope.$watch('searchString', function(oldValue, newValue){
if(newValue.length >= 3) {
$scope.disableSearch = false;
}
});
$scope.clickSearchButton = function() { /* Search logic */ }
$scope.clearStudentsSearch = function() {
$scope.searchString = "";
} }]);
So the logic is search icon will only get enabled once the searchString is greater than or equal to 3 letters. On clearing search button it gets cleared from UI and if we print the scope it has that value, Because of that on ENTER press, search work again.
Could you try using parent with the model ng-model="parent.name".

Disabling submit button based on fields added with ng-bind-html

JSFiddle here: http://jsfiddle.net/c6tzj6Lf/4/
I am dynamically creating forms and buttons and want to disable the buttons if the required form inputs are not completed.
HTML:
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
JavaScript:
angular.module('choicesApp', ['ngSanitize'])
.controller('ChoicesCtrl', ['$scope', '$sce', function($scope, $sce) {
$scope.custom = "Required Input: <input required type='text'>";
$scope.trustCustom = function() {
return $sce.trustAsHtml($scope.custom);
};
$scope.buttons = [
{text:'Submit 1'},
{text:'Submit 2'}];
}]);
choicesForm.$invalid is false and does not change when entering text into the input field.
Solution:
I ended up using the angular-bind-html-compile directive from here: https://github.com/incuna/angular-bind-html-compile
Here is the relevant bit of working code:
<ng-form name="choicesForm">
<div ng-if="choices" bind-html-compile="choices"></div>
<button ng-click="submitForm()" ng-disabled="choicesForm.$invalid">
Submit
</button>
</ng-form>
And choices might be a snippit of HTML like this:
<div><strong>What is your sex?</strong></div>
<div>
<input type="radio" name="gender" ng-model="gender" value="female" required>
<label for="female"> Female</label><br>
<input type="radio" name="gender" ng-model="gender" value="male" required>
<label for="male"> Male</label>
</div>
The main problem is that ngBindHtml doesn't compile the html - it inserts the html as it is. You can even inspect the dynamic input and see that it doesn't have the ngModel's CSS classes (ng-pristine, ng-untouched, etc) which is a major red flag.
In your case, the form simply doesn't know that you've added another input or anything has changed for that matter. Its state ($pristine, $valid, etc) isn't determined by its HTML but by the registered NgModelControllers. These controllers are added automatically when an ngModel is linked.
For example this <input required type='text'> won't affect the form's validity, even if it's required, since it doesn't have ngModel assigned to it.
But this <div ng-model="myDiv" required></div> will affect it since it's required and has ngModel assigned to it.
The ngDisabled directive on your buttons works as expected since it depends on the form's $invalid property.
See this fiddle which showcases how ngModel registers its controller. Note that the html containing the dynamic input gets compiled after 750ms just to show how NgModelControllers can be added after FormController has been instantiated.
There are a few solutions in your case:
use a custom directive to bind and compile html - like this one
use ngInclude which does compile the html
use $compile to compile the newly added HTML but this is a bit tricky as you won't know exactly when to perform this action
This is an answer yet imcomplete because i cannot do the code at the moment.
I think your html will be included, not compiled. So the inputs are not bind to angular and are not part of the angular form object.
The only way i see is to use a directive that will compile the passed html and add it to your form. This may be quite tricky though, if you want to go on this way i suggest to edit your question to ask for the said directive.
However i'm not really familiar with $compile so i don't know if it'll work to just add $compile around $sce.trustAsHtml()
You can write a method as ng-disabled does not work with booleans, it works with 'checked' string instead:
So on your controller place a method :
$scope.buttonDisabled = function(invalid){
return invalid ? "checked" : "";
};
And on your view use it on angular expression :
<button ng-repeat="button in buttons" ng-disabled="buttonDisabled(choicesForm.$invalid)">
Here is a working fiddle
Working DEMO
This is the solution you are looking for. You need a custom directive. In my example I have used a directive named compile-template and incorporated it in div element.
<div ng-bind-html="trustCustom()" compile-template></div>
Directive Code:
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I found the directive in this fiddle.
I believe what is really happening though due to jsfiddle I'm unable to dissect the actual scopes being created here.
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
The first div is your top level scope, your form is the first child scope. Adding the div using a function creates the dynamically added input field as a child of the first child, a grandchild of the top level scope. Therefore your form is not aware of the elements you're adding dynamically causing only the static field to be required for valid form entry.
A better solution would be to use ng-inclue for additional form fields or if your form isn't to large then simply put them on the page or template you're using.

How to include a filter in a custom directive

I'm new to Angular JS and am having trouble with custom directives. I've tried to copy a tutorial and I can't get it working using my own code.
Here's the relevant part of my HTML:
<div ng-controller="calcController" class="container">
<div class="form-group">
<label for="balInput">Balance:</label>
<input id="balInput" type="text" class="form-control" ng-model="balance" ng-change="updateAnnualInt(balance)" placeholder="Please enter your balance here...">
</div>
<p>{{'At a '+(interestRate)+'% interest rate you would save...'}}</p>
<p style="text-indent: 30px;" ng-repeat="interest in interests">{{'per '+interest.time+': '}}{{(interest.factor*annualInterest*0.01) | currency:'£'}}</p>
To start with, I'm just trying to turn the last paragraph into a custom directive. Here's my attempt:
app.directive('interest-amount',function(){
var directive={};
directive.restrict='E';
directive.template="<p style='text-indent: 30px;'>'per '+{{interest.time}}+': '{{(interest.factor*annualInterest*0.01) | currency:'£'}}</p>";
directive.compile=function(element,attributes){
var linkFunction=function($scope, element,attributes){
element.html("<p style='text-indent: 30px;'>per "+$scope.interest.time+": "+($scope.interest.factor*$scope.annualInterest*0.01)+"</p>");
}
return linkFunction;
}
return directive;
})
This isn't giving the HTML template I expect when I insert it as follows:
<interest-amount ng-repeat="interest in interests"></interest-amount>
My first question is why is this not working?
I am also confused as to how to include the currency filter in the linkFunction. What is the syntax for encoding this in Javascript rather than Angular-powered HTML?
Thanks
app.directive('interestAmount',function($filter){ // camel case, as #isha aggarwal noted
$filter('filterName')('argument');
});
Answer to your first question, the directive should be declared in camel case. So, declare the directive like this :
app.directive('interestAmount',function(){
And call it the way you are doing currently.
<interest-amount ng-repeat="interest in interests"></interest-amount>
This should take you inside the directive code.
For your second question, you should use a directive controller. Just create a controller for this directive, inject $filter in that controller and call function of that controller from link function of your directive.
Refer this : AngularJs - Use custom filter inside directive controller

AngularJS ng-change on select with custom directive

In my application I have a custom directive with select tag.
<div class="row pv10" ng-if="item.visible">
<div class="col-xs-4 text-danger"><span ng-if="item.label_visible"> {{item.label}}</span></div>
<div class="col-xs-8">
<div class="row">
<div class="col-xs-1">
<select ng-options="oneItem for oneItem in item.options" ng-model="selectedItem" ng-change="changeEvent()">
{{selectedItem | json}}
</select>
</div>
</div>
</div>
</div>
I would like to perform an action on every change event. I thought of using ng-change, which btw. worked just fine with radioBtn/checkBox. I tried two approaches here.
Firstly I tried to pass "changeEvent" function to the directive's isolated scope.
<custom-select item='item' change-event="change()" ng-if="item.component == 'select'" />
changeEvent() function exists in current controller (attached to the view holding custom-select)
scope: {
item: "=",
changeEvent: "&"
}
Sadly expression does not evaluate on change.
Secondly I tried to attach the function directly to directive
link: function ($scope, elems, attrs) {
$scope.changeEvent = function () {
alert("Change Event!");
}
}
but it didn't work either.
I read somewhere that ng-change even used within a custom directive operates on a controllers scope, not isolated scope of directive. However am not quite sure about that.
I'd really appreciate your help.
EDIT!
This is a JSFiddle to the directive I use. If I should provide more details, just let me know, this is the first time I've ever used jsfiddle.
This is how the directive is invoked:
<div class="row" ng-repeat="item in jsonData">
<div class="col-xs-12">
<cbp-select item='item' change="change()" ng-if="item.component == 'select'" />
</div>
</div>
https://jsfiddle.net/60nsfg0L/
EDIT2
When I have uploaded my solution to our local server it started to work properly.(Yes, I cleaned browser's cash, and my locals wamp/iss cache too).
But now I have a different problem.
I have a form built from different directives(like the one above), all have ng-click/change/blur. Everything seems to work fine until I change one of the options in my "select" directive. After selecting one of the values, ng-change expression is triggered (for not its just a simple alert) and from that moment onwards am not able to focus on any other elements. Weirdly enough, if I cause ng-change to trigger on any other element be it radiobtn or checkbox this problem does not occur.

Angular Databinding doesnt Work

I have got a form where user will enter a name and click Next. What I want to do is that, when the user clicks Next, I want to alert the updated value of $scope.name inside toChat function, but initial value is alerted, which is James. How can I access the updated value inside angular function? I have some serious problems understanding sharing variables within AngularJs.
js
.controller('NewcontactCtrl', function($scope,$rootScope, $ionicHistory,$window) {
$scope.name='James';
$scope.myGoBack = function() {
$ionicHistory.goBack();
};
$scope.toChat = function() {
alert($scope.name);
};
})
html
<ion-view view-title="New contact">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-buttons side="primary">
<button class="button" ng-click="myGoBack()">
Cancel
</button>
</ion-nav-buttons>
<ion-nav-buttons side="secondary">
<button class="button" ng-click="toChat()" >
Next
</button>
</ion-nav-buttons>
<ion-content scroll="false" has-header="false" padding="true" >
<div class="list">
<label class="item item-input">
<input type="text" placeholder="Name" ng-model="name" />
</label>
<label class="item item-input">
<textarea placeholder="Notes" ng-model="notes" rows="10"></textarea>
</label>
</div>
</ion-content>
</ion-view>
Can anyone help ?
Please see: https://github.com/angular/angular.js/wiki/Understanding-Scopes
The most relevant part in the above:
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view and ng-include all create new child scopes, so the problem often shows up when these directives are involved. ...
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.
Having a '.' in your models will ensure that prototypal inheritance is in play. So, use
<input type="text" ng-model="someObj.prop1"> rather than
<input type="text" ng-model="prop1">.
I believe you have a directive in there somewhere (probably ion-content) that is creating a new scope where your input field is, separated from the scope where your Next button is.
To simulate this, I've used ng-repeat in the below snippet (but I'm repeating only once), which causes the same behaviour of splitting the scopes. If you were to use your controller code unmodified with this html, you'd reproduce the issue you're experiencing.
The solution around this is to 'use a dot (.)' when binding. Notice that I've wrapped the name within an object called 'data' on the scope, so now you refer to this as data.name instead of just name.
(function() {
'use strict';
angular.module('myApp', [])
.controller('NewcontactCtrl', function($scope, $window) {
$scope.repeaterTest = [1];
$scope.data = {name: 'James'};
$scope.toChat = function() {
$window.alert($scope.data.name);
};
});
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="NewcontactCtrl">
<label ng-repeat="test in repeaterTest">
<input type="text" placeholder="Name" ng-model="data.name" />
</label>
<button class="button" ng-click="toChat()">
Next
</button>
</div>
</div>
I think you need to alert something similar to...
alert($scope.name)
Addition to #Paul Fitzgerald, points, ensure that ng-controller="NewcontactCtrl" is included at the top scope in HTML DOM.
try adding a service in order to share data within scopes
.controller('NewcontactCtrl', function($scope,$rootScope,sharedData $ionicHistory,$window) {
$scope.name=sharedData.Name ;
$scope.myGoBack = function() {
$ionicHistory.goBack();
};
$scope.toChat = function() {
alert(sharedData.Name);
};
});
app.factory('sharedData', [function () {
var self = {};
self.Name = "James";
return self;
}]);

Categories

Resources