Pass a reference to DOM object with ng-click - angularjs

I have multiple elements with the same callback on ng-click:
<button ng-click="doSomething()"></button>
<button ng-click="doSomething()"></button>
<button ng-click="doSomething()"></button>
<button ng-click="doSomething()"></button>
// In controller:
$scope.doSomething = function() {
// How do I get a reference to the button that triggered the function?
};
How can I get the reference to the object which made the call to doSomething? (I need to remove an attr from it)

While you do the following, technically speaking:
<button ng-click="doSomething($event)"></button>
// In controller:
$scope.doSomething = function($event) {
//reference to the button that triggered the function:
$event.target
};
This is probably something you don't want to do as AngularJS philosophy is to focus on model manipulation and let AngularJS do the rendering (based on hints from the declarative UI). Manipulating DOM elements and attributes from a controller is a big no-no in AngularJS world.
You might check this answer for more info: https://stackoverflow.com/a/12431211/1418796

The angular way is shown in the angular docs :)
https://docs.angularjs.org/api/ng/directive/ngReadonly
Here is the example they use:
<body>
Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
<input type="text" ng-readonly="checked" value="I'm Angular"/>
</body>
Basically the angular way is to create a model object that will hold whether or not the input should be readonly and then set that model object accordingly. The beauty of angular is that most of the time you don't need to do any dom manipulation. You just have angular render the view they way your model is set (let angular do the dom manipulation for you and keep your code clean).
So basically in your case you would want to do something like below or check out this working example.
<button ng-click="isInput1ReadOnly = !isInput1ReadOnly">Click Me</button>
<input type="text" ng-readonly="isInput1ReadOnly" value="Angular Rules!"/>

Related

Angular nested directive communication

I have a directive that look like this:
<div ng-controller="searchController" searchbar>
<input type="text" id="searchfield" ng-model="myVar">
<div searchresult>
<div ng-repeat="data in data.menu | filter: myVar">
{{data.title}}
</div>
</div>
</div>
There is one controller called searchController, and 2 directives called searchbar and searchresult.
How can I have the searchbar directive to run a function in searchresult directive? I was thinking to put require: "^searchresult" in searchbar directive and call it's function. Is that the correct way of doing? What if the searchresult is not available there?
Here is the detail scenario:
When user type in the input field, the search result update by the filter. Therefore the height of searchresult div is changed.
How can the searchresult directive knows about the changes of the height & run the required function instantly?
searchResult can have reference to searchBar not the other way around. As from documentation
^ - Locate the required controller by searching the element and its
parents. Throw an error if not found.
Also look at ^? for optional dependencies. See compile documentation.
If you detail your scenario, we can help better.
Update: Based on the scenario that you have outlined, you can directly watch myVar property in searchresult directive and whenever it changes you can trigger the function to resize the results.
Assuming that the searchresult directive does not create isolated scope. You can try, such a code inside your directive link function
scope.$watch('myVar',function(newValue) {
$timeout(function() {
//do some work here
},0);
});
Timeout helps as we cannot be sure when the rendering of the list is complete.

Angular ng-repeat with ng-form, accessing validation in controller

I am trying to generate an editable list using ng-repeat. I want to remind the user to update any edits before moving on, so I am using ng-form to create "nested" forms on the fly because the documentation says I can then use validation on these dynamically created inputs.
While that seems to work within the HTML, I don't see how to access those dynamically created forms and related validation fields in the controller. Specifically, when the user changes the input I use the form $dirty property to bring up a button to tell the user to commit the changes. So far, so good. However, once the changes are committed I want to $setPristine() on the field to indicate that the changes have been set. There may be other ways of ensuring that changes are committed on each input before I allow the main form committed, but this was the best I could come up with.
Unfortunately, even though the documentation says that if I name the ng-form it will be propagated to the $scope object, I can't find a way to access it. $scope.dynamic_form is undefined.
Here is a plunker showing what I mean:
plnk
Thanks!
[EDIT] Just to add to the issue, what does work for this specific example is to add to the ng-click on the dynamically created input:
ng-click="namesForm.name.$setPristine();clean()"
But I still don't have access to the dynamically created form in the controller. I would like, for example, to add a watcher to the namesForm.name.$pristine so that I can set the mainForm.$setValidity(false) whenever the sub-form is $dirty to prevent the user from submitting the main form until all sub-form changes have been committed.
So in a nutshell, the issue is how to access in a parent controller the validation values of a dynamically created nested ngForm?
Updated 2015-01-17:
As pointed out by Leblanc Meneses in the comments Angular 1.3 now supports interpolation with form, ngForm and input directives.
This means that using expressions to name your elements:
<div ng-form="namesForm_{{$index}}" ng-repeat="name in names">
<input type="text"
name="input_{{$index}}_0"></input>
<!-- ... -->
</div>
will work as expected:
$scope['namesForm_0']
$scope.namesForm_1
// Access nested form elements:
$scope.namesForm_1.input_1_0
...
Original answer for Angular <= 1.2:
Working with forms and the ngFormController can get tricky pretty quickly.
You need to be aware that you can dynamically add form elements and inputs but they can't be dynamically named - interpolation does not work in the ngForm or name directives.
For example, if you tried to name your nested forms dynamically like this:
<div ng-form="namesForm_{{$index}}" ng-repeat="name in names">
<!-- ... -->
</div>
Instead of making all the nested forms available on the scope like this: scope['namesForm_0'] you would only have access to the single (last) form with the literal name scope['namesForm_{{$index}}'].
In your situation you need to create a custom directive that will be added along with ngFormto handle setting $pristine$ and $invalid for that form instance.
JavaScript:
This directive will watch the $dirty state of its form to set the $validity to prevent submission when dirty and handle setting the $pristine state when the 'clean' button is pressed.
app.directive('formCleaner', function(){
return {
scope: true,
require: '^form',
link: function(scope, element, attr){
scope.clean = function () {
scope.namesForm.$setPristine();
};
scope.$watch('namesForm.$dirty', function(isDirty){
scope.namesForm.$setValidity('name', !isDirty);
});
}
};
});
HTML:
Then the only change to your HTML is to add the formCleaner directive.
So change your original HTML from this:
<body ng-controller="MainCtrl">
<form name="mainForm" submit="submit()">
<h3>My Editable List</h3>
<div ng-form="namesForm"
ng-repeat="name in names">
<!-- ... -->
</div>
<button class="btn btn-default" type="submit">Submit</button>
</form>
</body>
to this, by adding form-cleaner next to ng-form:
<body ng-controller="MainCtrl">
<form name="mainForm" submit="submit()">
<h3>My Editable List</h3>
<!-- Add the `form-cleaner` directive to the element with `ng-form` -->
<div form-cleaner
ng-form="namesForm"
ng-repeat="name in names">
<!-- ... -->
</div>
<button class="btn btn-default" type="submit">Submit</button>
</form>
</body>
Here is an updated Plunker showing the new behaviour: http://plnkr.co/edit/Lxem5HJXe0UCvslqbJr3?p=preview

angular directives comment section

I am working on a website that displays numerous articles. Each article has a comment section. I have effectively been able to recursively write the comments to the DOM with recursion inside an ng-repeat. However, I need to be able to click on a respond button on any of the comments (they display in a nested fashion) and for a div to be inserted beneath the clicked button. This div would contain a text area for the comment they want to submit and a button. When this second button is clicked, the controller will save the comment to the database. I initially wanted to do this by directly manipulating the DOM from the controller. However, after further research, that would be in direct violation of the MVC/MVW pattern. I believe the correct answer is to create a custom directive. Please give me some insight on how to correctly do this. Any and all information would be very helpful. Thanks in advance.
If you want to add response div dinamically:
<div ng-repeat="article in articles" id="article-{{$index}}">
<p>{{article.content}}</p>
<button ng-click="addAnswer($index)">Add Answer</button>
</div>
js:
myApp.controller("articlesController", function($compile){
$scope.addAnswer = function (index) {
var div = $("<div></div>");
var input = $("<input type='text' ng-model='article.response'></input>");
div.append(input);
var button = $("<button>Send</button>");
button.attr("ng-click", "sendResponse(article)");
$compile(div)($scope);
$("#article-" + index).append(div);
};
});
You don't really need to make a directive to achieve this.
html:
<div ng-repeat="article in articles">
<p>{{article.content}}</p>
<input type="text" ng-model="article.response"></input>
<button ng-click="sendResponse(article)">Send</button>
</div>
js:
myApp.controller("articlesController", function($http){
$scope.sendResponse = function (article) {
console.log(article.response);
$http.post(url, article);
};
});
Of course, you can do it better by hidding input and send button, and show it after user clicks over an answer button.

AngularJS: create element dynamically

How do I go about create an element in my controller? e.g. on a click event?
example controller:
function AddCtrl($scope){
$scope.add = function(){
// do stuff to create a new element?
}
}
example view:
<div ng-controller="AddCtrl">
<button ng-click="add()">Add</button>
// create <input type="text" ng-model="form.anotherField">
</div>
Any suggestions much appreciated.
AngularJS is intended to follow MVC - so the controller creating an element in the view doesn't agree with the MVC behavior. The controller should not know about the view.
It sounds as if you want to have a control appear based on some conditional logic. One approach would be to bind to the visibility of the element.
In Angular, your controllers should not be manipulating the DOM directly. Instead, you should describe the elements you need in your templates, and then control their display with directives, like ng-switch, ng-hide / ng-show, or ng-if, based on your model, ie, your data.
For example in your controller you might do something like:
$scope.showForm = false;
And then in your partial:
<div id="myForm" ng-show="showForm">
<!-- Form goes here -->
</div>
By switching $scope.showForm between true and false, you will see your myForm div appear and disappear.
This is a classical mistake coming from jQuery moving to Angular or any other MVC library. The way you should think is to let the view react to changes in the scope.
$scope.items = []
$scope.add = function(){
$scope.items.push({});
}
In the view:
<input type="text" ng-repeat="item in items" ng-model="item.property">
If you want to display an element based on some condition or after the click, use ng-switch: http://docs.angularjs.org/api/ng/directive/ngSwitch
If you want to add multiple elements, create a repeated list of items and add an item to your view-model on clicking the button:
$scope.yourlistofitems = [];
$scope.add = function() {
$scope.yourlistofitems.push("newitemid");
}
And in the HTML:
<input type="text" ng-repeat="item in yourlistofitems" ng-model="item.property">

How to use ngModel to bind an input control with Javascript after AngularJS's bootstrap?

How to use ngModel to bind an input control with JavaScript after AngularJS's bootstrap?
I tried to add ng-model attribute by setAttribute method, but it didn't work!
function FormController($scope) {
$scope.name="OK";
$scope.createBind=function(){
var texteditor=document.getElementById("test");
if(texteditor.getAttribute("ng-model")==null){
texteditor.setAttribute("ng-model","name");
$scope.$apply();
}
}
}
<input type="text" id="test">
<button ng-click="createBind()">Bind</button>
Angular don't get changes if you make them via javascript or jquery or etc...
You should call $scope.$apply() to get such changes manually...
Here is a good article for you to understand $scope.apply()...

Resources