Angular directive inserts wrong DOM node - javascript

I have an app with many forms. Each field has several HTML elements, so I thought I could extract some directives (one per type of field) to keep my forms tidy.
I've created a sample app to demonstrate the problem, but I'm getting inconsistent behavior. In the sample app, a <link /> element replaces the <input />. In my real app, <input /> just gets removed from the DOM completely. I feel like this should be easy; why doesn't it work?

To answer your stated question, it's because you told it to, with ng-transclude. That replaces the contents of the tag with the original element, which I don't think you wanted; you probably wanted the original contents to be transcluded as the label instead.
This is probably what you're looking for:
<div class="form-group" >
<label for="{{htmlId}}" ng-transclude></label>
<input id="{{htmlId}}" class="form-control" type="text" ng-model="model" />
<span ng-repeat="error in errors">{{error}}</span>
</div>
I've moved the tranclusion into the label. While this works, I would also recommend the style of actually passing a label attribute, rather than transclude it, just for the sake of having a consistent API and simpler code; it's functionally equivalent, though, so don't let me bully you.
Also, you've got a few errors in your .js as well. First, you want to use = in your scope instead of &
scope: {
model: '=',
errors: '='
},
& is used to pass methods, while = is used for objects (this is a simplification). Since your model and errors are objects, you'll want to use = instead.
Finally, in your example, your html template and your directive's template don't have the same name... you've got an extra 's' in your .js, but that's probably just in the plunker and not your real app.

Related

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.

Angular ui-router 'ui-view' vs 'data-ui-view' [duplicate]

I have begun to learn about AngularJS and am confused about what the differences are between the ng-app and data-ng-app directives.
Most of these answers are simply saying makes template valid HTML, or HTML Validator Compliant, without explaining what THOSE terms mean, either.
I do not know for sure, but I'm guessing that these terms apply to HTML validation programs that scan your code for standards compliance - kind of like lint. They do not recognize ng-app as a valid attribute. They expect non default HTML attributes to be prefaced with
data-attribute_name_here.
So, the creators of AngularJS have created alternate names for their directives that include the data- in front of them so that HTML validator programs will "like" them.
None in terms of the runtime behavior, those are just different styles of naming directives as described here: http://docs.angularjs.org/guide/directive
Directives have camel cased names such as ngBind. The directive can be
invoked by translating the camel case name into snake case with these
special characters :, -, or _. Optionally the directive can be
prefixed with x-, or data- to make it HTML validator compliant. Here
is a list of some of the possible directive names: ng:bind, ng-bind,
ng_bind, x-ng-bind and data-ng-bind.
As you can see from reading this the data- can be used to make your HTML pass HTML validator tests/
You can declare the angular namespace <html xmlns:ng="http://angularjs.org" ng-app>
In modern browsers there is no difference, but in older IEs, they won't work unless you declare an XML namespace defining it.
There is also a validation difference in that ng-app is not valid XHTML, and will cause your webpage to fail HTML validations. Angular allows you to prefix its directives with data- or x- to allow it to validate.
You can use data-ng-, instead of ng-, if you want to make your page HTML valid.
This will throw an error
<div ng-app="">
<p>Input something in the input box:</p>
<p>Name: <input type="text" ng-model="name"></p>
<p ng-bind="name"></p>
</div>
This won't throw an error
<div data-ng-app="scope" data-ng-init="name='test'">
<p>Input something in the input box:</p>
<p>Name: <input type="text" data-ng-model="name"></p>
<p data-ng-bind="name"></p>
</div>
The basic difference between these two terms is that data-ng-app validates the HTML while the latter don't.Functionality remains the same.
For more reference you can try w3Validator.
Absolutely there is no difference between the two, except that certain HTML5 validators will throw an error on a property like ng-app, but they don't throw an error for anything prefixed with data-, like data-ng-app. So using data- prefix with our angular directives is good.
Even you can make use angular directives in below mentioned ways
ng-bind, ng:bind, ng_bind, data-ng-bind, x-ng-bind

ng-click doesn't take parameters from the DOM

I have the following code:
<input id="id">
<button data-action="bea" ng-click="Create($('#id1')[0].value);" class="btn">Insert ID</button>
<button data-action="bea" ng-click="Create($('#id2')[0].value);" class="btn">Insert ID</button>
In the JS I have:
$scope.Create = function (id){
if (id === undefined) {
$scope.data = "You must specify an id";
} else {
$scope.data = data;
console.log(data);
});
}
};
When the call gets into the Create function the value of the id is undefined.
If I add the following line at the beginging of the Create function everything works ok:
id = $('#id')[0].value;
If I send a constant value it works:
<button data-action="bea" ng-click="Create('SomeID');" class="btn">Insert ID</button>
Why is this happening and how can I do that without putting the line of value into the method?
Thanks
This is just an extension of comments and other answers, You could achieve this in many ways using angular, one simple example could be:-
<!-- Add a controller -->
<div ng-controller="MainCtrl">
<!-- Give a model binding to your text input -->
<input ng-model="userEntry" type="text"/>
<!-- ng-click pass which ever argument you need to pass, provided it is an expression that can be evaluated against the scope or any constants -->
<button data-action="bea" ng-click="Create(userEntry);" class="btn">Insert ID</button>
<!-- Some simple data binding using interpolation -->
{{data}}
<!-- Just for demo on repeater on a list of items on the scope -->
<div ng-repeat="item in items track by $index">{{item}}</div>
</div>
Example Demo
My 2 cents on the lines of what were originally trying to do:-
Use angular bindings instead of accessing DOM directly for getting the data, it really helps you deal with just the data without worrying about how to access or render it in DOM. If you think you need to access DOM for implementing business logic re-think on the design, if you really need to do it, do it in a directive. Angular is very opinionated on the design and when where you do DOM access.
ng-model
ng-binding
controller
all about ngmodel controller
This is not the way you should do in AngularJS. You should really think in Angular if you want to use AngularJS. Refer this post ("Thinking in AngularJS" if I have a jQuery background?)
All DOM manipulation should be done in Directive. Refer this page that I found really clear.
(http://ng-learn.org/2014/01/Dom-Manipulations/)
My guess is that $ is not bound to the jQuery function when the ng-click value is evaluated, because it is not exposed in the Angular scope.
Solutions to adress this:
expose the jQuery function in scope somewhere, e.g $scope.$ = $; in a controller.
make the Create function parameterless as you suggested, with a var id = $('#id')[0].value; at the beginning
my favorite : avoid using jQuery. If you put some data in the #id element, there's probably a more natural and AngularJS-idiomatic way of retrieving it than querying the DOM (e.g an Angular service).
In particular, if the element you're targeting is an <input> element, then use the ngModel directive to link the value to a $scopeproperty that will be accessible in the controller :
<input ng-model="inputData"/>
The JavaScript you are trying to pass as a parameter of the create function is not available in the scope of the Create function.
Try to target the element a different way.
Does that help?

AngularJS Model Location

I'm writing a simple AngularJS example that doesn't have a controller. It just uses the default global controller/model.
<!DOCTYPE html>
<html lang="en" ng-app=>
<body>
<input type="text" ng-model="firstName" placeholder="first name">
<input type="text" ng-model="lastName" placeholder="last name">
<br />
<h2 ng-style=style>Welcome {{firstName + ' ' + lastName}}</h2>
<button ng-disabled="!(firstName.length && lastName.length)">Sign Up</button>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
</body>
</html>
Where are the firstName and lastName values stored? This form works as expected so the two-way binding is working. I've tried looking through the window object and I didn't see anything in there that might be the global Angular controller. If I wanted to submit these values, where would the "handle" to them be in this example?
UPDATE
As I said, this is working as evidenced by this jsFiddle. The name is computed correctly and the button is only enabled once both firstName and lastName have values. I also tried using Batarang and it did not help. It told me that there wasn't a scope defined yet the application still functions.
Try installing Batarang and inspecting for yourself, though I believe those two values would be applied to $rootScope
You can view your scope using ng-inspector. It may not work in js-fiddle, because they do things with iframes that hide the global variables the inspector depends on.
I tried it on a test application with an unnamed module, as in your example, and it discovers the scope as $rootScope 002
Because of this I theorized, and have found through experiment, that the scope of an application without a declared module name (and probably with one, too) is attached to whatever DOM node the ng-app directive is on.
So, if you put ng-app on the html tag, this would get you your scope:
angular.element(
document.getElementsByTagName('html')[0]
).scope();
Using JSFiddle will complicate this testing, since it does iFrame stuff and has multiple html and body tags. Try it in an isolated environment and it's pretty straight-forward.
In console, select the result frame, and type:
angular.element($('body')).scope().firstName
You should see the firstName value. With regards to "If I wanted to submit these values" part, I believe you want to do an ajax call with these values. The best place to do that is by attaching a controller, where you will have direct access to these variables, and then you can use $http service to post to a server.

When should prefer angular directive over html native tag?

I'm a big fan of angularjs, I started lately to use it in all of my 'coding for fun' projects.
I have a big curiosity:
I have a two inputs, one disabled by a ng-disabled directive and the other disabled with an html tag (A better illustration in this link):
//...
<input type="text" disabled value="This is an html input text disabled" />
<input type="text" ng-disabled="true" value="disabled with angular js directive" />
//...
Using the browser ability I can right click on the input and remove the disabled and ng-disabled tags but only the one with the disabled tag would be editable, the other one will still be tracked by angular even when ng-disabled directives has been removed.
So, When and Why should I prefer using ng directives over native html tags? Which could be the impact of letting angular track all these actions? is it really worth to use it everywhere?
Use the native html 'disabled' if the element should always be disabled. (static, for example if you want to provide an input with text and never let the user change it)
Use angular if it should change based on variables value in the scope.
For example, say a button should change the state of an input.
<input type="button" ng-click="inpDisabled = true" >Disable Input</input>
<input type="text" ng-disabled="inpDisabled" />
live example
No harm will come if you still use ng-disabled="true" but it's redundant.
If you want to make directive static, you shoud use native html
<your-tag disable><your-tag>
against
<your-tag ng-disabled="true"><your-tag>
But AngularJS does not work this way, you shoud initialize your application and controller, then pass a variable as parameter to your directive:
JS:
$scope.isDisabled = true;
HTML:
<your-tag ng-disabled="isDisabled"><your-tag>
You shoud read more tutorials to make things clear

Categories

Resources