AngularJS: How to implement a directive that outputs its HTML? - angularjs

I'd like to create an example directive that appends its inner HTML to itself. So, this:
<div example>
<label for="name">Name:</label>
<input id="name" type="text" ng-model="name">
</div>
should become this:
When inside a directive, the element already has things line class="ng-scope ng-pristine ng-valid" which shouldn't be in the outputted HTML.
How would implement such a directive?
My attempt is here

You probably don't need to transclude or use scope here, just use the compile function to grab the inner html and append it to the node:
.directive('example', function() {
return {
compile: function(ele) {
var innerHtml = ele.html();
ele.append(document.createTextNode(innerHtml));
}
};
});
Demo

Related

How use multiply ng-models in AngularJS

I have textarea with ng-model 'wordset' and ng-change="onChange()"
<div>
<textarea ng-model="wordset" ng-change="onChange()"
class="form-control app-word-set"
placeholder="Enter Word Set" rows="4">
</textarea>
</div>
I have button which added new textarea in this div. I needed that already added textarea includes the same on change method that my first textarea i have. But it should use ng-model...
I want to use on method in my angularJS controller that gets values from every textarea by foreach like this:
$scope.wordSetTextarea = angular.element(document.getElementsByClassName('app-word-set'));
$scope.onChange = function() {
angular.forEach($scope.wordSetTextarea, function(value, key) {
console.log(value);
});
}
Is this possible?
With the AngularJS framework, multiple elements are added with the ng-repeat directive:
<div ng-repeat="item in itemArr">
<textarea ng-model="item.wordset"
ng-change="onChange(item,$index)"
name="'item' + $index"
class="form-control app-word-set"
placeholder="Enter Word Set" rows="4">
</textarea>
</div>
<button ng-click="addNewTextarea()">Add input</button>
$scope.itemArr = [{}];
$scope.addNewTextarea = function() {
$scope.itemArr.push({});
};
New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view, ng-include and ng-if all create new child scopes, so [data hiding problems] often shows up when these directives are involved ... [they] can be easily avoided by following the "best practice" of always have a '.' in your ng-models.
For more information, see
AngularJS ng-repeat Directive API Reference -
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

AngularJS: Programmatically adding nested form (ngForm)

I have a specific problem where I need to add nested form in a form built with angular.
Statically (non-programmatically), I can the do the following:
<form>
...
<ng-form>
<input ng-model="myModel" required>
</ng-form>
...
</form>
And validation error in the nested form invalidates the outer form. Which is exactly what I need.
But I can't seem to do this programmically via a directive. In the template I have the following
<div dynamic-nested-form="">
</div>
And I have the following:
(function () {
angular
.module('controls')
.directive('dynamicNestedForm', dynamicNestedForm);
function dynamicNestedForm($compile) {
return {
restrict: 'A',
link: linkedFunction
};
function linkedFunction($scope, element) {
var nestedForm = angular.element('<ng-form><input ng-model="myModel" required></ng-form>');
element.append($compile(nestedForm)($scope));
}
})();
The form does get injected in the DOM and it is in invalid state, in Chrome element view, I see
<div dynamic-nested-form="" class="ng-scope">
<ng-form
class="ng-pristine ng-scope ng-invalid ng-invalid-required">
<input ng-model="myModel" required=""
class="ng-pristine ng-untouched ng-invalid ng-invalid-required">
</ng-form>
</div>
But outer form does not get invalidated. When the outer form loaded does it not see the inner form?
What am I doing wrong?
Right,
So,
Basically did a little debugging through Angular and it just happens that the nested form could not find the outer form controller. (The outer form controller is needed in ngModelDirective in its preLink)
The reason it cannot find it is because this preLink happens during compile (obviously, it's a preLink) and my compile was done before the nested form was attached.
So as a solution, instead of compiling before attaching, I do it after, like
var nestedForm = angular.element('<ng-form><input ng-model="myModel" required></ng-form>');
$compile(element.append(nestedForm ).contents())($scope);
Well, I think I am recompiling it.
Anyhoo, this then makes the inner control find the outer form and my problem is solved.

ng-change not working on a text input

I am new to angular js. In my code there is color picker initialized from a text field. User changes the value of color and I want that color to be reflected as a background of a text in a span. It is not working. What is missing?
HTML:
<body ng-app="">
<input type="button" value="set color" ng-click="myStyle={color:'red'}">
<input type="button" value="clear" ng-click="myStyle={}">
<input type="text" name="abc" class="color" ng-change="myStyle={color:'green'}">
<br/>
<span ng-style="myStyle">Sample Text</span>
<pre>myStyle={{myStyle}}</pre>
</body>
Plunker - http://plnkr.co/edit/APrl9Y98Em0d6rxuzRDE?p=preview
However when I change it to ng-click it works.
ng-change requires ng-model,
<input type="text" name="abc" class="color" ng-model="someName" ng-change="myStyle={color:'green'}">
I've got the same issue, my model is binding from another form, I've added ng-change and ng-model and it still doesn't work:
<input type="hidden" id="pdf-url" class="form-control" ng-model="pdfUrl"/>
<ng-dropzone
dropzone="dropzone"
dropzone-config="dropzoneButtonCfg"
model="pdfUrl">
</ng-dropzone>
An input #pdf-url gets data from dropzone (two ways binding), however, ng-change doesn't work in this case. $scope.$watch is a solution for me:
$scope.$watch('pdfUrl', function updatePdfUrl(newPdfUrl, oldPdfUrl) {
if (newPdfUrl !== oldPdfUrl) {
// It's updated - Do something you want here.
}
});
Hope this help.
When you want to edit something in Angular you need to insert an ngModel in your html
try this in your sample:
<input type="text" name="abc" class="color" ng-model="myStyle.color">
You don't need to watch the change at all!
Maybe you can try something like this:
Using a directive
directive('watchChange', function() {
return {
scope: {
onchange: '&watchChange'
},
link: function(scope, element, attrs) {
element.on('input', function() {
scope.onchange();
});
}
};
});
http://jsfiddle.net/H2EAB/
One can also bind a function with ng-change event listener, if they need to run a bit more complex logic.
<div ng-app="myApp" ng-controller="myCtrl">
<input type='text' ng-model='name' ng-change='change()'>
<br/> <span>changed {{counter}} times </span>
</div>
...
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.name = 'Australia';
$scope.counter = 0;
$scope.change = function() {
$scope.counter++;
};
});
https://jsfiddle.net/as0nyre3/1/
First at all i'm seing your code and you haven't any controller. So i suggest that you use a controller.
I think you have to use a controller because your variable {{myStyle}} isn't compile because the 2 curly brace are visible and they shouldn't.
Second you have to use ng-model for your input, this directive will bind the value of the input to your variable.

AngularJS: Reuse markup for editing many properties of an object

I have an object with a lot of properties (all of type number). I want to edit these properties so for every property I have an example markup:
<div>
propertyA: <input type="number" step="0.1" ng-model="configuration.propertyA" required>
</div>
Plunker
I don't want to repeat the markup for every property. I would like to use ng-repeat or custom directive, but I don't know how to deal with ng-model="...".
Something like:
<div ng-repeat="property in properties">
{{property.???}}: <input type="number" step="0.1" ng-model="property.???" required>
</div>
or custom directive (I know how to transclude static text but what with ng-model):
<my-directive input-value="PropertyA???">PropertyA: </my-directive>
EDIT (maybe will explain more):
I have an configuration object from Server. I don't want to repeat markup I have at the top of the question. I want to have markup once and then loop for every property, so every property will be edited. At the end I want to post configuration back to server.
following the obj you have in your plunkr, it'd just be
<div ng-repeat="item in configuration">
{{item}} <input type="number" step="0.1" ng-model="item" required>
</div>
http://plnkr.co/edit/k1qXyANKRAHJs5drTmXt?p=preview
$scope.configuration = {
propertyA: $scope.(NgModel-Name) <----
}
Instead of value put this $scope, it is called 2 way binding.
And use ng-value to put init value of items. Do you get it ?
It is very easy to do using directive.
app.directive('myInput', function() {
return {
restict: 'EA',
template: '<input type="number" step="0.1" ng-model="value">',
scope: {
value: '=',
},
}
})
Markup:
<span my-input value="configuration.weight"></span>
http://plnkr.co/edit/M9o5A8JYnQeDvhjWDCKU

Manual creation of nodes and ng-model

In more attempts to DRY bootstrap and AngularJS, I'm attempting to create a form and children while maintaining the ng-model relationships. I'm getting the correct HTML output, but something isn't connecting correctly with the model relationships, and the model isn't being updated:
Vanilla HTML
<form role="form" ng-model="customer">
<div class="form-group">
<label for="name">Your Name</label>
<input id="name" class="form-control" ng-model="customer.name" />
</div>
</form>
Simplified (goal) HTML
<div abs-form ng-model="customer">
<input id="name" label="Full Name" placeholder="i.e. Joe Smith"/>
</div>
Controller
.controller('HomeCtrl', function($scope){
$scope.customer = {};
}
abs-form Directive
.directive('absForm', function($compile){
var input = $('<input />'),
label = $('<label />');
group = $('<div class="form-group"></div>'),
formElements = [];
return {
restrict : 'EA',
replace : true,
transclude : false,
scope : {
ngModel : '=',
label : "#"
},
compile : function(tElement, tAttrs){
var children = tElement.children();
var tplElement = angular.element('<form role="form" ng-model="'+ tAttrs.ngModel +'" />');
// Clear the HTML from our element
tElement.html('');
// Loop through each child in the template node and create
// a new input, cloning attributes
angular.forEach(children, function(child){
var newInput = input.clone(),
newLabel = label.clone(),
newGroup = group.clone(),
$child = $(child),
attributes = child.attributes;
// Add the "for" attribute and the label text
newLabel.attr('for', $child.attr('id'));
newLabel.text($child.attr('label'));
// Add the class to the input
newInput.addClass('form-control');
newInput.attr('ng-model', tAttrs.ngModel + "." + $child.attr('id'));
// Copy the attributes from the original node to the new one
$.each(attributes, function(index, prop){
newInput.attr(prop.name, prop.value);
})
// Store the form elements for use in link() later
formElements.push(newLabel, newInput)
// Some reason passing in the formElements botches the appending
newGroup.append([newLabel, newInput]);
// Append the group to the element
tplElement.append(newGroup)
})
//$('input', tplElement).wrap('<span>')
// finally, replace it with our tplElement
tElement.replaceWith(tplElement);
}
}
})
This is the output of the directive above, like I said, the HTML is fine (as far as I can tell), but there's no connection of the model:
<form role="form" ng-model="customer" class="ng-pristine ng-valid">
<div class="form-group">
<label for="name">Full Name</label>
<input class="form-control ng-pristine ng-valid" ng-model="customer.name" id="name" label="Full Name" placeholder="i.e. Joe Smith">
</div>
</form>
Some of the questions I've found with similar scenarios (and similar ways to solve)
Changing ngModel
Adding ngModel to input
The second question was the best scenario, but I can't seem to get my new inputs to contribute to the "customer" model. I'm thinking there's more to it than just adding or changing the ng-model attribute on the node, but something Angular is doing to register the connection...?
The problem with your directive is that it introduces an isolate scope which does not include the original model name. The scope variable customer is henceforth known by the name ngModel within the directive's scope.
I updated the code to get rid of the jQuery dependency but basically it still does the same things.
See this fiddle: manual creation of nodes and ng-model

Resources