Dynamically adding input fields in an Angular application - angularjs

I have a standard Angular controller invoked with appropriate routing:
when('/admin/foo/new',{
controller:FooNewCtrl,
templateUrl: 'frontend/partials/admin/foo-new.html'
}).
FooNewCtrl, nothing in it:
function FooNewCtrl($scope) {
}
In foo-new.html I have a section where I want the user to be able to dynamically add and delete input fields:
<label>Fields<i class="foundicon-plus"></i></label>
<div ng-repeat="field in fields">
<input type="text" id="field.id"/>delete
</div>
I do not know how to make this happen. Note that I have a link, with a "plus" icon, used to add the new field, then when the field is displayed, a link that would delete it.
I do not know how to control "fields" with a controller and keep the user on the page.
I'm looking for a push in some direction.

In your ng-repeat you're basically saying 'for every field in the fields collection, located in this scope'.
function FooNewCtrl($scope) {
//What you're iterating over in your ng-repeat
$scope.fields = [];
//Declare a function on the scope that you can reference from your template
$scope.addField = function(){
$scope.fields.push(1); //Push a new object into the fields
};
}
So yeah you'd add an ng-click event to your element (parent of your icon), that pushed a new field onto fields.
<label>Fields<a ng-click="addField()"><i class="foundicon-plus"></i></a></label>
<div ng-repeat="field in fields">
<input type="text" id="field.id"/>delete
</div>
Clicking the plus icon should now dynamically add your inputs. I'll leave removing them to you.

Related

multiple inputs based on array

My angular experience is basically about 3 days part time, so there's probably something simple I'm missing here.
I'm trying to create a dynamic list of multiple inputs based on an array, which I then want to reference from elsewhere in the app. What I've tried is loading a template from a custom directive, then $compile-ing it.
<input data-ng-repeat="term in query" data-ng-model="term">
My controller contains $scope.query = [""] which successfully creates the first empty input box. But the input box doesn't seem to update $scope.query[0] when I modify it. This means that when I try to create another empty input box with $scope.query.push(""); (from a keypress listener looking for the "/" key) I get a "duplicates not allowed" error.
I've tried manually listening to the inputs and updating scope.$query based on their value, but that doesn't feel very "angular", and results in weird behaviour.
What do I need to do to link these values. Am I along the right lines or way off?
I made a simple jsfiddle showing how to use an angular model (service) to store the data. Modifying the text inputs will also modify the model. In order to reference them somewhere else in your app, you can include TestModel in your other controllers.
http://jsfiddle.net/o63ubdnL/
html:
<body ng-app="TestApp">
<div ng-controller="TestController">
<div ng-repeat="item in queries track by $index">
<input type="text" ng-model="queries[$index]" />
</div>
<br/><br/>
<button ng-click="getVal()">Get Values</button>
</div>
</body>
javascript:
var app = angular.module('TestApp',[]);
app.controller('TestController', function($scope, TestModel)
{
$scope.queries = TestModel.get();
$scope.getVal = function()
{
console.log(TestModel.get());
alert(TestModel.get());
}
});
app.service('TestModel', function()
{
var queries = ['box1','box2','box3'];
return {
get: function()
{
return queries;
}
}
});

AngularJS - Directive to append input value to a list and bind to model

End goal: Associate multiple email addresses, each with a frequency setting (daily,weekly,monthly), to a notification.
I am attempting to define a directive which acts on a button element, such that, when the button is clicked, it takes the email address from the input element and the frequency from the drop-down next to the button and inserts them into a list below the input+button and binds the dynamic list to a property on the controller so it can be sent to the server when the user submits the form.
The form is all wired up - thats not the question.
I just want some help in building the directive.
Here's what i have so far:
HTML:
<section ng-controller="notificationmanagement as vm">
<input name="email" type="email"/>
<select>
<option>Daily</option>
<option>Monthly</option>
<option>Weekly</option>
</select>
<button data-cm-click type="button" class="btn btn-default"></button>
<div ng-model="vm.Subscribers"></div>
</section>
Directive:
commonModule.directive('cmClick', function () {
return function (scope, element) {
element.bind("click", function () {
// Is this the best way to get the value of the EmailAddress & Frequency?
var emailAddress = element[0].parentElement.parentElement.children[0].value;
var frequency = element[0].parentElement.parentElement.children[1].value;
var spanEmailTag = angular.element('<span>' + emailAddress + '</span>');
var spanFreqTag = angular.element('<span>' + frequency + '</span>');
angular.element(spanEmailTag).appendTo(element[0].parentElement.parentElement);
angular.element(spanFreqTag).appendTo(element[0].parentElement.parentElement);
// How to make sure each added email+frequency is available
// in the array bound to 'vm.Subscribers'
});
}
});
The structure of 'vm.Subscribers' should be something like this, in order for it be consumed by the server:
vm.Subscribers = [ {'EmailAddress':'joe#email.com', 'Frequency':'Daily'}, {'EmailAddress':'bob#email.com', 'Frequency':'Monthly'} ];
NOTE: I would ideally like to achieve this without relying on jQuery within the directive.
Any and all pointers/help/advice would be most appreciated!
If you want to encapsulate and reuse some functionality, then, by all means, use a directive.
But first, understand the MVVM (Model-View-ViewModel) paradigm and how Angular implements this.
For starters, assume that there is no View (and so, no DOM, directives, etc...) and that user inputs magically occur when something is exposed on the $scope (or, if you are using ControllerAs - as a property of the controller). Now, build you app's functionality starting from the controller. Define the data structure and the behavior with functions.
app.controller("notificationmanagement", function(){
// list of subscribers. You could also populate it from the backend.
this.subscribers = [];
// this is where the details of a new subscriber will be stored
// until subscriber is added
this.newSubscriber = {EmailAddress: null, Frequency: null};
// action to add a susbscriber
this.addSubscriber = function(){
this.subscribers.push(this.newSubscriber);
// clear the object (it's not necessary to declare all the properties)
this.newSubscriber = {};
}
});
That is, in a nutshell, all your app is doing. The controller doesn't (and shouldn't) care how this is displayed in the View. This is why DOM manipulation is frown upon, because it breaks separation of concerns.
Now, to the View. The View is mostly declarative:
<section ng-controller="notificationmanagement as vm">
<input ng-model="vm.newSubscriber.EmailAddress" type="email>
<select ng-model="vm.newSubscriber.Frequency">
<option value="Daily">Daily</option>
<option value="Monthly">Monthly</option>
<option value="Weekly">Weekly</option>
</select>
<button ng-click="vm.addSubscriber()"> Add </button>
<hr/>
<h3>All subscribers:</h3>
<div ng-repeat="s in vm.subscribers">
<span>{{s.EmailAddress}}</span>
<span>{{s.Frequency}}</span>
</div>
</section>
Notice how ng-model directives bind input data to controller's data. Notice ng-click that invokes an action on the controller. Notice ng-repeat that iterates and creates DOM elements based on that data. The View is purely driven by data, which is referred to by ViewModel.
Understand this first, then move onto directives.

Forms-angular, how a custom directive field can affect the $pristine state of the full record?

Question related to forms-angular project.
Preamble
The default formInput directive of forms-angular can be override by a custom directive by specifying the form.directive property in an extended mongoose model,
var CategorySchema = new Schema({
name: String,
});
var PlansSchema = new Schema({
categories: {
type: [CategorySchema],
form: {
directive: 'plan-categories'
}
}
});
The custom plan-categories directive has a template where fields of [CategorySchema] can be edited.
What is working
Let's start with a first simple template:
<div>
<div ng-repeat="category in record.categories">
<input ng-model="category.name" />
</div>
</div>
Forms-angular can successfully detect changes in these custom plan-categories directive input fields bound to data (injected scope.record). In particular when changing the user changes the value of the above input fields, the "Save" button of the page is enabled, allowing the Save operation.
The activation of the Save button thanks to the following comparison in parent formInput's BaseCtrl scope false === $scope[$scope.topLevelFormName].$pristine (see base.js).
Not working
Now, the Save button doesn't get enabled, when changing the category.name variable with an expression or a function called by ng-click, as below:
<div>
<div ng-repeat="category in record.categories">
<input ng-model="category.name" />
<button ng-click="category.name = 'Hello'">Edit</button>
</div>
</div>
On button click, the category.name variable seems to be correctly changed, since the value in the input is changed accordingly. Unfortunately, the Save button stays disabled.
Note: I also unsuccessfully tried to pass to ng-click a method (from the scope injected in the link method of the custom directive) and setting the category.name variable in a $timeout call.
I guess the ng-model directive of the input field calls parent's (multi-ancestor?) $setDirty() method.
Question
how do I magically get $setDirty() called by forms-angular in order to enable the "Save" button
If it is not possible:
how do I access BaseCtrl scope and call $setDirty() when changing the record.categories elements?
Offhand I cannot think of a magical solution, but the decidedly non-magical way is to depend on $data.baseScope (see https://github.com/forms-angular/forms-angular/blob/master/js/controllers/base.js#L12) which saves going through lots of $parents.

Update directive variable from controller

New to AngularJS I have a simple directive, service and controller. I loop through a list of items from a database in the controller embedded in the directive to render a checkbox list of items. From a form I am able to update my list of items in the database. I would at the same time like to update my list of displayed items with the newly added item and was hoping to benefit from Angulars two-way binding but I can't figure out how...
My directive:
angular.module('myModule').directive('menu', function (menuService) {
return {
restrict: 'E',
templateUrl: '../templates/menu.html',
controller: function () {
var me = this;
menuService.getMenuItems().then(function(data) {
me.items = data;
});
},
controllerAs: 'menu'
};
});
and corresponding html:
<div ng-repeat="item in menu.items">
<div class="col-md-4" ng-if="item.menuItem">
<div class="checkbox">
<label for="{{item._id}}">
<input id="{{item._id}}" type="checkbox" name="menuItems" ng-model="menuItems[$index]" ng-true-value="{{item._id}}">
{{item.menuItem}}
</label>
</div>
</div>
</div>
My issue is now that if I add a new item using this controller
EDIT: On my page I have two things. 1: A list of items rendered using the directive above and 2: A simple form with a single input field. I enter a new menuItem and click "Save". That triggers the controller below to be called with the new menuItem. The menuItem is then added to a collection in my database but I would like the list on my page to update with the newly added item. Preferably without having to reload the entire page.
$scope.insertMenuItem = function () {
$http.post('/menuItems', $scope.formData)
.success(function (data) {
$scope.items = data;
});
};
then the "me.items" in my directive remains unchanged hence so does my list in my browser.
How do I "tie it all together" so that when I call insertMenuItem then my.items are updated automagically?
This didn't come out as well as I had hoped but I hope you get the meaning...
Thanks in advance
take a look at this http://jsbin.com/rozexebi/1/edit, it shows how to bind a list of items in a directive to a controller, hope it helps.

Adding to a list model that is immediately editable in the DOM, using AngularJS

I have a list of model objects that are presented in the view with an ng-repeat directive on input elements, being editable. There is an Add new button to add a new item to the list, which creates a new empty input. But as soon as the new empty input is created in the view, I want it to be focused and ready to be edited. I'm not sure how to go about this, since I have little access to the DOM in the controller and the controller is where I am adding the new empty model to the list.
I'm still new with AngularJS, writing this app to learn. So, do let me know any other things I've done wrongly in the implementation.
Plunker: http://plnkr.co/Ig4OtuUtFatIknO1Kfxi
Interesting pieces of code:
HTML:
<body ng-controller=PlanetCtrl>
<div>
<input ng-repeat='it in planets' type=text ng-model=it.name edit-spotlight>
</div>
<button ng-click=addNew() class='btn btn-primary'>+ Add new item</button>
<div id=spotlight-shadow></div>
</body>
Javascript:
angular.module('planet-man', []).
// ... editSpotlight directive ...
function PlanetCtrl ($scope) {
$scope.planets = [
{name: 'Mercury'},
{name: 'Venus'},
{name: 'Earth'},
{name: 'Mars'}
];
$scope.addNew = function () {
$scope.planets.push({name: ''});
};
}
As you can see from the plunker, when the Add button is clicked, a new input is added to the view, but one has to manually focus it. I want it to receive focus (and have the spotlight effect appear) immediately on clicking the Add button.
Thank you.
tried to do this in a bit more 'angular' way: http://plnkr.co/edit/8vtgfTqBh3sxqO0KLk4y
Here is modified version of your plunker: plnkr.co/edit/QScU5GE1EeGskbmrZCIc?p=preview

Resources