Unable to bind updated controller as variable to view in AngularJS - javascript

View:
<div ng-controller="MainPageController as mpc">
<h1>{{mpc.city}}</h1>
</div>
Controller:
app.controller('MainPageController',['GetCityService', function(GetCityService){
this.city = null;
this.getCityService = GetCityService;
this.getCityService.getCity().success(function(data){
this.city = data.city
console.log(this.city); // Prints the city correctly.
});
}]);
I am trying to use "controller as" method to access values from controller. My service returns the city name correctly which is assigned to controller variable "this.city". but I am unable to bind the value in the View" (I see nothing displayed in the view)
Please let me know if I'm doing it wrong.
PS: I tried to use $scope, it perfectly works. I am unable to understand why I am unable to bind the controller variable" and If possible suggest when $scope and this variables should be used.
Thanks in advance :)

Since the original question has been answered, I'll try to explain the answer. It's just a matter of understanding 'this' in javascript.
Within your controller function 'this' refers to the controller itself. With in your success callback 'this' no longer refers to the controller (I'm guessing the window if you're not in strict mode?).
By saving 'this' to a variable, such as 'vm', whenever you refer to vm you know that you are referencing the controller, and not whatever 'this' refers to in that context (which can change).
On another note, now that we have all these features in 1.5, it's best to not use $scope anymore and to use 'controllerAs' all the time. I also suggest moving to components instead of using stand-alone controllers.

Related

this vs $scope in angular js [duplicate]

In the "Create Components" section of AngularJS's homepage, there is this example:
controller: function($scope, $element) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
}
this.addPane = function(pane) {
if (panes.length == 0) $scope.select(pane);
panes.push(pane);
}
}
Notice how the select method is added to $scope, but the addPane method is added to this. If I change it to $scope.addPane, the code breaks.
The documentation says that there in fact is a difference, but it doesn't mention what the difference is:
Previous versions of Angular (pre 1.0 RC) allowed you to use this interchangeably with the $scope method, but this is no longer the case. Inside of methods defined on the scope this and $scope are interchangeable (angular sets this to $scope), but not otherwise inside your controller constructor.
How does this and $scope work in AngularJS controllers?
"How does this and $scope work in AngularJS controllers?"
Short answer:
this
When the controller constructor function is called, this is the controller.
When a function defined on a $scope object is called, this is the "scope in effect when the function was called". This may (or may not!) be the $scope that the function is defined on. So, inside the function, this and $scope may not be the same.
$scope
Every controller has an associated $scope object.
A controller (constructor) function is responsible for setting model properties and functions/behaviour on its associated $scope.
Only methods defined on this $scope object (and parent scope objects, if prototypical inheritance is in play) are accessible from the HTML/view. E.g., from ng-click, filters, etc.
Long answer:
A controller function is a JavaScript constructor function. When the constructor function executes (e.g., when a view loads), this (i.e., the "function context") is set to the controller object. So in the "tabs" controller constructor function, when the addPane function is created
this.addPane = function(pane) { ... }
it is created on the controller object, not on $scope. Views cannot see the addPane function -- they only have access to functions defined on $scope. In other words, in the HTML, this won't work:
<a ng-click="addPane(newPane)">won't work</a>
After the "tabs" controller constructor function executes, we have the following:
The dashed black line indicates prototypal inheritance -- an isolate scope prototypically inherits from Scope. (It does not prototypically inherit from the scope in effect where the directive was encountered in the HTML.)
Now, the pane directive's link function wants to communicate with the tabs directive (which really means it needs to affect the tabs isolate $scope in some way). Events could be used, but another mechanism is to have the pane directive require the tabs controller. (There appears to be no mechanism for the pane directive to require the tabs $scope.)
So, this begs the question: if we only have access to the tabs controller, how do we get access to the tabs isolate $scope (which is what we really want)?
Well, the red dotted line is the answer. The addPane() function's "scope" (I'm referring to JavaScript's function scope/closures here) gives the function access to the tabs isolate $scope. I.e., addPane() has access to the "tabs IsolateScope" in the diagram above because of a closure that was created when addPane() was defined. (If we instead defined addPane() on the tabs $scope object, the pane directive would not have access to this function, and hence it would have no way to communicate with the tabs $scope.)
To answer the other part of your question: how does $scope work in controllers?:
Within functions defined on $scope, this is set to "the $scope in effect where/when the function was called". Suppose we have the following HTML:
<div ng-controller="ParentCtrl">
<a ng-click="logThisAndScope()">log "this" and $scope</a> - parent scope
<div ng-controller="ChildCtrl">
<a ng-click="logThisAndScope()">log "this" and $scope</a> - child scope
</div>
</div>
And the ParentCtrl (Solely) has
$scope.logThisAndScope = function() {
console.log(this, $scope)
}
Clicking the first link will show that this and $scope are the same, since "the scope in effect when the function was called" is the scope associated with the ParentCtrl.
Clicking the second link will reveal this and $scope are not the same, since "the scope in effect when the function was called" is the scope associated with the ChildCtrl. So here, this is set to ChildCtrl's $scope. Inside the method, $scope is still the ParentCtrl's $scope.
Fiddle
I try to not use this inside of a function defined on $scope, as it becomes confusing which $scope is being affected, especially considering that ng-repeat, ng-include, ng-switch, and directives can all create their own child scopes.
The reason 'addPane' is assigned to this is because of the <pane> directive.
The pane directive does require: '^tabs', which puts the tabs controller object from a parent directive, into the link function.
addPane is assigned to this so that the pane link function can see it. Then in the pane link function, addPane is just a property of the tabs controller, and it's just tabsControllerObject.addPane. So the pane directive's linking function can access the tabs controller object and therefore access the addPane method.
I hope my explanation is clear enough.. it's kind of hard to explain.
I just read a pretty interesting explanation on the difference between the two, and a growing preference to attach models to the controller and alias the controller to bind models to the view. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ is the article.
NOTE: The original link still exists, but changes in formatting have made it hard to read. It's easier to view in the original.
He doesn't mention it but when defining directives, if you need to share something between multiple directives and don't want a service (there are legitimate cases where services are a hassle) then attach the data to the parent directive's controller.
The $scope service provides plenty of useful things, $watch being the most obvious, but if all you need to bind data to the view, using the plain controller and 'controller as' in the template is fine and arguably preferable.
I recommend you to read the following post:
AngularJS: "Controller as" or "$scope"?
It describes very well the advantages of using "Controller as" to expose variables over "$scope".
I know you asked specifically about methods and not variables, but I think that it's better to stick to one technique and be consistent with it.
So for my opinion, because of the variables issue discussed in the post, it's better to just use the "Controller as" technique and also apply it to the methods.
In this course(https://www.codeschool.com/courses/shaping-up-with-angular-js) they explain how to use "this" and many other stuff.
If you add method to the controller through "this" method, you have to call it in the view with controller's name "dot" your property or method.
For example using your controller in the view you may have code like this:
<div data-ng-controller="YourController as aliasOfYourController">
Your first pane is {{aliasOfYourController.panes[0]}}
</div>
Previous versions of Angular (pre 1.0 RC) allowed you to use this
interchangeably with the $scope method, but this is no longer the
case. Inside of methods defined on the scope this and $scope are
interchangeable (angular sets this to $scope), but not otherwise
inside your controller constructor.
To bring back this behaviour (does anyone know why was it changed?) you can add:
return angular.extend($scope, this);
at the end of your controller function (provided that $scope was injected to this controller function).
This has a nice effect of having access to parent scope via controller object that you can get in child with require: '^myParentDirective'
$scope has a different 'this' then the controller 'this'.Thus if you put a console.log(this) inside controller it gives you a object(controller) and this.addPane() adds addPane Method to the controller Object. But the $scope has different scope and all method in its scope need to be accesed by $scope.methodName().
this.methodName() inside controller means to add methos inside controller object.$scope.functionName() is in HTML and inside
$scope.functionName(){
this.name="Name";
//or
$scope.myname="myname"//are same}
Paste this code in your editor and open console to see...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>this $sope vs controller</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
<script>
var app=angular.module("myApp",[]);
app.controller("ctrlExample",function($scope){
console.log("ctrl 'this'",this);
//this(object) of controller different then $scope
$scope.firstName="Andy";
$scope.lastName="Bot";
this.nickName="ABot";
this.controllerMethod=function(){
console.log("controllerMethod ",this);
}
$scope.show=function(){
console.log("$scope 'this",this);
//this of $scope
$scope.message="Welcome User";
}
});
</script>
</head>
<body ng-app="myApp" >
<div ng-controller="ctrlExample">
Comming From $SCOPE :{{firstName}}
<br><br>
Comming from $SCOPE:{{lastName}}
<br><br>
Should Come From Controller:{{nickName}}
<p>
Blank nickName is because nickName is attached to
'this' of controller.
</p>
<br><br>
<button ng-click="controllerMethod()">Controller Method</button>
<br><br>
<button ng-click="show()">Show</button>
<p>{{message}}</p>
</div>
</body>
</html>

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

get access to variable in directive from controller

I created a directive, I´m using in my template:
<data-input> </data-input>
In my directive (dataInput) I have the template (...data-input.html).
In that template the html-code says:
<input ng-change="dataChanged()" ... > ...
In the JavaScript it says:
scope.dataChanged= function(){ //change the data }
Works perfectly, but know I need to safe the changed data to a variable in my controller (that has a different scope of course).
The template, where I put the <data-input> </data-input> belongs to the final scope, I´m trying to reach.
I tried it via parameter, but it didnt work.
Thanks
Here are your options:
You can nest controllers if possible. This will create scope inheritance and you will be able to share variables of the parent.
You can use $rootscope from both the controllers to share data. Your dataChanged function can save anything to the $rootscope
You can use angular.element to get the scope of any element. angular.element('data-input').scope() returns it's cope.
This is not recommended. But there are circumstances in which people use global space to communicate between angular and non-angular code. But I don't think this is your case.
You can use Angular Watches to see changes is some variable, like this
eg:
$scope.$watch('age + name', function () {
//called when variables 'name' or 'age' changed
//Or you can use just 'age'
});

Using controllerAs syntax in Angular, how can I watch a variable?

With standard controller syntax in AngularJS, you can watch a variable like:
$scope.$watch(somethingToWatch, function() { alert('It changed!'); });
Using the controllerAs syntax, I want to react to this change in an active controller. What's the easiest way to do this?
More detail, if it helps. I have one controller in a side pane that controls the context of the application (user selection, start time, end time, etc.). So, if the user changes to a different context, the main view should react and update. I'm storing the context values in a factory and each controller is injecting that factory.
You can always use a watcher evaluator function, especially helpful to watch something on the controller instance or any object. You can actually return any variable for that matter.
var vm = this;
//Where vm is the cached controller instance.
$scope.$watch(function(){
return vm.propToWatch;
}, function() {
//Do something
}, true);//<-- turn on this if needed for deep watch
And there are also ways to use bound function to bind the this context.
$scope.$watch(angular.bind(this, function(){
return this.propToWatch;
//For a variable just return the variable here
}), listenerFn);
or even ES5 function.bind:
$scope.$watch((function(){
return this.propToWatch;
}).bind(this), listenerFn);
If you are in typescript world it gets more shorter.
$scope.$watch(()=> this.propToWatch, listenerFn);
Eventhough you can watch on the controller alias inside the controller ($scope.watch('ctrlAs.someProp'), it opens up couple of problems:
It predicts (or in other words pre-determines) the alias used for the controller in the view/route/directive/modal or anywhere the controller is used. It destroys the purpose of using controllerAs:'anyVMAlias' which is an important factor in readability too. It is easy to make typo and mistakes and maintenance headache too since using the controller you would need to know what name is defined inside the implementation.
When you unit test the controller (just the controller), you need to again test with the exact same alias defined inside the controller (Which can probably arguably an extra step if you are writing TDD), ideally should not need to when you test a controller.
Using a watcher providing watcher function against string always reduced some steps the angular $parse (which watch uses to create expression) internally takes to convert the string expression to watch function. It can be seen in the switch-case of the $parse implementation

Two way binding of directive scope doesn't behave as expected

In my angularJS app I use a directive. This directive needs to know the value of a variable in the application scope. Because the app scope variable needs to change when the directive updates it and the directive variable needs to change when the app scope variable changes, I used two way binding.
In my directive:
scope: {
"selectedObject": "=selectedobject"
}
In my html:
<dirname selectedobject="foo"/>
and in my controller:
$scope.foo = "somevalue";
//$scope.$apply(); Adding this, I get a '$digest already in progress' error
Now when I try to read the value of the selectedObject in my directive it starts with null instead of "somevalue". However, changes made in the directive scope propagate nicely to the application scope. How can I make sure it works the other way around too? That, if my controller changes the value of foo, this changes propagates to the directive scope?
Please read following article as I believe you fall into common pitfall and this article will help you understand your problem
http://nathanleclaire.com/blog/2014/04/19/5-angularjs-antipatterns-and-pitfalls/

Categories

Resources