AngularJS - How do I split my controller? - angularjs

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.

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;
}
}
});

What sense does have a new child scope inaccessible from parent controller? (created by ng- directives)

In angular.js Some directives create child scopes. (ng-include, ng-if, etc)
I know there are ways to solve it, for example by declaring the variable in the scope of the controller. Just uncomment //$scope.inner = '1234' and removeng-init="inner='1234'and will work.
Another solution would be to use a object in the parent scope containing the variable.
Still does not make sense to me.
What sense does have a scope without a controller?
What practical use have these new child scope?
This is my example.
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope) {
$scope.result = "Result";
$scope.outer = "outer";
//$scope.inner = "1234";
$scope.test1 = function() {
if ($scope.inner) {
$scope.result = $scope.inner;
} else {
alert("inner is not accesible");
}
}
$scope.test2 = function() {
if ($scope.outer) {
$scope.result = $scope.outer;
} else {
alert("inner2 is not accesible");
}
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl" >
<script type="text/ng-template" id="/tpl.html">
<input type="text" ng-init="inner='Inner'" ng-model="inner"></input>
<button ng-click="test1()">TEST1</button>
</script>
<div>
<ng-include src="'/tpl.html'"></ng-include>
<br/>
<input type="text" ng-model="outer"></input>
<button ng-click="test2()">TEST2</button>
<p>{{result}}</p>
</div>
</div>
First you need to understand that scopes and controllers are two separate concepts.
The scope is an object that refers to your application model while a controller is a constructor function that you use to manipulate the scope.
So, "from an Angular's point of view", it's perfectly acceptable to have a scope that is not augmented by a controller.
The idea for creating new child scopes is to have a logical way to separate the application's model. Could you imagine having only one scope for your entire application? You would have to be very careful not to override functions or properties while manipulating the scope in your controllers. Since child scopes prototypically inherit from their parent scope you don't have to worry about that.
One practical example of the usability of these child scopes is, for example, when you have two ng-repeat directives side-by-side, "under" the same scope. If they didn't create their own child scopes, how would you have access to the $index, $first, $last, etc... properties from each of the ng-repeat directives? Without child scopes both would be polluting the "parent" scope with the same properties, overriding each other.
You can read more information on scopes here and on controllers here.
Specifically for ngInclude this is by design: In many cases you want the included content to be isolated.
A scope really does make little sense if there is no js code that works with it, but that code may be in a controller or link function or (as in the case with ngInclude) a postLink function.
Also see How to include one partials into other without creating a new scope? which is almost a duplicate and has a workaround.

Add Listener For Function in Different Controller - Angular JS

I have a page that uses two controllers. One is associated with the header (which is reused on many different pages) and the other is associated with the body of the page. The mark-up looks something like:
<div id = 'header' ng-controller = 'headerController' >
<div id = 'login-button' ng-click = 'login()'>
Login
</div>
</div>
<div id = 'body' ng-controller = 'myPageController'>
....
</div>
The controllers are defined in two separate JS files respectively:
headerController.js
myPageController.js
I would like to bind a listener to the login() function so that whenever the button associated with it is clicked, another function is called in the myPageController.js file.
How would I go about doing this in Angular?
Probably the best way is to $broadcast() an event from the $rootScope that your page controller handles using $scope.$on().
Example:
headerController.js:
function headerController($scope, $rootScope) {
$scope.login = function () {
$rootScope.$broadcast('login');
};
}
myPageController.js
function myPageController($scope) {
$scope.$on('login', function () {
// handle login event here
});
}
$rootScope.$broadcast() is fairly slow. If you just want two unrelated controllers to communicate, this is best done through a service. For example, see my answer at:
Communicating between a Multiple Controllers and a directive
Hope that helps!

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, () => {
});
}

Saving new models using AngularJS and $resource

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

Resources