Saving new models using AngularJS and $resource - angularjs

I'm trying to get an understanding of AngularJS using $resource, however most of the examples I see out there don't explain how to actually create new instances of something using $resource (or how the entire setup should look).
I've posted my code at the bottom of this.
I have the following setup, where posting to '/entry/api' should create a new entry.
The entry object it self has three properties: name, description and id.
i thought that calling
$scope.save(); would do two things:
Map the input fields as POST data
make a POST request to the url defined in the $resource (in this case '/entry/api')
Some examples I've seen are manually mapping the data to the resource as such:
var entry = new Entry();
entry.name = $name; // defined in entryController
entry.description = $scope.description; // <-- defined in entryController
entry.$save()
I thought this wasn't supposed to be necessary, as these fields are defined in the html.
This solution results in:
Defining a model in the backend
Defining a model in the front end (the entryController div)
Adding the values from the from the entryController div to the JS version of the model and then finally saving it.
It might be the way AngularJS works, however I thought that the input fields in the html would automatically be mapped.
Otherwise you have at least 3 places in the code to update if you add or remove a property of your (backend) model.
How are you supposed to use AngularJS along with $resource to save new objects?
angular.module('entryManager', ['ngResource']);
function pollController($scope, $resource) {
$scope.polls = $resource('/entry/api/:id', {id: '#id'});
$scope.saveEntry = function() {
this.save();
}
}
<html ng-app="entryManager">
... <-- include angularjs, resource etc.
<div ng-controller="entryController">
<input type='text' ng-model="name"><br/>
<textarea ng-model="description" required></textarea><br/>
<button class="btn btn-primary" ng-click="saveEntry()">Save</button>
</div>

The first think you should note, that scope != model, but scope can contain model(s).
You should have some object in your scope and then save it.
So, there would be something like the following:
HTML:
<div ng-controller="entryController">
<input type='text' ng-model="poll.name"><br/>
<textarea ng-model="poll.description" required></textarea><br/>
<button class="btn btn-primary" ng-click="saveEntry()">Save</button>
</div>
JavaScript:
function pollController($scope, $resource) {
var polls = $resource('/entry/api/:id', {id: '#id'});
$scope.saveEntry = function() {
polls.save($scope.poll);
}
}
Note1: even if you do not have initialized poll object, AngularJS will automatically create new object when you start typing.
Note2: its better to wrap your form into ngForm (by adding ng-form="someformname" attribute to div with ng-controller or wrap with <form name='...'>..</form>. In this case you could check validity of form by $scope.someformname.$valid before saving.
Good example is on main page of AngularJS web site under "wiring the backend" section (btw, mine favorite).

Don't use save method over model object itself, use save method of model class, For example -
//inject User resource here
$scope.user = new User();
$scope.user.name = "etc";
User.save($scope.user,function(response){
});
previously i was using $scope.user.$save(function(response){}) it was clearing my $scope.user object

Related

Why I cannot access form angular's properites in JS such as $error?

I cannot access angular's Form members. I don't have a clue why. I'm using angular 1.4. The full code is at: http://jsbin.com/lujojiduru/edit?html,js,console,output
angular.module('test',[]).controller('testController', function($scope){
$scope.sendInvitations = function(){
var error = myForm.NewInvitations.$error;
console.log('sent');
console.log('value: ' + error );
};
});
the value of $error is always undefined. Any idea?
var error = myForm.NewInvitations.$error;
should be:
var error = $scope.myForm.NewInvitations.$error;
Notice the $scope
This is assuming you have the name="myForm" on your <form> tag
So something like:
<div ng-controller="testController">
<form name="myForm" novalidate>
...
</form>
</div>
you can also, if you prefer, send in the validity of your form, to your method on the controller.
So:
<button class="btn btn-success" ng-click="sendInvitations(myForm.$valid)">Send Invitations</button>
And in your controller:
$scope.sendInvitations = function(isValid){
if(!isValid)
return;
};
Update
You also don't have any errors.
Add required to your input.
So your controller is now:
angular.module('test',[]).controller('testController', function($scope){
$scope.sendInvitations = function(){
debugger;//
var error = $scope.myForm.NewInvitations.$error;
console.log('sent');
console.log(error );
};
});
and your input
<input style="width: 95%" name="NewInvitations" type="email" ng-model="newInvitations" required />
This is an update to your bin
http://jsbin.com/hebikucanu/1/edit?html,js,console,output
myForm is not accessible in the global scope. You can pass it in as an argument to sendInvitations.
ng-click="sendInvitations(myForm)
$scope.sendInvitations = function(myForm){
It's unlikely that you'd need to do this, though. You can do use the myForm properties in the view.
In angular, you should never touch the DOM. Meaning, you should never invoke HTML elements directly as you would in traditional HTML/JS settings. (read more)
Angular encourages the use of ng-model to employ two-way binding as a means to communicate between the controller and the view. (angular two-way data binding tutorial)
Thus, the first change you should attempt is to replace
var error = myForm.NewInvitations.$error;
with:
var error = $scope.NewInvitations;
This will cause the code to run.
But it appears that you want to retrieve the email input validation error and display it through angular.
Here is an excellent explanation with a tutorial that I think achieves what you're trying to do. If you just want to see the code in action, try this link. (Be sure to hit the Run button.)
Hope this helps!

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.

AngularJS - How do I split my controller?

I would like to split my controller into two - Todos and Tags, right now I have both in one file. But I don't know how to tell angularJS. I tried to split it but then my directives in HTML were no longer valid. I mean in my HTML I have two functions from two different controllers (if I split them) and I need to call them both without making other instance of any controller. How could I do this?
I have cut some irrelevant code from this example and replaced it with dots, but I wrote comments what is there if someone found it important.
This is my JS:
var todoApp = angular.module('todoApp', []);
... // here is some rest api code for Todos
todoApp.controller('TodoCtrl', ['$scope', 'dataService', function($scope, dataService) {
...
$scope.addTodo = function () {
var newTodo = $scope.newTodo.trim();
if (!newTodo.length) {
return;
}
... // more code follows up about Todo functions
$scope.addTag = function () {
var newTag = $scope.newTag.trim();
if (!newTag.length) {
return;
}
... // more code follows up about Tag functions
and this is in my HTML
<div ng-controller="TodoCtrl">
...
<div class="row" id="topnav">
<div class="col-md-12">
<form ng-submit="addTodo()">
<input type="text" id="inputNewTodo" placeholder="Create new todo" ng-model="newTodo" />
</form>
<form ng-submit="addTag()">
<input type="text" id="inputNewTag" placeholder="Create new tag" ng-model="newTag" />
</form>
</div>
</div>
...
</div>
If someone is interested you can check the live code here http://taskybird.com/
a controller contains the logic dedicated to a specific view/viewmodel. If a view (or component) on your site contains these two buttons, then these functions belong to the same controller. This doesn't mean that the business logic can't be sepparated (e.g. by using services)!
You can use different controllers for different parts of your view by using ng-controller attribute. In case you only need to modularize your code, you can create separate modules and inject them in your main app.js file.
Eg. var myApplication = angular.module("myApplication", ["mySharedElements"]);
Here, mySharedElements is another module defined in a separate file. This approach helps to segregate different dependencies used in one controller.
Create a controller if you want to extend your scope or rootscope. If you don't want to extend the scope (create child scopes); you don't need controllers.
Angularjs follows MVC architecture as you know, Our SPA have single/multiple views and each view/feature you could have multiple controllers. Controllers is the file where we are defining the behaviour of the view objects using scope. In the each view/feature you can mention the specific controller.

AngularJs can't access form object in controller ($scope)

I am using bootstrap-ui more specifically modal windows. And I have a form in a modal, what I want is to instantiate form validation object. So basically I am doing this:
<form name="form">
<div class="form-group">
<label for="answer_rows">Answer rows:</label>
<textarea name="answer_rows" ng-model="question.answer_rows"></textarea>
</div>
</form>
<pre>
{{form | json}}
</pre
I can see form object in the html file without no problem, however if I want to access the form validation object from controller. It just outputs me empty object. Here is controller example:
.controller('EditQuestionCtrl', function ($scope, $modalInstance) {
$scope.question = {};
$scope.form = {};
$scope.update = function () {
console.log($scope.form); //empty object
console.log($scope.question); // can see form input
};
});
What might be the reasons that I can't access $scope.form from controller ?
Just for those who are not using $scope, but rather this, in their controller, you'll have to add the controller alias preceding the name of the form. For example:
<div ng-controller="ClientsController as clients">
<form name="clients.something">
</form>
</div>
and then on the controller:
app.controller('ClientsController', function() {
// setting $setPristine()
this.something.$setPristine();
};
Hope it also contributes to the overall set of answers.
The normal way if ng-controller is a parent of the form element:
please remove this line:
$scope.form = {};
If angular sets the form to your controllers $scope you overwrite it with an empty object.
As the OP stated that is not the case here. He is using $modal.open, so the controller is not the parent of the form. I don't know a nice solution. But this problem can be hacked:
<form name="form" ng-init="setFormScope(this)">
...
and in your controller:
$scope.setFormScope= function(scope){
this.formScope = scope;
}
and later in your update function:
$scope.update = function () {
console.log(this.formScope.form);
};
Look at the source code of the 'modal' of angular ui bootstrap, you will see the directive has
transclude: true
This means the modal window will create a new child scope whose parent here is the controller $scope, as the sibling of the directive scope. Then the 'form' can only be access by the newly created child scope.
One solution is define a var in the controller scope like
$scope.forms = {};
Then for the form name, we use something like forms.formName1. This way we could still access it from our controller by just call $scope.forms.formName1.
This works because the inheritance mechanism in JS is prototype chain. When child scope tries to create the forms.formName1, it first tries to find the forms object in its own scope which definitely does not have it since it is created on the fly. Then it will try to find it from the parent(up to the prototype chain) and here since we have it defined in the controller scope, it uses this 'forms' object we created to define the variable formName1. As a result we could still use it in our controller to do our stuff like:
if($scope.forms.formName1.$valid){
//if form is valid
}
More about transclusion, look at the below Misco's video from 45 min. (this is probably the most accurate explanation of what transcluded scopes are that I've ever found !!!)
www.youtube.com/watch?v=WqmeI5fZcho
No need for the ng-init trickery, because the issue is that $scope.form is not set when the controller code is run. Remove the form = {} initialization and get access to the form using a watch:
$scope.$watch('form', function(form) {
...
});
I use the documented approach.
https://docs.angularjs.org/guide/forms
so, user the form name, on "save" click for example just pass the formName as a parameter and hey presto form available in save method (where formScopeObject is greated based upon the ng-models specifications you set in your form OR if you are editing this would be the object storing the item being edited i.e. a user account)
<form name="formExample" novalidate>
<!-- some form stuff here -->
Name
<input type="text" name="aField" ng-model="aField" required="" />
<br /><br />
<input type="button" ng-click="Save(formExample,formScopeObject)" />
</form>
To expand on the answer by user1338062: A solution I have used multiple times to initialize something in my controller but had to wait until it was actually available to use:
var myVarWatch = $scope.$watch("myVar", function(){
if(myVar){
//do whatever init you need to
myVarWatch(); //make sure you call this to remove the watch
}
});
For those using Angular 1.5, my solution was $watching the form on the $postlink stage:
$postLink() {
this.$scope.$watch(() => this.$scope.form.$valid, () => {
});
}

Resources