Using ng-change in AngularJS with "Controller As" syntax - javascript

I am trying to avoid using $scope in my controller function, instead opting to use
var viewModel = this;
with "controller as" viewModel syntax.
My problem is that I need to use ng-change to call a function in my controller but while I am able to access data from a service, I am unable to call functions.
//Controller
(function () {
'use strict';
angular
.module('app')
.controller('GeneralSettingsController', GeneralSettingsController);
GeneralSettingsController.$inject = ['SimulationService'];
function GeneralSettingsController(SimulationService) {
var viewModel = this;
viewModel.SimulationService = SimulationService;
viewModel.setSimulationPeriod = setSimulationPeriod;
function setSimulationPeriod() {
console.log("Entered local setSimulationPeriod");
viewModel.SimulationService.setSimulationPeriod();
}
}
})();
The controller is being instantiated in a directive that defines the controller and controllerAs: 'viewModel'
My html looks like this:
<div class="col-xs-2">
<input type="text" class="form-control" id="startyear" name="startyear" placeholder="start year"
autocomplete="off" value="2017" maxlength="4"
ng-model="viewModel.SimulationService.data.simulationPeriodStart" ng-change="viewModel.setSimulationPeriod">
</div>
I was able to call things fine when I used $scope instead of referencing the controller however I feel this is not ideal. I was hoping there is a way of calling a function with ng-change that still uses viewModel.

angular expressions
You're not calling the function. Instead try:
<input ng-change="viewModel.setSimulationPeriod()">
Note the () at the end of your function. Ng-change, like most other angular directives use expressions, meaning they're actually trying to execute a subset of JavaScript. In this case when you just passed a reference to your vm's function, it simply evaluated it rather than executing it.
order of assignment
Also, you've defined the viewModel function before you've defined the function you're setting it to. Move the function declaration above the part of your code where you assign it to your viewModel.
instead of this
viewModel.setSimulationPeriod = setSimulationPeriod;
function setSimulationPeriod() {
console.log("Entered local setSimulationPeriod");
viewModel.SimulationService.setSimulationPeriod();
}
reorder it like this
function setSimulationPeriod() {
console.log("Entered local setSimulationPeriod");
viewModel.SimulationService.setSimulationPeriod();
}
viewModel.setSimulationPeriod = setSimulationPeriod;

jusopi was right. My controller was not wired up correctly. The problem was that I had another controller active at a higher scope which was also set to controllerAs: viewModel. This caused me to reference the wrong controller where the function did not exist. Once I gave this controller a unique name everything went smoothly which is why it worked for $scope.

Related

Change parent controller model through directive $watch using controllerAs syntax

I'm new to controllerAs syntax of angular and just trying to understand how it works with directive. I've created one directive for password validation. I want to make some flag true based on conditions and those will be used in parent template for displaying error messages. I'm not getting how can I achieve this!
JSFiddle
VIEW
<div ng-app="myapp">
<fieldset ng-controller="PersonCtrl as person">
<input name="emailID" type="text" ng-model="person.first" >
<input name="pass" type="password" ng-model="person.pass" password-validator>
<p ng-show="person.showMsg">Password validation message here.</p>
</fieldset>
</div>
Directive
myapp.directive('passwordValidator',function() {
return {
controller : PasswordCtrl,
controllerAs : 'dvm',
bindToController : true,
require : ['ngModel','passwordValidator'],
link : function(scope,ele,attrs,ctrls) {
var person = ctrls[1];
var ngModelCtrl = ctrls[0];
scope.$watch(function() {
return ngModelCtrl.$modelValue;
},function(newVal) {
if(newVal!='') {
person.showMsg = true;
} else {
person.showMsg = false;
}
console.log(person.showMsg);
});
}
}
function PasswordCtrl() {
}
});
Specially I want to understand why and how below watch is working fine!
// Why this below is also working, can anyone explain what's going behind!!
scope.$watch('person.pass',function(newVal) {
console.log("Watch fires");
});
This is just for learning purpose so please explain how controllerAs and bindToController works!
I know this was not part of your question, I will get to it, but using directive 'ng-controller' is an anti-pattern. If if are interested why I can explain in a separate post but in short it makes code much harder to follow.
Now, to get to the heart of your question.
From reading the Angular documentation for bindToController it would appear that if you are not also creating an isolated scope, i.e. scope: true or scope: {} it does not do anything.
Personally I have never used it before and does not seem particularly useful.
Using ng-controller is in essence adding a property to the current scope with that controller object.
So:
<fieldset ng-controller="PersonCtrl as person">
Is effectively saying, (in a contrived way):
$scope.person = new PersonCtrl();
Your directive passwordValidator which is using the controllerAs syntax within it is basically doing:
$scope.dvm= new PasswordCtrl();
In this case you effectively have a scope object that looks like:
$scope = {
person = new PersonCtrl(),
dvm: new PasswordCtrl()
}
Your person controller and dvm controller are sibling objects.
Within your passwordValidator directive you are requiring in its controller, which is the dvm object. Using that dvm object are you setting person.showMsg which is the same as doing:
$scope.dvm.person.showMsg = <value>
The dvm object does not have a way to access the person object, as they are siblings, on the $scope. So you need to use the $scope itself to access the person object. You would need to do:
$scope.person.showMsg = <value>
Although this assumes that person exists on the scope, which is a dangerous assumption.
your example is kinda messy but ill try to answer your questions.
// Why this below is also working, can anyone explain what's going behind!!
scope.$watch('person.pass',function(newVal) {
console.log("Watch fires");
});
this works because your directive is using SAME scope and variables on it as its parent controller.
this.checkDirCtrl = function() {
console.log($scope.dvm);
}
this is undefined because you are using controllerAs on SAME scope so new variable called dvm is not created and all your dvm controller variables are initialized on parent scope.
That means that you dont need to 'inject' person controller into directive because you already have controller instance on directive scope. So just set your variable 'showMsg' like this and it will work like a magic!
if(newVal!='') {
scope.person.showMsg = true;
} else {
scope.person.showMsg = false;
}
console.log(scope.person.showMsg);
I made a fiddle for you: JSFiddle

Is the $scope neccessary always for a method definition in Angular

It is clear that a method should be set to scope in order to be visible or available for the view (in html) or in a directive or in any other place where the method should be accessed, so that the method can be accessed through the $scope. My question is to know whether $scope is always necessary or a good practice to use when a method is defined. For instance, following are different method declarations:
Scenario 1. $scope.myMethod = function(){};
Scenario 2. var myMethod= function(){};
if 'myMethod' is only used in one controller, is it required to set it to the $scope? What are the advantages or why scenario 1 or 2 is good ?
What if someone has declared it as $scope.myMethod = function(){} ? is it not good or an unnecessary load to the $scope ? What can be the best practice?
NB: I don't need to make any poll here, please let me know any pros and cons
You mainly use the first scenario for things like binding click events. If you will only call the myMethod you don't really need to define it in scope.
For example following will need the first definition:
<button ng-click="myMethod()">My Button</button>
But following can use the second:
angular.module('myCtrl', [])
.controller('myController', function($scope) {
var myMethod = function (text) {alert(text)};
$scope.mySecondMethod = function () { myMethod('second'); }
$scope.myThirdMethod = function () { myMethod('third'); }
In second case you can use mySecondMethod and myThirdMethod in event binding.
Scenario 1. $scope.myMethod = function(){};
Scenario 2. var myMethod= function(){};
Scope is the glue between the controller and the view. If you really
need any variable and methods for the current view, then this should
be added to the scope variable. Please see the controller as property
if you don't want to add the methods to scope.
Scenario 1
If you declare a method using the scope, then this will be available/accessed from the view.
Scenario 2
If you really don't need this method to be accessed from the view, then you can remove this method from the scope.
You need $scope to define a method only if you are calling that function from your html code.
You can use this or some_names also instead of $scope.
$scope is just to mean that the function's (mehtod's) scope is inside that controller function and can be accessible from the html code that written inside the controller
If the function is calling inside the javascript (controller) only, then use normal definition
function myMethod(){}
OR
var myMethod = function(){}
Declaring a method or variable as $scope variable is only for accessing from DOM. If you are creating a new variable in $scope. It just adding that variable to the clousure of $scope as $scope: {first, seccond, third}
When ever you are calling a $scope function that just returns from the closure. There is not much load to the $scope I guess
Scope is the glue between application controller and the view. During
the template linking phase the directives set up $watch expressions on
the scope. The $watch allows the directives to be notified of property
changes, which allows the directive to render the updated value to the
DOM.
Both controllers and directives have reference to the scope, but not
to each other. This arrangement isolates the controller from the
directive as well as from the DOM. This is an important point since it
makes the controllers view agnostic, which greatly improves the
testing story of the applications.
From the documentation

Global function not defined in Angular controller code

I have a global function declared as follows (only necessary bits):
initiateCheckList = {};
$(function() {
initiateCheckList = function() {
...
}
And then I have a function inside an Angular controller that tries to call that function, but I get the error initiateCheckList is not defined when the following function is called:
$scope.updateSuburbs = function () {
$scope.suburbs.getModel($scope.areas.areaId);
initiateCheckList();
};
This function is nested inside a controller, and is bound to a dropdown change event like so:
<select class="form-control" ng-model="areas.areaId" ng-change="updateSuburbs()">
What is Angular doing so that I can't call a global function, and how can I fix things so I can call it?
Below is the ideal way of defining global properties/function in angular:
You've got basically 2 options for "global" variables:
use a $rootScope [http://docs.angularjs.org/api/ng.$rootScope][1]
use a service [http://docs.angularjs.org/guide/services][1]
$rootScope is a parent of all scopes so values exposed there will be visible in all templates and controllers. Using the $rootScope is very easy as you can simply inject it into any controller and change values in this scope. It might be convenient but has all the problems of global variables.
Services are singletons that you can inject to any controller and expose their values in a controller's scope. Services, being singletons are still 'global' but you've got far better control over where those are used and exposed.
Using services is a bit more complex, but not that much, here is an example:
var myApp = angular.module('myApp',[]);
myApp.factory('UserService', function() {
return {
name : 'anonymous',
printData: function() {console.log('Print Data');}
};
});
and then in a controller:
function MyCtrl($scope, UserService) {
$scope.name = UserService.name;
UserService.printData();
}
I hope it would help you to understand adding global properties/functions in angular.

AngularJs "controller as" syntax - clarification?

I read about the new syntax from angularJS regarding controller as xxx
The syntax InvoiceController as invoice tells Angular to instantiate
the controller and save it in the variable invoice in the current
scope.
Visualization :
Ok , so I wont have the parameter $scope in my controller and the code will be much cleaner in the controller.
But
I will have to specify another alias in the view
So Until now I could do :
<input type="number" ng-model="qty" />
....controller('InvoiceController', function($scope) {
// do something with $scope.qty <--notice
And now I can do :
<input type="number" ng-model="invoic.qty" /> <-- notice
....controller('InvoiceController', function() {
// do something with this.qty <--notice
Question
What is the goal of doing it ? removing from one place and add to another place ?
I will be glad to see what am I missing.
There are several things about it.
Some people don't like the $scope syntax (don't ask me why). They say that they could just use this. That was one of the goals.
Making it clear where a property comes from is really useful too.
You can nest controllers and when reading the html it is pretty clear where every property comes.
You can also avoid some of the dot rule problems.
For example, having two controllers, both with the same name 'name', You can do this:
<body ng-controller="ParentCtrl">
<input ng-model="name" /> {{name}}
<div ng-controller="ChildCtrl">
<input ng-model="name" /> {{name}} - {{$parent.name}}
</div>
</body>
You can modify both parent and child, no problem about that. But you need to use $parent to see the parent's name, because you shadowed it in your child controller. In massive html code $parent could be problematic, you don't know where that name comes from.
With controller as you can do:
<body ng-controller="ParentCtrl as parent">
<input ng-model="parent.name" /> {{parent.name}}
<div ng-controller="ChildCtrl as child">
<input ng-model="child.name" /> {{child.name}} - {{parent.name}}
</div>
</body>
Same example, but it is much much clearer to read.
$scope plunker
controller as plunker
The main advantage with controller as syntax I see is that you can work with controllers as classes, not just some $scope-decorating functions, and take advantage of inheritence. I often run into a situation when there's a functionality which is very similar to a number of controllers, and the most obvious thing to do is to create a BaseController class and inherit from it.
Even though there's is $scope inheritence, which partially solves this problem, some folks prefer to write code in a more OOP manner, which in my opinion, makes the code easier to reason about and test.
Here's a fiddle to demonstrate: http://jsfiddle.net/HB7LU/5796/
I believe one particular advantage is clear when you have nested scopes. It will now be completely clear exactly what scope a property reference comes from.
Source
Difference between Creating a controller using the $scope object and Using the “controller as” syntax and vm
Creating a controller using the $scope object
Usually we create a controller using the $scope object as shown in the listing below:
myApp.controller("AddController", function ($scope) {
$scope.number1;
$scope.number2;
$scope.result;
$scope.add = function () {
$scope.result = $scope.number1 + $scope.number2;
}
});
Above we are creating the AddController with three variables and one behaviour, using the $scope object controller and view, which talk to each other. The $scope object is used to pass data and behaviour to the view. It glues the view and controller together.
Essentially the $scope object performs the following tasks:
Pass data from the controller to the view
Pass behaviour from the controller to the view
Glues the controller and view together
The $scope object gets modified when a view changes and a view gets modified when the properties of the $scope object change
We attach properties to a $scope object to pass data and behaviour to the view. Before using the $scope object in the controller, we need to pass it in the controller function as dependencies.
Using the “controller as” syntax and vm
We can rewrite the above controller using the controller as syntax and the vm variable as shown in the listing below:
myApp.controller("AddVMController", function () {
var vm = this;
vm.number1 = undefined;
vm.number2=undefined;
vm.result =undefined;
vm.add = function () {
vm.result = vm.number1 + vm.number2;
}
});
Essentially we are assigning this to a variable vm and then attaching a property and behaviour to that. On the view we can access the AddVmController using controller as syntax. This is shown in the listing below:
<div ng-controller="AddVMController as vm">
<input ng-model="vm.number1" type="number" />
<input ng-model="vm.number2" type="number" />
<button class="btn btn-default" ng-click="vm.add()">Add</button>
<h3>{{vm.result}}</h3>
</div>
Ofcourse we can use another name than “vm” in the controller as syntax. Under the hood, AngularJS creates the $scope object and attaches the properties and behaviour. However by using the controller as syntax, the code is very clean at the controller and only the alias name is visible on the view.
Here are some steps to use the controller as syntax:
Create a controller without $scope object.
Assign this to a local variable. I preferred variable name as vm, you can choose any name of your choice.
Attach data and behaviour to the vm variable.
On the view, give an alias to the controller using the controller as syntax.
You can give any name to the alias. I prefer to use vm unless I’m not working with nested controllers.
In creating the controller, there are no direct advantages or disadvantages of using the $scope object approach or the controller as syntax. It is purely a matter of choice, however, using the controller as syntax makes the controller’s JavaScript code more readable and prevents any issues related to this context.
Nested controllers in $scope object approach
We have two controllers as shown in the listing below:
myApp.controller("ParentController", function ($scope) {
$scope.name = "DJ";
$scope.age = 32;
});
myApp.controller("ChildController", function ($scope) {
$scope.age = 22;
$scope.country = "India";
});
The property “age” is inside both controllers, and on the view these two controllers can be nested as shown in the listing below:
<div ng-controller="ParentController">
<h2>Name :{{name}} </h2>
<h3>Age:{{age}}</h3>
<div ng-controller="ChildController">
<h2>Parent Name :{{name}} </h2>
<h3>Parent Age:{{$parent.age}}</h3>
<h3>Child Age:{{age}}</h3>
<h3>Country:{{country}}</h3>
</div>
</div>
As you see, to access the age property of the parent controller we are using the $parent.age. Context separation is not very clear here. But using the controller as syntax, we can work with nested controllers in a more elegant way. Let’s say we have controllers as shown in the listing below:
myApp.controller("ParentVMController", function () {
var vm = this;
vm.name = "DJ";
vm.age = 32;
});
myApp.controller("ChildVMController", function () {
var vm = this;
vm.age = 22;
vm.country = "India";
});
On the view these two controllers can be nested as shown in the listing below:
<div ng-controller="ParentVMController as parent">
<h2>Name :{{parent.name}} </h2>
<h3>Age:{{parent.age}}</h3>
<div ng-controller="ChildVMController as child">
<h2>Parent Name :{{parent.name}} </h2>
<h3>Parent Age:{{parent.age}}</h3>
<h3>Child Age:{{child.age}}</h3>
<h3>Country:{{child.country}}</h3>
</div>
</div>
In the controller as syntax, we have more readable code and the parent property can be accessed using the alias name of the parent controller instead of using the $parent syntax.
I will conclude this post by saying that it’s purely your choice whether you want to use the controller as syntax or the $scope object. There is no huge advantage or disadvantage to either, simply that the controller as syntax you have control on the context is a bit easier to work with, given the clear separation in the nested controllers on the view.
I find the main advantage is a more intuitive api since the methods/properties are associated with the controller instance directly and not the scope object. Basically, with the old approach, the controller becomes just a decorate for building up the scope object.
Here are some more info on this: http://www.syntaxsuccess.com/viewarticle/551798f20c5f3f3c0ffcc9ff
From what I've read, $scope will be removed in Angular 2.0, or at least how we view the use of $scope. It might be good to start using controller as as the release of 2.0 nears.
Video link here for more discussion on it.

AngularJS: dynamically assign controller from ng-repeat

I'm trying to dynamically assign a controller for included template like so:
<section ng-repeat="panel in panels">
<div ng-include="'path/to/file.html'" ng-controller="{{panel}}"></div>
</section>
But Angular complains that {{panel}} is undefined.
I'm guessing that {{panel}} isn't defined yet (because I can echo out {{panel}} inside the template).
I've seen plenty of examples of people setting ng-controller equal to a variable like so: ng-controller="template.ctrlr". But, without creating a duplicate concurrant loop, I can't figure out how to have the value of {{panel}} available when ng-controller needs it.
P.S. I also tried setting ng-controller="{{panel}}" in my template (thinking it must have resolved by then), but no dice.
Your problem is that ng-controller should point to controller itself, not just string with controller's name.
So you might want to define $scope.sidepanels as array with pointers to controllers, something like this, maybe:
$scope.sidepanels = [Alerts, Subscriptions];
Here is the working example on js fiddle http://jsfiddle.net/ADukg/1559/
However, i find very weird all this situation when you might want to set up controllers in ngRepeat.
To dynamically set a controller in a template, it helps to have a reference to the constructor function associated to a controller. The constructor function for a controller is the function you pass in to the controller() method of Angular's module API.
Having this helps because if the string passed to the ngController directive is not the name of a registered controller, then ngController treats the string as an expression to be evaluated on the current scope. This scope expression needs to evaluate to a controller constructor.
For example, say Angular encounters the following in a template:
ng-controller="myController"
If no controller with the name myController is registered, then Angular will look at $scope.myController in the current containing controller. If this key exists in the scope and the corresponding value is a controller constructor, then the controller will be used.
This is mentioned in the ngController documentation in its description of the parameter value: "Name of a globally accessible constructor function or an expression that on the current scope evaluates to a constructor function." Code comments in the Angular source code spell this out in more detail here in src/ng/controller.js.
By default, Angular does not make it easy to access the constructor associated to a controller. This is because when you register a controller using the controller() method of Angular's module API, it hides the constructor you pass it in a private variable. You can see this here in the $ControllerProvider source code. (The controllers variable in this code is a variable private to $ControllerProvider.)
My solution to this issue is to create a generic helper service called registerController for registering controllers. This service exposes both the controller and the controller constructor when registering a controller. This allows the controller to be used both in the normal fashion and dynamically.
Here is code I wrote for a registerController service that does this:
var appServices = angular.module('app.services', []);
// Define a registerController service that creates a new controller
// in the usual way. In addition, the service registers the
// controller's constructor as a service. This allows the controller
// to be set dynamically within a template.
appServices.config(['$controllerProvider', '$injector', '$provide',
function ($controllerProvider, $injector, $provide) {
$provide.factory('registerController',
function registerControllerFactory() {
// Params:
// constructor: controller constructor function, optionally
// in the annotated array form.
return function registerController(name, constructor) {
// Register the controller constructor as a service.
$provide.factory(name + 'Factory', function () {
return constructor;
});
// Register the controller itself.
$controllerProvider.register(name, constructor);
};
});
}]);
Here is an example of using the service to register a controller:
appServices.run(['registerController',
function (registerController) {
registerController('testCtrl', ['$scope',
function testCtrl($scope) {
$scope.foo = 'bar';
}]);
}]);
The code above registers the controller under the name testCtrl, and it also exposes the controller's constructor as a service called testCtrlFactory.
Now you can use the controller in a template either in the usual fashion--
ng-controller="testCtrl"
or dynamically--
ng-controller="templateController"
For the latter to work, you must have the following in your current scope:
$scope.templateController = testCtrlFactory
I believe you're having this problem because you're defining your controllers like this (just like I'm used to do):
app.controller('ControllerX', function() {
// your controller implementation
});
If that's the case, you cannot simply use references to ControllerX because the controller implementation (or 'Class', if you want to call it that) is not on the global scope (instead it is stored on the application $controllerProvider).
I would suggest you to use templates instead of dynamically assign controller references (or even manually create them).
Controllers
var app = angular.module('app', []);
app.controller('Ctrl', function($scope, $controller) {
$scope.panels = [{template: 'panel1.html'}, {template: 'panel2.html'}];
});
app.controller("Panel1Ctrl", function($scope) {
$scope.id = 1;
});
app.controller("Panel2Ctrl", function($scope) {
$scope.id = 2;
});
Templates (mocks)
<!-- panel1.html -->
<script type="text/ng-template" id="panel1.html">
<div ng-controller="Panel1Ctrl">
Content of panel {{id}}
</div>
</script>
<!-- panel2.html -->
<script type="text/ng-template" id="panel2.html">
<div ng-controller="Panel2Ctrl">
Content of panel {{id}}
</div>
</script>
View
<div ng-controller="Ctrl">
<div ng-repeat="panel in panels">
<div ng-include src="panel.template"></div>
</div>
</div>
jsFiddle: http://jsfiddle.net/Xn4H8/
Another way is to not use ng-repeat, but a directive to compile them into existence.
HTML
<mysections></mysections>
Directive
angular.module('app.directives', [])
.directive('mysections', ['$compile', function(compile){
return {
restrict: 'E',
link: function(scope, element, attrs) {
for(var i=0; i<panels.length; i++) {
var template = '<section><div ng-include="path/to/file.html" ng-controller="'+panels[i]+'"></div></section>';
var cTemplate = compile(template)(scope);
element.append(cTemplate);
}
}
}
}]);
Ok I think the simplest solution here is to define the controller explicitly on the template of your file. Let's say u have an array:
$scope.widgets = [
{templateUrl: 'templates/widgets/aWidget.html'},
{templateUrl: 'templates/widgets/bWidget.html'},
];
Then on your html file:
<div ng-repeat="widget in widgets">
<div ng-include="widget.templateUrl"></div>
</div>
And the solution aWidget.html:
<div ng-controller="aWidgetCtrl">
aWidget
</div>
bWidget.html:
<div ng-controller="bWidgetCtrl">
bWidget
</div>
Simple as that! You just define the controller name in your template. Since you define the controllers as bmleite said:
app.controller('ControllerX', function() {
// your controller implementation
});
then this is the best workaround I could come up with. The only issue here is if u have like 50 controllers, u'll have to define them explicitly on each template, but I guess u had to do this anyway since you have an ng-repeat with controller set by hand.

Categories

Resources