I am trying to use the controllerAs property on a $routeProvider route without any success.
Here is the sample code:
var app = angular.module('app', ['ngRoute']);
app.config(['$routeProvider', '$locationProvider',
function($routeProvider) {
$routeProvider
.when('/', {
template:'<div>This should be visible:{{ ctrl.one }}</div><div>This should not:{{ one }}</div>',
controller: 'Ctrl',
controllerAs: 'ctrl',
});
}]);
app.controller('Ctrl', function($scope) {
$scope.one = 'actual';
});
Not sure if this is a bug or I am doing something wrong, This is a plnkr that demonstrates the issue
Actual problem:
You seem to be using controllerAs (assigning a value of 'ctrl') originally, but then later not making use of it in the rest of your code. (you used $scope)
Solution:
Working demo as per your sample
When I've been using the controllerAs syntax you need to use one of the following pattern(s) to get access to the instance of the controller:
As opposed to appending the property to $scope, you have to bind to the controller scope. Note this is different to $scope. For lack of a better term, you need to bind controller itself (think of it as its context). As we're dealing with the display layer or view model, I tend to use var vm = this; as a convention, but this personal preference.
[A]: Preferred Solution
app.controller('Ctrl', function() {
this.one = 'actual';
});
//or using 'vm' convention
app.controller('Ctrl', function() {
var vm = this;
vm.one = 'actual';
});
[B]
app.controller('Ctrl', function() {
var vm = {};
vm.one = 'actual';
return vm;
});
Explanation:
When I first started using Angular, [B] was the approach that I used, purely coming from a Knockout background. I was used to binding to a "container" object then binding the object to the view. That being said, I prefer to use [A], or append directly to $scope and forgo the alias completely. Reasons:
I feel its cleaner ITO readability
As #Swordfish0321 stated, [A] is more performant (should it be a concern to you)
I had binding issues with custom directives I wrote that where dependent on certain parent scope properties (specific to my code-base)
Just as a visual:
Demo
app.controller('Ctrl', function($scope) {
var vm = this;
vm.one = 'actual';
console.log($scope)
});
Passing in the $scope object and further inspecting it, you'll see a new ctrl child object containing all your public properties and functions that was bound to vm inside the controller code. This is because you've assigned var vm = this. Meaning the vm object in the code is referencing the controller's own scope, which ultimately gets bound to the view. controllerAs basically groups all properties and functions contained internal to the controller to a new object named after the alias that you've provided.
To be clear--because I don't think the accepted answer was explicit--the problem with your example is that even though you are assigning a value to controllerAs you are bypassing it by still using $scope.
The "vm" approach stands for view-model which is just a convention, but IMO is far more representative of what is actually going on than "$scope". All we're really trying to do here is bind the view to the view model.
That being said you can technically use both controllerAs AND normal $scope at the same time plunk.
Additionally, the difference between Rohan's examples A and B is that A is the way you should be doing it because you are able to leverage JavaScript's prototypal inheritance which is conducive to better perf. It's also worth noting that because you are now using controllerAs and this you no longer need to inject $scope.
// Functioning Example
var app = angular.module('app', ['ngRoute']);
app.config(['$routeProvider', '$locationProvider',
function($routeProvider) {
$routeProvider
.when('/', {
template:'<div>This should be visible:{{ vm.one }}</div><div>This should not:{{ one }}</div>',
controller: 'Ctrl',
controllerAs: 'vm',
});
}]);
app.controller('Ctrl', function() {
this.one = 'actual';
});
The controllerAs is an alias for the controller instance, if you would like to use that, you have to store data into the controller instance this instead of $scope:
app.controller('Ctrl', function($scope) {
var ctrl = this; // assign to a variable to be consistent when using in the template
ctrl.one = 'actual';
});
Example Plunker: http://plnkr.co/edit/r8AYtSKbiLQAaPBPriRp?p=preview
If you work with $scope, you will have to return $scope at the end of the function for the controllerAs syntax to function.
app.controller('Ctrl', function($scope) {
$scope.one = 'actual';
// return $scope
return $scope;
});
Good Luck.
Related
I'm new to UI. I do have confusion between $scope's in AngularJS. Please refer below snippet.
var mainApp = angular.module("mainApp", []);
mainApp.controller(['$scope', function($scope) {
$scope.name = "John";
}]);
So, what's the difference between $scope and function($scope)? Also how can we relate both? Is it required to have $scope parameter? Please explain me with an example. I really appreciate that.
Thanks,
John
1.When you apply Minification of Following Angular JS code:
var mainApp = angular.module("mainApp", []);
mainApp.controller(['$scope','$log', function($scope,$log) {
$scope.name = "John";
$log.log("John");
}]);
Minified Version :
var mainApp=angular.module("mainApp",
[]);mainApp.controller(["$scope","$log",function(n,o)
{n.name="John",o.log("John")}]);
2.When you apply Minification of Following Angular JS code:
var mainApp = angular.module("mainApp", []);
mainApp.controller(function($scope,$log) {
$scope.name = "John";
$log.log("John");
});
Minified Version :
var mainApp=angular.module("mainApp",[]);mainApp.controller(function(n,a)
{n.name="John",a.log("John")});
3.When you apply Minification of Following Angular JS code:
var mainApp = angular.module("mainApp", []);
mainApp.controller(function($log,$scope) {
$scope.name = "John";
$log.log("John");
});
Minified Version :
var mainApp=angular.module("mainApp",[]);mainApp.controller(function(n,a)
{n.name="John",a.log("John")});
You will Notice in Ex-2 and Ex-3 that you have interchanged the Dependency place of $scope and $log then also your minified version is the same ,this will give you dependency Injection error ,so we place a String value as String Value can't be minified as you can see in Ex-1.
It is not required to have $scope each time you define your controller but $scope provides important functionality like binding the HTML (view) and the JavaScript (controller). .
https://docs.angularjs.org/guide/scope
what's the difference between $scope and function($scope)?
When you do
mainApp
.controller(
['$scope', //line 1
function($scope) //line 2
{
}
]);
In line 1 it refers to $scope, which is an object that refers to the application model
In line 2 it is the variable (conveniently called $scope too) in which the (mentioned above) $scope object is injected. This variable can have any other name, $scope is used as a way to keep a suggestive reference through the whole code.
For instance, your example would work too if I change its name to myFunnyScope like this:
var mainApp = angular.module("mainApp", []);
mainApp.controller(['$scope', function(myFunnyScope) {
myFunnyScope.name = "John";
}]);
Also how can we relate both?
Taking as reference my previously posted snippet, you can tell the $scope object is being injected in the myFunnyScope variable, it means you use myFunnyScope as if it were $scope itself.
Is it required to have $scope parameter?
As long as you need to get access to all benefits provided by the mentioned $scope object, when you do minification it is required to inject the object ([$scope, ...) into the holder (function($scope) { ...) in order to not get the AngularJS application broken. Otherwise, no, you don't need to inject the object, but then you have to explicitly call it $scope in the function parameter so AngularJS knows it has to inject the $scope object inside it. This rule applies not only to $scope, but to all other AngularJS related services, factories, etc such as $timeout, $window$, $location, etc.
You might want to read about the AngularJS injection mechanism and consider using the controller as syntax for reasons explained here if you do not want to use $scope directly.
I have one situation where I have to pass the directive, functions and variables declared in the controller. I can easily do this with $scope. But I read one article which states that we should not populate the scope instead use this. The article had the following example -
//Don't do this
app.controller('MyCtrl', function($scope){
$scope.name = 'Techno Fattie';
});
//Do this
var MyCtrl = function(){
this.name = 'Techno Fattie';
};
app.controller('MyCtrl', MyCtrl);
I liked the idea and I tried implementing the same in my situation which is as follows -
I have a CountryController - which I modified to use this instead of $scope.
I have a countryList.tpl.html - which has only a directive in it and no other code.
Now I have a parent controller which has a stateProvider where I have configuration for country. something like this -
.state('app.country', {
url: "/country",
templateUrl: "countryList.tpl.html",
controller: CountryController
})
I converted the controller's $scope to this but then I observed that I the directive is not receiving the function and variables and the page is not loading properly.
Is it that if the tpl file has a directive then this approach is not useful? If that is not true, then how can we do this? Can anyone help?
// Somewhere in FooCtrl
$scope.myvar = 'titi'
$location.path('/viewSubscribe'); // redirection to the subscribe page
I'd like to be able to retrieve the "myvar" variable in the $scope of SubscribeCtrl (linked to /viewSubscribe route).
What is the clean way to do it?
Thanks.
Well, there are several ways, depending on the use case. Here are some approaches ordered by level of relevancy to yours:
Route it (demo)
To the path
Pass it as a path variable, and define the variable name in when routing (in the $routeProvider configuration):
// somewhere
$scope.myvar = 'titi';
$location.path('/viewSubscribe/' + $scope.myvar);
// ...
// in the module configuration
app.config(function ($routeProvider) {
$routeProvider
.when('/viewSubscribe/:myVar', {
templateUrl: 'views/myView.html',
controller: 'MyCtrl'
})
// etc.
});
Then you can inject the route parameters service to the desired location, and get the path variable's value:
// somewhere else
app.controller('MyCtrl', function($routeParams, $log) {
$log.info($routeParams['myVar']); // --> 'titi'
});
Shoot it (demo)
As in "event fired"
You can fire events to scopes up or down the hierarchy with the scope's $emit() or $broadcast(), respectively, e.g.:
app.controller('ParentCtrl', function($scope) {
$scope.$watch('model.text', function(newVal) {
$scope.$broadcast('myVarChanged', newVal);
});
});
app.controller('ChildCtrl', function($scope, $log) {
$scope.$on('myVarChanged', function (myVar) {
$log.info(myVar); // --> 'titi'
});
});
Fix it (demo)
As in "for good"
You can also set it once, and get it everywhere, if you declare it as a constant (or a value):
// at some outer level
app.constant('myVar', 'titi');
// anywhere, really
app.controller('MyCtrl', function(myVar, $log) {
$log.info(myVar); // --> 'titi'
});
Plant it (demo)
So it has roots
Declare it in the root scope, so it is shared to all scopes:
app.run(function ($rootScope) {
$rootScope.myVar = 'titi';
});
// later
app.controller('MyCtrl', function($rootScope, $log) {
$log.info($rootScope.myVar); // --> 'titi'
$log.info($scope.myVar); // --> 'titi'
});
This method is less recommended, as it sets a common property for all inherited scopes, which can be easily overridden or shadowed (scopes are inherited prototypaly) and it cannot be used with isolated scopes (i.e. in directives that define their scope as such).
In the case you don't want to retrieve the variable as a path parameter you can also make use of services. These are guaranteed to last for the duration of an AngularJS app.
See http://docs.angularjs.org/guide/services
You can then store the variables inside your service as you would from a normal Java-like class and pass the services via dependency injection.
I've been experimenting a little with Angular.js lately. As part of this I created a very simple set of controllers with an ng-view and templates to trigger depending on the route requested. I'm using angularFireCollection just to grab an array from Firebase. This works fine in the thumbnailController which does not form part of the ng-view.
My problem is that in addition to the data flowing into the thumbnailController, I also need the two other controllers to be able to access the data. I initially simply set the data to either be part of $rootScope or to have the ng-view as a child of the div in which the thumbnailController is set.
However, the issue from that perspective is that each sub-controller presumably attempts to set the data in the template before it is actually available from Firebase.
The solution appears to be using resolve as per the answer to this question angularFire route resolution. However, using the below code (and also referencing angularFireCollection) I get an error message of angularFire being an unknown provider. My understanding is the code below should be sufficient, and I would also add that the usage of angularFireCollection in thumbnailController works fine as I say.
I also experimented with injecting angularFire/aFCollection directly into the controllers using .$inject however a similar issue arose in terms of it being considered an unknown provider.
If possible could someone advise on what the issue may be here?
var galleryModule = angular.module('galleryModule', ['firebase']);
galleryModule.config(['$routeProvider', 'angularFire', function($routeProvider, angularFire){
$routeProvider.
when('/', {
controller: initialController,
templateUrl: 'largeimagetemplate.html',
resolve: {images: angularFire('https://mbg.firebaseio.com/images')}
}).
when('/view/:id', {
controller: mainimageController,
templateUrl: 'largeimagetemplate.html',
resolve: {images: angularFire('https://mbg.firebaseio.com/images')}
}).
otherwise({
redirectTo: '/'
});
}]);
galleryModule.controller('thumbnailController', ['$scope', 'angularFireCollection', function($scope, angularFireCollection){
var url = 'https://mbg.firebaseio.com/images';
$scope.images = angularFireCollection(url);
}]);
function initialController($scope,images){
$scope.largeurl = images[0].largeurl;
}
function mainimageController($scope, images, $routeParams){
$scope.largeurl = images[$routeParams.id].largeurl;
}
I got the chance to dig into this a little bit - it seems like regular services cannot be used in .config sections. I'd instantiate angularFire in the controller instead of using resolve, for example:
galleryModule
.value("url", "https://mbg.firebaseio.com/images")
.controller('thumbnailController', ['$scope', 'angularFireCollection', 'url',
function($scope, angularFireCollection, url) {
$scope.images = angularFireCollection(url);
}])
.controller('initialController', ['$scope', 'angularFire', 'url',
function($scope, angularFire, url) {
angularFire(url, $scope, 'images').then(function() {
$scope.largeurl = $scope.images[0].largeurl;
});
}])
.controller('mainimageController', ['$scope', 'angularFire', '$routeParams', 'url',
function($scope, angularFire, $routeParams, url){
angularFire(url, $scope, 'images').then(function() {
$scope.largeurl = $scope.images[$routeParams.id].largeurl;
});
}]);
This is not ineffecient, since the data is only loaded once from the URL by Firebase, and all subsequent promises will be resolved almost immediately with data already at hand.
I would like to see angularFire work with resolve in the $routeProvider, however. You can use this method as a workaround until we figure out a more elegant solution.
I need current path from url in template (content of $location.path). But not via controller, because I have a lot of controllers (and I do not want to duplicate declaration of $scope.currentUrl = $location.path;). Thanks for the advice.
AngularJS template can only see what is available in a scope so you will need somehow to put $location service in a scope. There is one scope that is always available in AngularJS application called $rootScope so it could be use for your use-case.
What you could do is to use run() method of a module to expose $location in the $rootScope:
var myApp = angular.module('myApp', []).run(function($rootScope, $location) {
$rootScope.location = $location;
});
this would make 'location' available in all templates so later on you could do in your template:
Current path: {{location.path()}}
An alternative is to use the more semantic and versatile ui-router, then in the controller, retrieve the current state, and store it on the $scope:
app.controller('MyCtrl', ['$scope', '$state', function MyCtrl($scope, $state) {
$scope.state = $state.current.name;
...
}