Angular: Using "this" instead of " $scope" in "Controller As" - javascript

I want to use "this" instead of $scope because I have a controller structure that doesn't initialize $scope:
.controller('MyCtrl', function($scope) {
...
});
I must follow a tutorial with the structure above and put the $scope service to make it work.
Controller
(function() {
'use strict';
angular
.module('example.cancha')
.controller('CanchaController', CanchaController);
CanchaController.$inject = ['$state','$scope', 'canchaService'];
function CanchaController($state, $scope, canchaService) {
var vm = angular.extend(this, {
canchasComplejo: []
});
(function activate() {
cargarCanchasComplejo();
})();
//funcion que llama al servicio para obtener las canchas del complejo
function cargarCanchasComplejo() {
canchaService.obtenerCanchasComplejo()
.then(function(canchasComplejo) {
vm.canchasComplejo = canchasComplejo;
$scope.groups = [];
for (var i=0; i<canchasComplejo.length; i++) {
$scope.groups[i] = {
name: 'Cancha N°'+canchasComplejo[i].nroCancha+' ('+canchasComplejo[i].tipoCancha+')',
items: ['Información','Habilitada','Ver Agenda'],
show: false
}
};
$scope.toggleGroup = function(group) {
group.show = !group.show;
};
$scope.isGroupShown = function(group) {
return group.show;
};
});
}
}
})();
As you can see, this is not the regular structure. Can I avoid using $scope using "this"? Thanks!

remove $scope injection from your controller:
CanchaController.$inject = ['$state','$scope', 'canchaService'];
function CanchaController($state, $scope, canchaService) {
.....
should be:
CanchaController.$inject = ['$state', 'canchaService'];
function CanchaController($state, canchaService) {
then replace all $scope occurrences within controller scope with vm:
$scope.groups = []; becomes vm.groups = [];
now be aware that in the HTML using this controller you cannot access groups directly, but you should use ng-controller="CanchaController as vm" (free fill to use any name instead of vm) and access groups with vm.groups. Your HTML might look like:
<div ng-controller="CanchaController as vm">
<pre>{{ vm.groups | json }}</pre>
</div>
if this controller is used with routing or directive, then controllerAs: 'vm' should be set on JS level and not HTML

Sure you can use so called vm pattern from John Papa. Check the article here https://daveceddia.com/convert-scope-to-controlleras/

You should use the ControllerAs syntax instead of injecting the $scope into the function. This is the recommended way of settings up your controller ,see John Papa's style guide here. Specifically, look for rule [Style Y031].
Another option that you have, if you are using Angular 1.5.x, is to use Components instead of Controllers. When you declare the Component, you will specify the markup associated with it and that is all you have to do. The Component uses the ControllerAs syntax by default and it uses the alias $ctrl to reference the scope.

You can use "this" but it will always point on the function that triggers it. That will give you restrictions on the future or errors.
It's 100% recommended to use $scope in angular so you can have a standard format on each function to avoid problems

Related

Unit Testing Angular 1.5 component that requires ngModel

To test angular 1.5 components, the docs recommend you use ngMock's $componentController instead of using $compile if you don't need to test any of the DOM.
However, my component uses ngModel which I need to pass into the locals for $componentController, but there is no way to programmatically get the ngModelController; the only way to test it is to actually $compile an element with it on it, as this issue is still open: https://github.com/angular/angular.js/issues/7720.
Is there any way to test my components controller without resorting to $compiling it? I also don't want to have to mock the ngModelController myself as its behavior is somewhat extensive and if my tests rely on a fake one rather than the real thing there is a chance newer versions of Angular could break it (though that probably isn't an issue given Angular 1 is being phased out).
tl;dr: Solution is in the third code block.
but there is no way to programmatically get the ngModelController
Not with that attitude. ;)
You can get it programmatically, just a little roundabout. The method of doing so is in the code for ngMock's $componentController service (paraphrased here); use $injector.get('ngModelDirective') to look it up, and the controller function will be attached to it as the controller property:
this.$get = ['$controller','$injector', '$rootScope', function($controller, $injector, $rootScope) {
return function $componentController(componentName, locals, bindings, ident) {
// get all directives associated to the component name
var directives = $injector.get(componentName + 'Directive');
// look for those directives that are components
var candidateDirectives = directives.filter(function(directiveInfo) {
// ...
});
// ...
// get the info of the component
var directiveInfo = candidateDirectives[0];
// create a scope if needed
locals = locals || {};
locals.$scope = locals.$scope || $rootScope.$new(true);
return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs);
};
}];
Though you need to supply the ngModelController locals for $element and $attrs when you instantiate it. The test spec for ngModel demonstrates exactly how to do this in its beforeEach call:
beforeEach(inject(function($rootScope, $controller) {
var attrs = {name: 'testAlias', ngModel: 'value'};
parentFormCtrl = {
$$setPending: jasmine.createSpy('$$setPending'),
$setValidity: jasmine.createSpy('$setValidity'),
$setDirty: jasmine.createSpy('$setDirty'),
$$clearControlValidity: noop
};
element = jqLite('<form><input></form>');
scope = $rootScope;
ngModelAccessor = jasmine.createSpy('ngModel accessor');
ctrl = $controller(NgModelController, {
$scope: scope,
$element: element.find('input'),
$attrs: attrs
});
//Assign the mocked parentFormCtrl to the model controller
ctrl.$$parentForm = parentFormCtrl;
}));
So, adapting that to what we need, we get a spec like this:
describe('Unit: myComponent', function () {
var $componentController,
$controller,
$injector,
$rootScope;
beforeEach(inject(function (_$componentController_, _$controller_, _$injector_, _$rootScope_) {
$componentController = _$componentController_;
$controller = _$controller_;
$injector = _$injector_;
$rootScope = _$rootScope_;
}));
it('should update its ngModel value accordingly', function () {
var ngModelController,
locals
ngModelInstance,
$ctrl;
locals = {
$scope: $rootScope.$new(),
//think this could be any element, honestly, but matching the component looks better
$element: angular.element('<my-component></my-component>'),
//the value of $attrs.ngModel is exactly what you'd put for ng-model in a template
$attrs: { ngModel: 'value' }
};
locals.$scope.value = null; //this is what'd get passed to ng-model in templates
ngModelController = $injector.get('ngModelDirective')[0].controller;
ngModelInstance = $controller(ngModelController, locals);
$ctrl = $componentController('myComponent', null, { ngModel: ngModelInstance });
$ctrl.doAThingToUpdateTheModel();
scope.$digest();
//Check against both the scope value and the $modelValue, use toBe and toEqual as needed.
expect(ngModelInstance.$modelValue).toBe('some expected value goes here');
expect(locals.$scope.value).toBe('some expected value goes here');
});
});
ADDENDUM: You can also simplify it further by instead injecting ngModelDirective in the beforeEach and setting a var in the describe block to contain the controller function, like you do with services like $controller.
describe('...', function () {
var ngModelController;
beforeEach(inject(function(_ngModelDirective_) {
ngModelController = _ngModelDirective_[0].controller;
}));
});

Angular doesn't work when defining a service and factory separately

I'm trying to write a simple angular service and a factory like below:
html:
<div ng-controller="mycontroller">
{{saycheese}}
</div>
Javascript
var myApp = angular.module('myApp', []);
myApp.service('myservice', function() {
this.sayHello = function() {
return "from service";
};
});
myApp.factory('myfactory', function() {
return {
sayHello: function() {
return "from factory!"
}
};
});
//defining a controller over here
myapp.controller("mycontroller", ["myfactory", "myservice", function(myfactory, myservice) {
$scope.saycheese = [
myfactory.sayHello(),
myservice.sayHello()
];
}]);
But the JSFiddle still just displays {{saycheese}} instead of angular mapping the function.
Link to my fiddle:
http://jsfiddle.net/PxdSP/3047/
Can you point me where am I going wrong in this case ? Thanks.
You have several syntax errors in your code, and checking the console would have helped without questioning the SO. Here's one possible way to write the controller (demo):
myApp.controller("mycontroller", ["$scope", "myfactory", "myservice",
function($scope, myfactory, myservice) {
$scope.saycheese = [
myfactory.sayHello(),
myservice.sayHello()
];
}]);
Apart from obvious fix myapp => myApp (variable names are case-sensitive in JavaScript), $scope should be passed into controller as an argument (and mentioned as its dependency if using arrayed - proper - form of controller definition, as you did) before you can access it. Otherwise you just get ReferenceError: $scope is not defined exception when controller code is invoked.
Couple things:
myapp.controller(...) should be myApp.controller(...)
You need to inject $scope in your controller.
Fixed controller:
myApp.controller("mycontroller", ["myfactory", "myservice", "$scope", function(myfactory, myservice, $scope) {
$scope.saycheese = [
myfactory.sayHello(),
myservice.sayHello()
];
}]);

AngularJS: Controller split across two files? [duplicate]

I have three controllers that are quite similar. I want to have a controller which these three extend and share its functions.
Perhaps you don't extend a controller but it is possible to extend a controller or make a single controller a mixin of multiple controllers.
module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
// Initialize the super class and extend it.
angular.extend(this, $controller('CtrlImpl', {$scope: $scope}));
… Additional extensions to create a mixin.
}]);
When the parent controller is created the logic contained within it is also executed.
See $controller() for for more information about but only the $scope value needs to be passed. All other values will be injected normally.
#mwarren, your concern is taken care of auto-magically by Angular dependency injection. All you need is to inject $scope, although you could override the other injected values if desired.
Take the following example:
(function(angular) {
var module = angular.module('stackoverflow.example',[]);
module.controller('simpleController', function($scope, $document) {
this.getOrigin = function() {
return $document[0].location.origin;
};
});
module.controller('complexController', function($scope, $controller) {
angular.extend(this, $controller('simpleController', {$scope: $scope}));
});
})(angular);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>
<div ng-app="stackoverflow.example">
<div ng-controller="complexController as C">
<span><b>Origin from Controller:</b> {{C.getOrigin()}}</span>
</div>
</div>
Although $document is not passed into 'simpleController' when it is created by 'complexController' $document is injected for us.
For inheritance you can use standard JavaScript inheritance patterns.
Here is a demo which uses $injector
function Parent($scope) {
$scope.name = 'Human';
$scope.clickParent = function() {
$scope.name = 'Clicked from base controller';
}
}
function Child($scope, $injector) {
$injector.invoke(Parent, this, {$scope: $scope});
$scope.name = 'Human Child';
$scope.clickChild = function(){
$scope.clickParent();
}
}
Child.prototype = Object.create(Parent.prototype);
In case you use the controllerAs syntax (which I highly recommend), it is even easier to use the classical inheritance pattern:
function BaseCtrl() {
this.name = 'foobar';
}
BaseCtrl.prototype.parentMethod = function () {
//body
};
function ChildCtrl() {
BaseCtrl.call(this);
this.name = 'baz';
}
ChildCtrl.prototype = Object.create(BaseCtrl.prototype);
ChildCtrl.prototype.childMethod = function () {
this.parentMethod();
//body
};
app.controller('BaseCtrl', BaseCtrl);
app.controller('ChildCtrl', ChildCtrl);
Another way could be to create just "abstract" constructor function which will be your base controller:
function BaseController() {
this.click = function () {
//some actions here
};
}
module.controller('ChildCtrl', ['$scope', function ($scope) {
BaseController.call($scope);
$scope.anotherClick = function () {
//other actions
};
}]);
Blog post on this topic
Well, I'm not exactly sure what you want to achieve, but usually Services are the way to go.
You can also use the Scope inheritance characteristics of Angular to share code between controllers:
<body ng-controller="ParentCtrl">
<div ng-controller="FirstChildCtrl"></div>
<div ng-controller="SecondChildCtrl"></div>
</body>
function ParentCtrl($scope) {
$scope.fx = function() {
alert("Hello World");
});
}
function FirstChildCtrl($scope) {
// $scope.fx() is available here
}
function SecondChildCtrl($scope) {
// $scope.fx() is available here
}
You don't extend controllers. If they perform the same basic functions then those functions need to be moved to a service. That service can be injected into your controllers.
Yet another good solution taken from this article:
// base controller containing common functions for add/edit controllers
module.controller('Diary.BaseAddEditController', function ($scope, SomeService) {
$scope.diaryEntry = {};
$scope.saveDiaryEntry = function () {
SomeService.SaveDiaryEntry($scope.diaryEntry);
};
// add any other shared functionality here.
}])
module.controller('Diary.AddDiaryController', function ($scope, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
}])
module.controller('Diary.EditDiaryController', function ($scope, $routeParams, DiaryService, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
$scope.diaryEntry = data;
});
}]);
You can create a service and inherit its behaviour in any controller just by injecting it.
app.service("reusableCode", function() {
var reusableCode = {};
reusableCode.commonMethod = function() {
alert('Hello, World!');
};
return reusableCode;
});
Then in your controller that you want to extend from the above reusableCode service:
app.controller('MainCtrl', function($scope, reusableCode) {
angular.extend($scope, reusableCode);
// now you can access all the properties of reusableCode in this $scope
$scope.commonMethod()
});
DEMO PLUNKER: http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview
You can try something like this (have not tested):
function baseController(callback){
return function($scope){
$scope.baseMethod = function(){
console.log('base method');
}
callback.apply(this, arguments);
}
}
app.controller('childController', baseController(function(){
}));
You can extend with a services, factories or providers. they are the same but with different degree of flexibility.
here an example using factory : http://jsfiddle.net/aaaflyvw/6KVtj/2/
angular.module('myApp',[])
.factory('myFactory', function() {
var myFactory = {
save: function () {
// saving ...
},
store: function () {
// storing ...
}
};
return myFactory;
})
.controller('myController', function($scope, myFactory) {
$scope.myFactory = myFactory;
myFactory.save(); // here you can use the save function
});
And here you can use the store function also:
<div ng-controller="myController">
<input ng-blur="myFactory.store()" />
</div>
You can directly use $controller('ParentController', {$scope:$scope})
Example
module.controller('Parent', ['$scope', function ($scope) {
//code
}])
module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
//extend parent controller
$controller('CtrlImpl', {$scope: $scope});
}]);
You can use Angular "as" syntax combined with plain JavaScript inheritance
See more details here
http://blogs.microsoft.co.il/oric/2015/01/01/base-controller-angularjs/
I wrote a function to do this:
function extendController(baseController, extension) {
return [
'$scope', '$injector',
function($scope, $injector) {
$injector.invoke(baseController, this, { $scope: $scope });
$injector.invoke(extension, this, { $scope: $scope });
}
]
}
You can use it like this:
function() {
var BaseController = [
'$scope', '$http', // etc.
function($scope, $http, // etc.
$scope.myFunction = function() {
//
}
// etc.
}
];
app.controller('myController',
extendController(BaseController,
['$scope', '$filter', // etc.
function($scope, $filter /* etc. */)
$scope.myOtherFunction = function() {
//
}
// etc.
}]
)
);
}();
Pros:
You don't have to register the base controller.
None of the controllers need to know about the $controller or $injector services.
It works well with angular's array injection syntax - which is essential if your javascript is going to be minified.
You can easily add extra injectable services to the base controller, without also having to remember to add them to, and pass them through from, all of your child controllers.
Cons:
The base controller has to be defined as a variable, which risks polluting the global scope. I've avoided this in my usage example by wrapping everything in an anonymous self-executing function, but this does mean that all of the child controllers have to be declared in the same file.
This pattern works well for controllers which are instantiated directly from your html, but isn't so good for controllers that you create from your code via the $controller() service, because it's dependence on the injector prevents you from directly injecting extra, non-service parameters from your calling code.
I consider extending controllers as bad-practice. Rather put your shared logic into a service. Extended objects in javascript tend to get rather complex. If you want to use inheritance, I would recommend typescript. Still, thin controllers are better way to go in my point of view.

Unit testing ControllerAs with forms in AngularJS

I have the following situation with Angular:
Controller
function Controller () {
// form used in template
this.form ...
}
Template that has a form and uses this controller
Template
<div ng-controller="Controller as ctrl">
<form name="ctrl.form">
...
</div>
I have to say I'm in general confused why Angular doesn't have a better way of adding the form to the controller other than automatically adding either this.form or $scope.form, depending on how you use controllers, but that's I guess another question.
The real issue I have right now is that I'm not sure how I should test this. If I just instantiate the controller in the test, then my form is undefined
$controller('Controller as ctrl')
I did find a way, by just compiling the template
$scope = $rootScope.$new();
$compile(template)($scope);
But because ng-controller in the template starts a new scope, I can't access the controller directly with $scope.ctrl, instead I'd have to do something like $scope.$$childHead.login
... and I feel it's getting too complicated. edit: not to mention that $$ indicates a 'private' property.
I've solved it myself, but I'm leaving it here unaccepted because I don't think the solution is very nice. If anyone knows a better way, please post.
The problem with compiling templates is that it's also not possible to insert mock services in the controller, at least I didn't figure it out. You get a controller instance on $scope, like $scope.ctrl, but that's it.
The second attempt was to locate just the form in the template, compile and add it to a controller that was instantiated separately. This worked, but not really, because the $scope for the form and controller were different and so any update to a field didn't reflect on the controller state.
The way it works in the end is to instantiate the controller with a $scope
ctrl = $controller('Controller as ctrl', {
someDependency: mockDependency,
$scope: $scope
});
and then to compile the partial template (just the form) with the same $scope
var formTpl = angular.element(template).find('form');
form = $compile(formTpl)($scope);
This way, the controller ends up in $scope.ctrl, but because the form is already named name="ctrl.form" it gets inserted into $scope.ctrl.form and it's visible inside a controller.
When using controllerAs, you can access your form in tests like so:
// 1. Create a new scope
var $scope = $rootScope.$new();
// 2. Run the controller
var Controller = $controller("Controller as ctrl", { $scope: $scope });
// 3. Compile the template against our scope
// This will add property $scope.ctrl.form (assuming that form has name="ctrl.form")
$compile(angular.element(templateHtml))($scope);
// 4. Controller.form should now also be defined
expect(Controller.form).toBeDefined();
Meanwhile mocking can be achieved by using angular's $provide:
beforeEach(angular.mock.module(function ($provide) {
function ApiServiceMock() {
this.getName() = function () {
return "Fake Name";
};
}
// Provide our mocked service instead of 'ApiService'
// when our controller's code requests to inject it
$provide.value("ApiService", ApiServiceMock);
}));
Full example (using both - mocking & form compilation with controllerAs):
Main app code:
angular.module("my.module", [])
.service("ApiService", function () {
this.getName = function () {
return "Real Name";
};
})
.controller("Controller", function (ApiService) {
var ctrl = this;
ctrl.someProperty = ApiService.getName();
});
HTML:
<div ng-controller="Controller as ctrl">
<form name="ctrl.form">
<input type="email" name="email" />
</form>
</div>
Test:
describe("Controller", function () {
var $scope,
Controller;
beforeEach(angular.mock.module("my.module"));
beforeEach(angular.mock.module(function ($provide) {
function ApiServiceMock() {
this.getName() = function () {
return "Fake Name";
};
}
$provide.value("ApiService", ApiServiceMock);
}));
beforeEach(inject(function ($rootScope, $controller, $compile) {
$scope = $rootScope.$new();
Controller = $controller("Controller as ctrl", { $scope: $scope });
// FIXME: Use your own method of retrieving template's HTML instead 'getTemplateContents' placeholder
var html = getTemplateContents("./my.template.html"),
formTpl = angular.element(html).find('form');
$compile(formTpl)($scope);
}));
it('should contain someProperty', function () {
expect(Controller.someProperty).toBeDefined();
});
it('form should contain an email field', function () {
expect(Controller.form.email).toBeDefined();
});
});

Call function from another Controller Angular Js

I am new to using angular js and i have declare many controller and now i want to user function of one controller into another controller. here is my sample code.
app.controller('Controller1',function($scope,$http,$compile){
$scope.test1=function($scope)
{
alert("test1");
}
});
app.controller('Controller2',function($scope,$http,$compile){
$scope.test2=function($scope)
{
alert("test1");
}
});
app.controller('Controller3',function($scope,$http,$compile){
///
});
Now i want to call test2 function inside controller3.
Can anybody help..
Thanks in Avance... :)
You can't call a method from a controller within a controller. You will need to extract the method out, create a service and call it. This will also decouple the code from each other and make it more testable
(function() {
angular.module('app', [])
.service('svc', function() {
var svc = {};
svc.method = function() {
alert(1);
}
return svc;
})
.controller('ctrl', [
'$scope', 'svc', function($scope, svc) {
svc.method();
}
]);
})();
Example: http://plnkr.co/edit/FQnthYpxgxAiIJYa69hu?p=preview
Best way is write a service and use that service in both controllers. see the documentation Service documentation
If you really want to access controller method from another controller then follow the below option:
emitting an event on scope:
function FirstController($scope) { $scope.$on('someEvent', function(event, args) {});}
function SecondController($scope) { $scope.$emit('someEvent', args);}
The controller is a constructor, it will create a new instance if for example used in a directive.
You can still do what you wanted, assuming that your controllers are in the same scope, just do:
Note they must be in the same scope, could still work if a child scope was not isolated.
The directive's definition:
{
controller: Controller1,
controllerAs: 'ctrl1',
link: function($scope) {
$scope.ctrl1.test1(); // call a method from controller 1
$scope.ctrl2.test2(); // created by the directive after this definition
$scope.ctrl3.test3(); // created by the directive after this definition
}
}
....
{
controller: Controller2,
controllerAs: 'ctrl2',
link: function($scope) {
$scope.ctrl1.test1(); // created earlier
$scope.ctrl2.test2(); // ...
$scope.ctrl3.test3(); // created by the directive after this definition
}
}
....
{
controller: Controller3,
controllerAs: 'ctrl3',
link: function($scope) {
$scope.ctrl1.test1();
$scope.ctrl2.test2();
$scope.ctrl3.test3();
}
}
This should work.

Categories