Angular.js sanity check: Services vs. Factories vs. Controllers... + Directives + Behavior - angularjs

This article about when to use directives, services and controllers, as awesome and helpful as it is... has me massively confused about everything I think I know about the ideal structure of an angular application and I just need a sanity check:
If you have two inputs:
<label><span>false</span>
<input type='radio' value='false' ng-value='false' ng-model='thing.exists' />
</label>
<label><span>true</span>
<input type='radio' value='true' ng-value='true' ng-model='thing.exists' />
</label>
that are part of a larger form, which will in turn submit to pull in another form... and that information will later be shown for review, is this the correct way to architect that:
TLDR: Flow of execution:
ng-model="thing.exist" ==> thing ==> ThingController ==> a service ==> ...details... ==> getDetails?
Right now I have:
<div ng-controller='ThingController as thing'>
<fieldset>
<label><span>Doesn't exist</span>
<input type='radio' value='false' name='checkExist'
ng-value='false' ng-model='thing.exists' />
</label>
<label><span>Does exist</span>
<input type='radio' value='true' name='checkExist'
ng-value='true' ng-model='thing.exists' />
</label>
</fieldset>
<!-- ... -->
</div>
When the input changes,
I should use ng-change on the inputs to trigger the behavior (like the addition of a directive)... right (via a controller)?
I should use then controller to add the result of the ng-change to a service? Like... passing the model value (thing.exists) to a service so I can use that value later?
As a complication factor- this application uses Require.js to manage dependencies.
(Actually, the article itself isn't the source of my confusion- it's the comments on the article that are killing me.)

That's pretty much it, you've got it right. The idea is the following:
Use directives for managing user interface interactions and -some- state changes
Use controllers for managing a shallow set of logic
Use services for sharing data, functionality and business logic.
Aka, just like on your server - try not to load too much into a controller.

Related

AngularJS: Are there multiple ways to apply ng-model to a form?

I'm using Scala-Play with the Play-Bootstrap extension and AngularJS. Since the controlling of the application is managed by AngularJS I need the form to be submitted and the response managed by AngularJS and not by the Play controller.
As I understand and using pure AngularJS one can use ng-model to link each input to a specific $scope nested variable e.g.
<form name="userForm">
<label>
Name:
<input type="text" name="userName"
ng-model="user.name"/>
</label><br />
<label>
Other data:
<input type="text" ng-model="user.data" />
</label><br />
</form>
is it possible to accomplish the same by using ng-model on the form tag? instead of applying it to each input? the problem is that it is not possible injecting the needed ng-model to each input while using Play-Bootstrap i.e. this doesn't work:
#b3.text(computeInSampleForm("impliedVolSpread"), '_label -> messagesApi("myapp.impliedVolSpread"),
'_showConstraints -> false, 'ng-model -> "impliedVolSpread")
it fails with error value - is not a member of Symbol it would work if I only knew how to escape the - dash character.
Since I already created a customized version of the b3.form as b3.bgform it would be great if I could do bg-model at the form level ... is that possible?
You can fix this error by explicitly converting to a Symbol:
Symbol("ng-model") -> "impliedVolSpread"
Or by using an implicit conversion import:
#import views.html.helper.Implicits._

AutoSave on Angular project with very large forms

I am currently working on a JHipster generated Angular + Spring project.
We have very large forms with dozens of fields each, which are not declared with a markup such as <form>, but enclosed in divs such as this:
<div class="form-group row">
<label class="col-md-2 form-control-label">Search Publication by id: </label>
<div class="col-md-4 ">
<div class="input-group">
<!--<input type="text" class="form-control" placeholder="Search" ng-model="surveyDataId"-->
<!--ww-enter="searchSurveyData()">-->
<input type="text" class="form-control" ng-model="publicationId"
placeholder="Search"
uib-typeahead="publication.id as publication.id + ', ' +publication.title+ ', ' +publication.firstAuthor for publication in publications | filter:$viewValue | limitTo:15"
typeahead-editable="false" ww-enter="searchPublication()">
The js part is all separated in several controllers for each html view
Now, the problem I'm facing with such implementation comes when trying to autosave the form.
I have tried several solutions:
http://adamalbrecht.com/2013/10/30/auto-save-your-model-in-angular-js-with-watch-and-debounce/ -> No good as I can't put watch on to each input field having so many
angularjs autosave form is it the right way? -> No good as the form is made up of several html files, not a single markup with form in the whole project
https://github.com/tiberiuzuld/angular-auto-save-form -> Same issue as before
Sisyphus -> Very unclear documentation, tried implementing by script tags, but no joy
ng-blur and ng-change are not a valid option either for obvious reasons
I would appreciate any recomendations
If you are trying to save together multiple objects across different controllers, then you would be better off having the data saved in a factory, which is then injected into the controllers that need it.
In a factory, you could create a single object which houses the data for each of the html pages or form partials that you have. The factory can also house the functionality to auto-save or otherwise manipulate that data. Each controller would then inject the factory and modify its portion of the data object. From the controller, on-change/debounce/watchers could call functions from the factory and perform any needed data manipulation.

AngularJS ng-required better implement from controller?

I'm thinking of a good way to implement ng-required.
Let's say I have a bunch of inputs with ng-required in my app.
<input type="text" id="one" />
<input type="date" id="two" />
<input type="radio" id="three" />
<input type="checkbox" id="four" />
I would like to do something in a controller, where I could pass an array of required fields. I'm thinking that if I made an array of elements such as:
var myEl = angular.element( document.querySelector( '#some-id' ) );
and some how set the required property that way.
I write a directive which would decide from an array if the field is required, if it does not exist in the array, it's not required if it does, it's required.
Ultimately, I would like to have an array that allows passing of fields in such a way:
var reqArray = ('#id', ('#id1' || 'id2')) etc.
Works the same as conditional logic operators:
#id is required
#id1 || #id2 is required, but not both.
Not sure where to begin, or if it's feasible in Angular.
This would make it easier to modify required fields in large applications without having to modify the actual html.
It can be done, but angular already provides its own ways to validate forms. Some brief details:
The form tag must have a novalidate attribute in order to prevent HTML5 validation. <form name="myForm" novalidate>
With that, now angular can take charge of the validation by adding a "required" attribute to your inputs <input ng-model="myModel" type="text" required>
By this point, angular has taken control of your validation, it can also validate other HTML5 attributes like pattern. <input pattern="[0-9][A-Z]{3}" type="text" title="Single digit followed by three uppercase letters."/>
I suggest you take look at this official guide (also take a look at the directives guide on that same site, I wanted to link it but I don't yet have the rep).
That being said, what you are trying to accomplish is also possible, but rather redundant since you would have to add an attribute to your inputs anyway (the directive, and angular is able to validate already) and also require ngModel in the directive.
I made this plunkr to show you how to do it, but take notice of the extra work needed in order to validate something that angular already does natively, I'd leave this kind of work for expanding on validations, like comparing items or models.
Also, querying a selector directly like in your suggestion is not considered "the angular way". A better way would be to add the directive to your form element and access the element through the 'element' parameter in the directive.
Hope this helps.

One form element updating multiple pieces of the model?

I am looking at using angular for a project at work but I have a question first. We have a single page application that's pretty intricate. We do have a basic model set up but some fields in the model are redundant. If I couldn't reduce the redundancy, what steps could I take in angular so that one form element changes two variables in the model?
I've put together a bare bones jsfiddle of what I'm hoping to do:
<div ng-app>
<input type="text" ng-model="yourName" placeholder="Enter a name here" /><br/>
<span>Hello {{yourName}}!</span><br/>
<span>Hello {{altName}}!</span>
</div>
How could I change this around so that the input would assign it's value to both yourName as well as altName? I've tried what I thought would be obvious such as comma or pipe delimiting the ng-model attribute to no avail. Is it possible?
You could set a $watch on the yourname-Variable within your controller and then change the altName in its callback. Should look like this:
$scope.$watch('yourName', function(){
$scope.altName = $scope.newName;
});

angularjs make div readonly - where to put business logic

I want to make a section readonly based on a checkbox. Typical example is primary address and the billing address. The user enters primary address and can chose the billing address to be the same the primary address.
See fiddle for example.
HTML (primary address):
<fieldset ng-model="primaryAdd">
<legend>Primary Address:</legend>
House#: <input type="text" ng-model="primaryAdd.houseNum"><br>
Street: <input type="text" ng-model="primaryAdd.street"><br>
State: <input type="text" ng-model="primaryAdd.state"><br>
Zip: <input type="text" ng-model="primaryAdd.zip">
</fieldset>
HTML (Same as address)
<input type="checkbox" name="makeSameAsAddress" ng-model="makeSameAsAddressCheck" ng-click="makeSameAsAddress($event)">Same as above<br/>
HTML (Billing Address)
<fieldset ng-model="billingAddress">
<legend>Billing Address:</legend>
<div>
<div style="font-size:small">if the address is the same as residence, prefill and make it readonly</div>
House#: <input type="text" ng-model="billingAddress.houseNum" ng-readonly="makeSameAsAddressCheck"><br>
Street: <input type="text" ng-model="billingAddress.street" ng-readonly="makeSameAsAddressCheck"><br>
State: <input type="text" ng-model="billingAddress.state" ng-readonly="makeSameAsAddressCheck"><br>
Zip: <input type="text" ng-model="billingAddress.zip" ng-readonly="makeSameAsAddressCheck">
</div>
</fieldset>
The fiddle does work. When I click on "Same as above" checkbox, the billing section gets the address from the primary section and is readonly. The question is why I cannot put the ng-readonly on the fieldset or the div surrounding the inputs. I have to put the ng-readonly on all the inputs. I feel like there must be a better way to do this.
Maybe I need to create a directive called "makeSameAsPrimaryAndReadOnly". I read that business logic should sit in directives and not HTML directly or the controllers. But I don't see the point of doing this. Unless I was reusing the billing address in multiple places.
Secondly, where is a good place to initialize the model and have a schema to share with other controllers etc? I imagine a factory would be best for this? Thank you.
Angular rocks !!
You could create a directive, but it might be overkill for this purpose.
You can't apply the ng-readonly to the div or the fieldset like it were a css style. Honestly, I don't think it's too bad the way it is if you only have 4 inputs.
You can simplify the logic for the checkbox (and eliminate the ng-click event) by using a watch expression:
$scope.$watch('makeSameAsAddressCheck', function (newv, oldv) {
if (newv)
$scope.billingAddress = $scope.primaryAdd;
else if (newv !== oldv)
$scope.billingAddress = angular.copy($scope.primaryAdd);
});
Notice that when you turn the checkbox on, I set both scope variables to reference the same object. Now when you make modifications to the primary address, it's automatically updated in the billing address.
When you turn the checkbox off, I make a copy of the primary address. They can now be edited independently.
http://jsfiddle.net/fcSgz/
Business login in controllers is fine. In fact, that's where it should be.
You can initialize your model in a service, which is created with the "factory" function.

Resources