I am very new to angular js. I want to create an input box on click of particular div. Here I need to create element on div which repeating.
<div><div ng-repeat ng-click="create();"></div><div>
What will be the best way to do so?
DOM manipulation in Angular is done via directives (There is paragraph on 'Creating a Directive that Manipulates the DOM' here)
First, read through this excellent article: How do i think in Angular if i have a jQuery background
The Angular Team also provides a pretty neat tutorial, which definetly is worth a look: http://docs.angularjs.org/tutorial
While Angular is pretty easy and fun to use once you have wrapped your head around the concepts, it can be quite overwhelming to dive into the cold. Start slow and do not try to use each and every feature from the beginning. Read a lot.
I strongly recommend egghead.io as a learning resource. The video-tutorials there are bite-sized and easy to watch and understand. A great place for both beginners and intermediates. Start from the bottom here.
Some folks have done great things with Angular. Take a look at http://builtwith.angularjs.org/ and check out some source code.
Use an array and ng-repeat to do that. Have a look at the following code.
I crated scope variable as an empty array. Then created a function to add values to that array.
app.controller('MainCtrl', function($scope) {
$scope.inputFields = [];
$scope.count = 0;
$scope.addField = function(){
$scope.inputFields.push({name:"inputText"+$scope.count++});
}
});
I used ng-repeat with this array. and called the function on the click event of a div.
<div ng-click="addField()">Click here to add</div>
<div ng-repeat="inputField in inputFields">
<input type="text" name="inputField.name">
</div>
Check this working link
Update - Show only one text box on click
I created addField() as follows.
$scope.addField = function(){
$scope.newTextField = "<input type='text' name='myTxt'>";
}
To render this html in my view file I created a new directive called compile as follows.
app.directive('compile', function($compile) {
// directive factory creates a link function
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
Then used this directive in my view.html file
<body ng-controller="MainCtrl">
<div ng-click="addField()">Click to Add</div>
<div compile="newTextField"></div>
</body>
click here to view the working link
Related
I am trying to create directive but my directive function is not getting called.
index.html
<div ng-repeat="que in questions.Cars">
<question-dir>print from direcetive</question-dir>
</div>
ConytrolDirective.js
(function () {
"use strict";
angular
.module("autoQuote")
.directive('questionDir',["questionDir"] );
function questionDir()
{
return {
template : "<h1>Made by a directive!</h1>"
};
}
}());
There are several mistakes in your code.
You should have function name instead of "questionDir"
.directive('questionDir',[questionDir] );
Use kebab case(- separated words) while using directive name
`<question-dir>print from direcetive</question-dir>`
Additionally you need to refer <script src="controlDirectives.js"></script> on your index.html page.
Demo here
I don't see many mistakes in your code but it would have helped many to stop assuming only if you had posted your code sample link too.
But one primary change you have to make is : Change your directive definition function invoking from
.directive('questionDir', ["questionDir"])
to
.directive('questionDir', questionDir)
See this working fiddle which has just added questions.Cars into $rootScope to make answer look more relevant to your query.
To preserve content of your directive element :
What i see in your question is : Element <question-dir> has child content / inner text print from direcetive in it and it gets overridden by Directive Element <h1>Made by a directive!</h1> completely.
That's the default nature of Angular : Element's (to which the directive is being applied) children will be lost to Directive Element. If you wanted to perserve the element's original content/children you would have to translude it.
Use transclude : true in Directive Definition Object and add <div ng-transclude></div> where you want directive Content / text print from direcetive to be included.
Code snippet :
function questionDir() {
return {
transclude : true,
template: "<div ng-transclude></div><h1>Made by a directive!</h1>"
};
}
Check this one for transclude changes.
Check these 2 links for more information on Transclude :
Understanding the transclude option of directive definition
Understanding transclude with replace
You had a few things wrong in you code:
In your IIFE closure you need to pass in angular
You need to normalize the directive name to kabab case in your html element name: `'
You need to include the reference to the directory factory in your directive registration: .directive('questionDir', questionDir);
Missing module dependencies array: .module("autoQuote", []) -- you should only have this defined once with the dependancies, [] so ignore this if you already have it elsewhere in your code.
Missing restrict in the Directive Definition Object: restrict: 'E',
If you click on Run code snippet you will see this directive now works.
(function (angular) {
"use strict";
angular
.module("autoQuote", [])
.directive('questionDir', questionDir);
function questionDir()
{
return {
restrict: 'E',
template: "<h1>Made by a directive!</h1>"
};
}
}(angular));
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="autoQuote">
<question-dir>print from direcetive</question-dir>
</div>
Finally, if you are using AngularJS 1.5 or greater you should consider using the component syntax for this.
with this Angular directive every time my model changes, new HTML item appended to the page:
app.directive('helloWorld', function($compile) {
return {
restrict: 'AE',
replace: true,
scope:{
arrayItem: '=ngModel'
},
link: function ($scope, ele, attrs) {
$scope.$watch( 'ngModel' , function(){
ele.html('<div ng-click="sendLike({{arrayItem.data.timeline_content}})" class="timeline-item"> Hello {{arrayItem2.data.timeline_content}} </div>');
$compile(ele.contents())($scope);
});
}
};
});
And this is HTML view:
<hello-world ng-repeat="arrayItem in arr" ng-model="arrayItem"></hello-world>
But ng-click inside dynamically generated HTML doesn't work. actually recompiling of this new added section does not works.
UPDATE:
this is what i want to achieve:
in fact i want to create a chat Application. messages stored inside an Array and i have to bind that array to the HTML view. if i click on every message, i need to an alert() fired inside the controller. my controller is like this:
app.controller("timelineCtrl", function ($scope) {
$scope.arr={};
$scope.sendLike = function (id) {
alert(id);
};
.
.
.
}
in the jQuery way, i simply use DOM manipulation methods and add new tags for each message but in Angular way i have to bind that array as a ng-model or something like that.
at first glance, i realize that designing a directive should be a good idea and inside module i can access to main scope and do needed thing with that directive and i expect that changes inside that directive should projected to HTML view but it fails and things like ng-click doesn't work for dynamically created tags.
There are two ways that you could accomplish this, with or without a directive.
Let's start without a directive; we'll assume that you have an array in the controller.
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<div ng-click="sendAlert(post)">
<span class="postnumber">{{::post.lineNumber}}:</span>
<span class="message">{{::post.message}}</span>
</div>
</div>
</div>
whenever an object is added to $scope.timeline, it can be assigned a lineNumber, and we can use angular OrderBy to sort the direction in reverse lineNumber order (using -). The $scope.sendAlert(post) will send the specific post to the function. in our bindings, we use :: to indicate that these are one time bindings, i.e. not values that need to be monitored independently of the array. This can improve performance on large lists.
using a Directive, we can accomplish this in a very similar manner, by making a Directive that renders a specific post, and passing the post in as a property.
app.directive('timelinePost', function() {
return {
restrict: 'AE',
scope:{
post: '='
},
template: '<div ng-click="postCtrl.sendAlert()">
<span class="postnumber">{{::postCtrl.post.lineNumber}}:</span>
<span class="message">{{::postCtrl.post.message}}</span>
</div>',
controller: 'postController',
controllerAs: 'postCtrl',
bindToController: true
};
app.controller("postController", function(){
var self = this; //save reference to this
self.sendAlert = function(){
//the specific post is available here as self.post, due to bindToController
};
};
//usage in HTML:
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<timeline-post post='post'></timeline-post>
</div>
</div>
you could further wrap this timeline in a directive in a similar manner, if you so desired. Either of these will accomplish the same task, of looping through your data, ordering it so that the newest post is at the top, and updating whenever the array changes. In the non-directive method, the timelineCtrl handles the $scope.sendAlert function; in the directive method, it is handled by the directive controller postController.
Please note, this is a rough draft based on what you have described and the information from various posts over the last 2 days. I haven't created a dataset to iterate through to test thoroughly, but the logic here should get you started.
I've looked all over SO and Google and seen a whole bunch of reasons why ng-show wouldn't work inside of ng-repeat (scope issues, binding issues, etc.) but I can't quite pin down why mine isn't working. I was hoping that an extra pair of eyes could help me out. I'm very new to Angular, so hopefully my code makes some sense.
The goal: when $scope.current_set changes, the visible lmf-optionset changes. Currently, only the first optionset loads via Decision_Tree:loaded, and then when I try to load the next one via clickbox:clicked, $scope.current_set changes, but the view won't update.
JS
angular.module('lmf.option_set', [])
.controller('OptionsetCtrl', ['$scope', 'Optionsets', 'Decision_Tree',
function($scope, Optionsets, Decision_Tree) {
$scope.Optionsets = Optionsets;
$scope.current_set = {
name: null
};
$scope.$on('clickbox:clicked', function() {
$scope.current_set = Decision_Tree.get_next_optionset();
});
$scope.$on('Decision_Tree:loaded', function() {
$scope.current_set = Decision_Tree.get_next_optionset();
});
}
])
HTML
<div ng-controller='OptionsetCtrl'>
<div ng-repeat='set in Optionsets.option_sets'>
<div ng-show="set.name == current_set.name" lmf-optionset="{{set.name}}"></div>
</div>
</div>
Apparently lmf-optionset creates an isolated scope for each entry. These child scopes have link to parent's current_set, but they doesn't maintain a two-way binding. So when you are replacing the current_set value in you event handlers, child scopes are unaware that current_set was changed, and they are still referring to the old value.
You can either rewrite your event handlers like this:
$scope.current_set.name = Decision_Tree.get_next_optionset().name;
Or you can use controller as construction like this:
<div ng-controller='OptionsetCtrl as vm'>
<div ng-repeat='set in Optionsets.option_sets'>
<div ng-show="set.name == vm.current_set.name" lmf-optionset="{{set.name}}"></div>
</div>
</div>
I'm trying to create an angular component that adds a prefix to an <input> element. Something like this:
The idea would be to use it like this:
<input type="text name="url" input-prefix="http://">
For this I need to wrap the <input> around a <div> container which will also include a <span> with the prefix and add some custom CSS.
I believe I need to use the directive compile function to achieve this because of the DOM manipulation, but I don't understand very well how it works and I haven't found much documentation.
The issue I found so far is that after manipulating the DOM on the compile function, the <input> appears to be completely unusable, I cannot even type in it. Here's my fiddle:
http://codepen.io/anon/pen/vokxy - Can't type on the <input> inside the green wrapper.
You don't manipulate the DOM in the compile phase, you do it in link phase (postLink actually), since the DOM isn't actually set up correctly until then. There's an example in the Developer Guide.
Moved your code to link method, here's a working example.
Example:
var app = angular.module('my-app', []);
app.controller("myController", function($scope) {
$scope.ctrl = this;
$scope.name = "w";
$scope.minLength = 3;
});
app.directive("inputPrefix", function() {
return {
restrict: 'A',
link: function(scope,element, attrs) {
var wrapper = angular.element(
'<div class="prefixed-input">');
element.after(wrapper);
element.removeAttr("input-prefix");
wrapper.append(element);
}
}
});
Live example: http://jsfiddle.net/choroshin/cnc5m/
I'm looking for a way to integrate something like ng-repeat with static content. That is, to send static divs and to have them bound to JS array (or rather, to have an array constructed from content and then bound to it).
I realize that I could send static content, then remove and regenerate the dynamic bits. I'd like not to write the same divs twice though.
The goal is not only to cater for search engines and people without js, but to strike a healthy balance between static websites and single page applications.
I'm not sure this is exactly what you meant, but it was interesting enough to try.
Basically what this directive does is create an item for each of its children by collecting the properties that were bound with ng-bind. And after it's done that it leaves just the first child as a template for ng-repeat.
Directive:
var app = angular.module('myApp', []);
app.directive('unrepeat', function($parse) {
return {
compile : function (element, attrs) {
/* get name of array and item from unrepeat-attribute */
var arrays = $parse(attrs.unrepeat)();
angular.forEach(arrays, function(v,i){
this[i] = [];
/* get items from divs */
angular.forEach(element.children(), function(el){
var item = {}
/* find the bound properties, and put text values on item */
$(el).find('[ng-bind^="'+v+'."]').each(function(){
var prop = $(this).attr('ng-bind').split('.');
/* ignoring for the moment complex properties like item.prop.subprop */
item[prop[1]] = $(this).text();
});
this[i].push(item);
});
});
/* remove all children except first */
$(element).children(':gt(0)').remove()
/* add array to scope in postLink, when we have a scope to add it to*/
return function postLink(scope) {
angular.forEach(arrays, function(v,i){
scope[i] = this[i];
});
}
}
};
});
Usage example:
<div ng-app="myApp" >
<div unrepeat="{list:'item'}" >
<div ng-repeat="item in list">
<span ng-bind="item.name">foo</span>
<span ng-bind="item.value">bar</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">spam</span>
<span ng-bind="item.value">eggs</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">cookies</span>
<span ng-bind="item.value">milk</span>
</div>
</div>
<button ng-click="list.push({name:'piep', value:'bla'})">Add</button>
</div>
Presumable those repeated divs are created in a loop by PHP or some other backend application, hence why I put ng-repeat in all of them.
http://jsfiddle.net/LvjyZ/
(Note that there is some superfluous use of $(), because I didn't load jQuery and Angular in the right order, and the .find on angular's jqLite lacks some features.)
You really have only one choice for this:
Render differently for search engines on the server, using something like the approach described here
The problem is you would need to basically rewrite all the directives to support loading their data from DOM, and then loading their templates somehow without having them show up in the DOM as well.
As an alternative, you could investigate using React instead of Angular, which (at least according to their website) could be used to render things directly on the web server without using a heavy setup like phantomjs.