I'm trying to use the angular-seed template with the default settings. In controllers.js I use
angular.module('myApp.controllers', []).
controller('MyCtrl1', [function($scope) {
$scope.test = 'scope found!';
}])
.controller('MyCtrl2', [function() {
}]);
There the $scope is always undefined.
When I take the controller out of the module and register it globally it works fine. As here:
function MyCtrl1($scope) {
$scope.test = "scope found!";
}
MyCtrl1.$inject = ['$scope'];
Could someone explain to me why this is?
You cannot mix things like that. You need to decide on one of the two possibilities:
app = angular.module('test', []);
// possibility 1 - this is not safe for minification because changing the name
// of $scope will break Angular's dependency injection
app.controller('MyController1', function($scope) {
// ...
});
// possibility 2 - safe for minification, uses 'sc' as an alias for $scope
app.controller('MyController1', ['$scope', function(sc) {
// ...
}]);
I would not advise using the other syntax which declares Controller directly. Sooner or later with the growth of you app it will become hard to maintain and keep track. But if you must, there are 3 possibilities:
function myController1 = function($scope) {
// not safe for minification
}
function myController2 = ['$scope', function(sc) {
// safe for minification, you could even rename scope
}]
var myController3 = function(sc) {
// safe for minification, but might be hard
// to read if controller code gets longer
}
myController3.$inject = ['$scope'];
This is the proper way:
angular.module('myApp.controllers', []);
angular.module('myApp.controllers').controller('MyCtrl1', ['$scope', function($scope) {
}]);
I was also searching for that one, it seems that you need to type '$scope' before the function, as below:
angular.module('myApp.controllers', []).
controller('MyCtrl1', ['$scope', function($scope) {
$scope.test = 'scope found!';
}])
.controller('MyCtrl2', ['$scope',function() {
}]);
It kinda makes sense, I think it should be more clear though..
You can simply remove '[' and ']' when You are using $scope.
angular.module('myApp.controllers', []).
controller('MyCtrl1', function($scope) {
$scope.test = 'scope found!';
})
.controller('MyCtrl2', [
function() {
}
]);
Related
I'm trying to figure out how controller inheritance works. I have three controllers:
var myApp = angular.module('app', []);
myApp.controller('MainController', ['$scope', function($scope) {
$scope.name = 'main';
$scope.getName = function() {
return $scope.name;
};
}]);
myApp.controller('Child1', ['$scope', function($scope) {
$scope.name = 'child1';
}]);
myApp.controller('Child2', ['$scope', function($scope) {
$scope.name = 'child2';
}]);
and my view
<div ng-app='app'>
<div ng-controller='MainController'>
<div ng-bind='getName()'></div>
<div ng-controller='Child1'>
<div ng-bind='getName()'></div>
<div ng-controller='Child2'>
<div ng-bind='getName()'></div>
</div>
</div>
</div>
</div>
but they're all showing "main". How do I fix this?
here's a fiddle http://jsfiddle.net/g3xzh4ov/3/
Here's an example of how controllers can be extended in Angular.
myApp.service('baseCtrl', function () {
this.name = 'base';
this.getName = function() {
return this.name;
};
});
myApp.controller('MainController', ['baseCtrl', function (baseCtrl) {
angular.extend(this, baseCtrl);
this.name = 'main';
}]);
myApp.controller('Child1', ['baseCtrl', function (baseCtrl) {
angular.extend(this, baseCtrl);
this.name = 'child1';
}]);
myApp.controller('Child2', ['baseCtrl', function (baseCtrl) {
angular.extend(this, baseCtrl);
this.name = 'child2';
}]);
It obliges to use controllerAs, which replaces $scope with this, it is especially good for such cases.
Notice the usage of service instead of other Angular service types, it uses new under the hood, so this... statements can be brought right from a controller to separate service.
There are several ways of doing controller inheritance. Here is another approach.
Regarding the original code, there is no 'controller iheritance' in Angular. And $scope prototypical inheritance assumes that
$scope.getName = function() {
return $scope.name;
};
returns $scope.name from the context where it was defined, it is MainController function in your case.
The problem that you're facing is actually based on core Javascript functionality.
You see, the confusion that you're facing stems from the mix of scoping, and prototypical inheritance. The properties are copied over, but the scoping remains the same, preventing you from accessing the variables that you expect to be able to access. To understand this better, perhaps instead of $scope, we can look at a simpler variable:
myApp.controller('MainController', ['$scope', function($scope) {
var a = 1;
$scope.getName = function() {
console.log(a); // -> 1
console.log(b); // Error! `Uncaught ReferenceError: b is not defined`
};
}]);
myApp.controller('Child1', ['$scope', function($scope) {
var b = 2;
}]);
Obviously, MainController doesn't know that Child1 defined some variable called b, so it errors. That variable is strictly out of lexical scope.
Likewise, if we renamed b to a, it won't turn the value in MainController to 2. This demonstrates exactly what's happening for you: you have three things called $scope, and only one is in the lexical scope.
Two options to fix it:
1) use this:
$scope.getName = function() {
return this.name;
};
The this solution works because of how Javascript determines "this" based on context. Basically, since it's attached to a given $scope Object, that Object's a good candidate. But Mozilla can explain this better than I can, so view their page on the topic here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
2) Otherwise, you can simply pass the $scope:
$scope.getName = function(item) {
return item.name;
};
If you want to overwrite name property in child scopes ,convert your primitive name property into object.
$scope.user = {};
$scope.user.name='main';
$scope.getName = function() {
return $scope.user.name;
};
And you should read https://github.com/angular/angular.js/wiki/Understanding-Scopes for detailed information.
Just wondering if there's difference between:
angular.module('app', [])
.controller('myctrl', ['$scope', 'customService',
function($scope, customService) {
// use customService
}
]);
and using $injector
angular.module('app', [])
.controller('myctrl', ['$scope', '$injector',
function($scope, $injector) {
// get customService via injector and use it
$injector.get('customService');
}
]);
Sometimes I need to inject quite a few dependencies and my parameter list ends up to be lengthy. That's why I'm thinking the latter approach may be better for my scenario.
Does the difference affect testing or minification or any other reason?
You are on the right track. Look at John Papa's Style Guide. You will do the injection before the controller function.
/* recommended */
angular
.module('app')
.controller('Dashboard', Dashboard);
Dashboard.$inject = ['$location', '$routeParams', 'common', 'dataservice'];
function Dashboard($location, $routeParams, common, dataservice) {
}
Basically there is not much different, until you first encounter your circular dependency error while loading your module.
Simple Fiddle that demonstrates circular dependency.
Code (from fiddle):
angular.module('app', [])
.service('serviceA', function (serviceB) {
return {};
})
.service('serviceB', function (serviceA) {
return {};
})
.controller('myCtrl', function (serviceA, serviceB) {
});
Using $injector helps prevent that scenario because when you use it inside the Controller/Factory/Service/etc instead of injecting the first way, you are delaying the dependency and therefore solving the problem.
And a simple Fiddle that demonstrates how that problem is solved.
Code (from fiddle):
angular.module('app', [])
.service('serviceA', function (serviceB) {
return {
test: "it works!"
};
})
.service('serviceB', function ($injector) {
return {
test: function() {
var serviceA = $injector.get("serviceA");
return serviceA.test;
}
};
})
.controller('myCtrl', function ($scope, serviceA, serviceB) {
$scope.test = serviceB.test();
});
I have a partial view that is using angular. How do I change a variable in the MainController from PartialController? I am not sure how to create the interdependence...
angularApp.controller('MainController', ['$scope', '$http', '$compile', function MainController($scope, $http, $compile) {
$scope.myVariable = "0";
//Had the following before refactoring due to repetitive code.
//Code now in PartialController
//$scope.searchData = function ($event) {
// //code
// $scope.myVariable = "1";
//}
}]);
angularApp.controller('PartialController', ['$scope', '$http', '$compile', function PartialController($scope, $http, $compile) {
$scope.searchData = function ($event) {
//code
$scope.myVariable = "1";
}
}]);
For sake of completeness, there are at least 3 ways:
With a service as #tymeJV suggested (BEST answer)
app.factory('dataStore', function () {
var dataStore = {};
return dataStore;
});
app.controller('ParentCtrl', function($scope, dataStore) {
$scope.dataStore = dataStore;
$scope.dataStore.foo = 'bar';
});
app.controller('ChildCtrl', function($scope, dataStore) {
dataStore.foo = 'not bar anymore';
});
With an object reference on the parent scope (A bit hackish)
app.controller('ParentCtrl', function($scope) {
$scope.data = {
foo: 'bar'
};
});
app.controller('ChildCtrl', function($scope) {
$scope.data.foo = 'not bar anymore';
});
With $parent (equally hackish)
app.controller('ParentCtrl', function($scope) {
$scope.foo = 'bar';
});
app.controller('ChildCtrl', function($scope) {
$scope.$parent.foo = 'not bar anymore';
});
Why are #2 and #3 hackish?
Because they create a dependency in your ChildCtrl of having it always be a child of the ParentCtrl... otherwise it will break.
So why include #2 and #3 at all?
For a few reasons:
Directives can have controllers, and required parent directives. Because of this, there are cases where you can "safely" use $parent or scope inheritance because you'll always know that ChildCtrl has ParentCtrl as a parent.
Sometimes you just need to hack something together.
As I said, for the sake of completeness.
This is a prime use for a service that can be injected to controllers when you need it and pull data from it:
app.factory("myService", function() {
var myVariable = null;
return {
get: function() {
return myVariable;
},
set: function(value) {
myVariable = value;
}
}
});
//Inject
angularApp.controller('MainController', ['$scope', '$http', 'myService', '$compile', function MainController($scope, $http, $compile, myService) {
myService.set(3);
});
tymeJV's answer is correct and is probably best practice in this case. I believe the reason it wasn't working in your example is because in Javascript, primitives (strings, numbers, booleans) are passed by value, whereas objects are passed by reference.
i.e. if you had $scope.obj.myVariable=1 in your main controller and edit $scope.obj.myVariable in your child controller, you should see the new value in both. (this is kinda #2 in blesh's answer). This is a common source of "bugs" in Angular so it's good to be aware of it.
I have written a service in AngularJS, but I can't get it to work with the angular-seed way of doing things.
The controller code is as follows:
/*function PhotoCtrl($scope, Photo) {
$scope.photos = Photo.query();
}*/
angular.module('myApp.controllers', []).
controller('PhotoCtrl', [function($scope,Photo) {
$scope.photos = Photo.query();
}])
.controller('MyCtrl2', [function() {
}]);
Note that the commented out section works fine, but I would like to handle it somewhat like the (recommended) second way.
The error I get is that Photo is undefined, so my guess would be my method of passing (injecting) it is wrong, but I can't find out how to do it correctly
You need to define the Photo service:
angular.module('myApp.controllers', [])
.service('Photo', ['$log', function ($log) {
return {
query: function() {
// the query code here.
}
};
}])
.controller('PhotoCtrl', ['$scope', 'Photo', function ($scope, Photo) {
$scope.photos = Photo.query();
}])
.controller('MyCtrl2', [function() {
}]);
A couple of references:
http://docs.angularjs.org/api/angular.Module
http://docs.angularjs.org/api/AUTO.$provide#service
In the above sample code I used parameters aliasing, which I suggest in order to avoid issues when minifying your code.
See also an example here:
AngularJS multiple uses of Controller and rootScope
And a Plunker here:
http://plnkr.co/edit/Bzjruq
There is a lot of reusable functionality that I have defined in my application that EVERY controller uses with the $scope variable. Instead of me having to create a shared service each time, is there a way to extend the $scope variable so that I can have my extended code available everywhere?
Something like:
//I've tested this out and it doesn't work, but this is what I want to do.
angular.module('App',[]).config(['$scopeProvider',function($scope) {
$scope.method1 = function() { ... };
$scope.method2 = function() { ... };
}]);
Then later on:
var HomeCtrl = function($scope) {
$scope.method1();
};
Is this possible? Or do I need to create a shared service and then have the $scope extend from that for the first line of each controller?
Instead of .config try .run, this will do exactly what you want.
angular.module('App', []).run(['$rootScope', function($rootScope) {
$rootScope.foo = function() {
alert("WIN!");
};
}]);
angular.module('App').controller('HomeCtr', ['$scope', function($scope) {
$scope.foo(); #will call the alert
}]);
NOTE I have only used module.controller because I like it, var HomeCtrl = function($scope) { will have the same effect.