I am working on an AngularJS tutorial, and I see the following code:
.state('index',{
url:"/",
templateUrl:"views/index.html",
controller:"IndexCtrl",
controllerAs:"index"
})
What is the reason someone would want to use the controllerAs property?
Few things:
1. Reduce scope usage
Instead of loading every data in the scope of a controller, you could simply use this to load up everything that you require.
eg:
Route
state('index',{
url:"/",
templateUrl:"views/index.html",
controller:"ListCtrl",
controllerAs:"list"
})
In Controller
angular.module('feed').controller('ListCtrl', function($scope, reddit){
var vm = this;
vm.names = ["Michael", "Roy"];
});
In Template:
<ul>
<li ng-repeat="name in list.names">
<div>{{name}}</div>
</li>
</ul>
2. Correct scope usage
When multiple controllers come into play, scope becomes a tricky thing. Using controllerAs method will go a long way is resolving this. An example is shown below:
Wrong:
<span>Outside Controller: Your name is: {{username}}</span>
<div ng-controller="SignupController">
<span>Inside Controller: Your name is: {{username}}</span>
<fieldset legend="User details">
<input ng-model="username">
</fieldset>
</div>
Correct:
<span>Outside Controller: Your name is: {{user.name}}</span>
<div ng-controller="SignupController">
<span>Inside Controller: Your name is: {{user.name}}</span>
<fieldset legend="User details">
<input ng-model="user.name">
</fieldset>
</div>
Found an image which makes everthing more clear:
Courtesy : AngularJs "controller as" syntax - clarification?
Yes. A little more info:
Before the controllerAs syntax, methods and properties needed to be exposed to views by binding them to the $scope. With controllerAs, your controller instance is bound to the $scope as the property you select.
This way you can use Plain Old JavaScript Classes for your controllers.
Editorial: This is a much cleaner approach to development. One of the things that makes Angular so easy to write tests for is that your Controllers and components do not need to inherit from framework base-classes. See Backbone and Ember.
So with the old style your controllers would look like (in ES6 for simplicity):
YourController.$inject = ['$scope'];
class YourController {
constructor($scope) {
$scope.myMethod = () => { . . . };
$scope.myProperty = true;
}
}
With the controllerAs
class YourController {
constructor() {
this.myProperty = true;
}
myMethod() { . . . };
}
Just a plain old class rather than decorating or monkeypatching the $scope.
maybe you didn't googled enough!
http://toddmotto.com/digging-into-angulars-controller-as-syntax/
As the name "controllerAs" tell us, is an alias for controller them you can access your controller with that alias.
You can set your controller in many ways like :
$stateProvider.state('contacts', {
template: '<h1>{{title}}</h1>',
controller: function($scope){
$scope.title = 'My Contacts';
}
})
Or if you already have a controller defined on the module, like this:
$stateProvider.state('contacts', {
template: ...,
controller: 'ContactsCtrl'
})
Alternatively using the controllerAs syntax the above become:
$stateProvider.state('contacts', {
template: '<h1>{{contact.title}}</h1>',
controller: function(){
this.title = 'My Contacts';
},
controllerAs: 'contact'
})
and
$stateProvider.state('contacts', {
template: ...,
controller: 'ContactsCtrl as contact'
})
Or for more advanced needs you can use the controllerProvider to dynamically return a controller function or string for you:
$stateProvider.state('contacts', {
template: ...,
controllerProvider: function($stateParams) {
var ctrlName = $stateParams.type + "Controller";
return ctrlName;
}
})
Source : https://github.com/angular-ui/ui-router/wiki#controllers
Simply, Controller as syntax helps when we are working with nested controllers. The named scopes are clearly defined so there won’t be conflicts between controllers since you must state which controller you’re referencing before the dot.
<div ng-controller="Shell as shellVm">
<h1>{{shellVm.title}}</h1>
<article ng-controller="Customers as customersVm">
<h2>{{customersVm.title}} in {{shellVm.title}}</h2>
<ul ng-repeat="c in customersVm.customers">
<li>{{c.name}}</li>
</ul>
</article>
</div>
Refer AngularJs "controller as" syntax - clarification? as well.
Related
I'm having trouble linking two brother components in angularJS without using a service. I saw examples but I can not make them work. This is what I have. What am I failing? Thanks!
cluster.js
<div class="row">
<filter-component></filter-component>
<result-component filters="$ctrl.filters"></result-component>
</div>
filter.component.js
'use strict';
angular
.module('filter' , ['ui.bootstrap'])
.component('filterComponent', {
bindings: {},
templateUrl : 'app/filter/filter.html',
controller : filterCtrl
})
function filterCtrl($scope){
this.filters = 'FILTRO' // <-- I give it a value
}
results.component.js
(function(){
'use strict';
angular
.module('result')
.component('resultComponent', {
bindings: {
filters :'<' // <-- inject
},
templateUrl : 'app/result/result.html',
controller : resultCtrl
})
function resultCtrl($scope) {}
result.html
<h1>{{$ctrl.filters}}</h1> //<-- nothing is shown :(
Use an attribute with expression binding in the HTML:
<div class="row">
<filter-component on-update="$ctrl.filters=$filters">
</filter-component>
<result-component filters="$ctrl.filters">
</result-component>
</div>
Use expression binding with & in <filter-component>:
app.component('filterComponent', {
bindings: {onUpdate: "&"},
templateUrl : 'app/filter/filter.html',
controller : filterCtrl
})
function filterCtrl() {
this.filters = 'FILTRO' // <-- I give it a value
this.onUpdate({$filters: this.filters});
}
For more information, see
AngularJS Developer Guide - Component-based application architecture
AngularJS Comprehensive Directive API - isolate scope bindings
I want to make a dynamic page using angular where the user after he logged-in see his user picture and some information charged by json file throught an http request.
This is the module where i do the http requestes:
'user strict';
angular.
module('userIssue').
component('userIssue',{
templateUrl: 'user-issue/user-issue.html',
controller: 'userIssueCtrl'
})
.controller('userIssueCtrl',['$scope','$http','$routeParams',function userIssueCtrl($scope,$http,$routeParams){
var self = this;
var percorso;
$scope.utente=$routeParams.username;
document.title="Benvenuto"+" "+$routeParams.username;
$http.get('user/'+ $routeParams.username+'.json').then(function(response){
self.utenti = response.data;
});
$http.get('user/'+$routeParams.username+'.info.json').then(function(qualcosa){
self.d = qualcosa.data;
});
self.show = function(){
$scope.showForm='false';
}
}]);
and this is the part of the html page not working:
<div class="col-sm-4">
<img style="position:center;" ng-src="{{$ctrl.d.imgUrl}}" class="img-circle" alt="user image"></div>
I'm using the same approch in this part of the code:
<div class="col-md-9 col-sm-9" id="issueColumn" >
<div id="issue" ng-repeat='utenti in $ctrl.utenti'>
<li>
<dt>CODICE OPERAZIONE</dt>
<dd>{{utenti.id}}</dd>
<dt>DESCRIZIONE PROBLEMA</dt>
<dd>{{utenti.description}}</dd>
<dt>DATA SEGNALAZIONE</dt>
<dd>{{utenti.forward}}</dd>
<dt>IL PROBLEMA SARà RISOLTO ENTRO IL:</dt>
<dd>{{utenti.endstimed}}</dd>
<dt>STATO DEL PROBLEMA:</dt>
<dd>{{utenti.stato}}</dd>
</li> </br>
</div>
</div>
and here it is working! So why does the data binding not work for the image?
Could someone gently explain me why?
Here is the json file I use for the image:
[
{
"imgUrl": "img/Filippo.png",
"ciao": "ciao_calro"
}
]
As you want to use 1st element of d array object, you should be accessing 1st element of imgUrl property explicitly.
ng-src="{{$ctrl.d[0].imgUrl}}"
Hi can you try the below changes from your code and let me know,
Introduced controllerAs with the controller object:
component('userIssue',{
templateUrl: 'user-issue/user-issue.html',
controllerAs: 'userCtrl'
controller: 'userIssueCtrl' })
Accessing through the controller object name:
Try to access through the controller object name like,
{{userCtrl.d.imgUrl}} in template.
Directives in Angular 1.X are set to have two way binding by default. Components have isolated scopes by default. I have a view that looks like:
<div class="my-view">
{{controllerVariable}}
</div>
If I have the above set up as a directive, the controllerVariable loads correctly in the following situation:
<div ng-controller="myController">
<my-view></my-view>
</div>
But if I have it set up as a component using the following:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: '='
});
then the variable value isn't displayed. I have tried adding $ctrl to the variable:
<div class="my-view">
{{$ctrl.controllerVariable}}
</div>
but this doesn't display the value either.
What am I missing here?
You need to pass the value from the directive into the component:
<my-view passed-var='ctrl.passedVar'></my-view>
and in the component:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: {
passedVar: '='
},
controller: function () {
var vm = this;
console.log(vm.passedVar);
}
});
then you will be able to access in the component as in the example
There are a few other ways to do it, such as using a service to handle the information or using require which would give your component access to the controller of the directive. You can find the above method and others here: https://docs.angularjs.org/guide/component.
I had to explicitly state the variable I wanted to bind:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: {
controllerVariable: '#'
}
});
Also, since controllerVariable is a string, I had to use the # sign binding.
I am trying to write component-style AngularJS, similar to the practice put forward by this article.
However, I have come to realize there are various ways to pass functions to directives from an associated controller. The directive I'm working on is quite complex and I was passing each function in by binding to the directive in the template, but I now see I could just implicitly inherit the $scope object or reference the Controller object directly.
Here is an example of what I mean:
app.js
var app = angular.module('plunker', [])
app
.controller('myCtrl', function($scope) {
$scope.output = '';
// fn foo is passed into directive as an argument
$scope.foo = function () {
$scope.output = 'foo';
}
// fn inherited from controller
$scope.bar = function () {
$scope.output = 'bar';
}
// fn attached to ctrl object and referenced directly
this.baz = function () {
$scope.output = 'baz';
}
})
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
controller: 'myCtrl',
controllerAs: 'ctrl'
};
})
index.html
<body ng-controller="myCtrl">
<my-directive
output="output"
foo="foo()">
</my-directive>
</body>
template.html
<div>
<button ng-click="foo()">Click Foo</button>
<button ng-click="bar()">Click Bar</button>
<button ng-click="ctrl.baz()">Click Baz</button>
<p>You clicked: <span style="color:red">{{output}}</span></p>
</div>
Plunkr: http://plnkr.co/edit/1JzakaxL3D2L6wpPXz3v?p=preview
So there are three functions here and they all work, yet are passed to the directive in different ways. My question is what are the merits of each and which is the best from a code and testability perspective?
You're not really passing anything to the directive, as it's using the same controller as the file containing it...
For instance, if you delete the following:
scope: {
output: '=',
foo: '&',
}
from your directive, everything still works the same. I can't think of a reason to use the same controller for a directive and and the containing application like this. I would never recommend this approach.
If you also remove
controller: 'myCtrl',
controllerAs: 'ctrl'
only foo and bar work. This is because the directive inherits the scope it's contained in. This is only recommended if your directive is pretty simple and tightly coupled to the view using it. Usually this approach is OK when you're just doing some visual modifications that repeat themselves in the page. Just notice that when you change something in the controller, the directive will probably break, and that goes against the encapsulation principle.
Finally, the correct way to pass a function to a directive is indeed using '&' modifier. This lets your directive keep an isolated scope, which means it won't break if some code on the containing controller changes. This makes your directive truly an encapsulated, independent module that you can "drag and drop" anywhere.
Here's a fork of your plunkr.
If i'd like to use the "Controller as ..." syntax in Angular, how should I approach things like $scope.$on(...) that i need to put inside the controller?
I get an impression i could do it some other way than the one shown below.
Here, to get $scope.$on working i bind "this" to the callback function. I tried to invoke $on on "this" inside the controller but it didn't work.
Could you give me a hint here or if i'm completely messing up, could you point me to some right way to do it? Thanks.
main.js:
angular.module('ramaApp')
.controller('MainCtrl', ['$scope', '$location', function ($scope, $location) {
this.whereAmINow = 'INDEX';
$scope.$on('$locationChangeStart', function(event) {
this.whereAmINow = $location.path();
}.bind(this));
this.jumpTo = function(where) { $location.path(where); }
}]);
index.html:
<div ng-controller="MainCtrl as main">
<p>I am seeing the slide named: {{ main.whereAmINow }}</p>
<div ng-click="main.jumpTo('/slide1')">Slide 1</div>
<div ng-click="main.jumpTo('/slide2')">Slide 2</div>
<div ng-click="main.jumpTo('/slide3')">Slide 3</div>
</div>
As far as I know, you need to inject $scope if you want $scope watchers/methods. ControllerAs is just syntactic sugar to enable to see more clearly the structure of your nested controllers.
Three ideas though which may simplify your code.
Use var vm = this, in order to get rid of the bind(this).
var vm = this;
vm.whereAmINow = "/";
$scope.$on('$locationChangeStart', function(event) {
vm.whereAmINow = $location.path();
});
vm.jumpTo = function(where) {
$location.path(where);
}
The whole whereamINow variable makes sense putting it into the initialization of app aka .run() (before config) since I assume it's a global variable and you don't need to use a $scope watcher/method for it.
Another option is to use a factory to make the changes persist, so you simply create a location factory which holds the current active path.
Inject $scope and your controller is accessible by whatever you named it
EG:
$stateProvider
.state('my-state', {
...
controller: 'MyCtrl',
controllerAs: 'ctrl',
...
});
.controller('MyCtrl', function($scope) {
var $this = this;
$scope.$on('ctrl.data', function(new, old) {
// whatevs
});
$timeout(function() {
$this.data = 'changed';
}, 1000);
});
Ok, i think people just do the same, just as in this question:
Replace $scope with "'controller' as" syntax