Related
I am working on a application originally created with backbone and jQuery, however due to client requirement, new modules are built with angular. Routing of the application is handled with backbone route and we have successfully integrated angular modules.
The actual problem is, I need to retrieve the current instance of a module in angular and execute a function from the controller of that module based on actions handled by a backbone controller.
Here is what my angular module and controller looks like:
//In chat.module.js
( function () {
angular
.module( 'chat.module', [] );
})();
//In chat.controller.js
(function () {
angular
.module('chat.module')
.controller('chat.controller', ['profileFactory', '$filter', '$q', '$timeout', 'Position', 'Chat', chat]);
function chat(profileFactory, $filter, $q, $timeout, Position, Chat) {
var vm = this;
vm.initChatFlag = false;
vm.initChat = initChat;
vm.setInformation = setInformation;
function setInformation() {
//handle business logic here
}
...
In backbone, the module is created as follows:
chatmodule: function () {
var self = this;
var element = angular.element(document.querySelector('#modalCallback'));
var chat = angular.element(document.querySelector('#chatModule'));
var isInitializedChat = chat.injector();
var isInitialized = element.injector();
if (!isInitialized) {
angular.bootstrap($('#modalCallback'), ['app']);
}
if (!isInitializedChat) {
angular.bootstrap($('#chatModule'), ['app']);
}
//TODO: chat.controller.setInformation() get access to fields like chat.controller.initChatFlag etc
The main app module is defined thus:
(function(){
angular
.module('app',[
'callback',
'ui.bootstrap',
'720kb.datepicker',
'ngLocale',
'directives.module',
'interceptor',
'directive.loading',
'angularUtils.directives.dirPagination',
'blog.module',
'profile.module',
'filters.module',
'chat.module',
'ui.toggle',
]);
})();
The AngularJS $injector is where a lot of the magic happens, so if you expose that outside of the AngularJS code you can hook it up to non-AngularJS code like the following:
//A simple AngularJS service:
app.service('myService', function() {
this.message = "This is my default message.";
});
//Expose the injector outside the angular app.
app.run(function($injector, $window) {
$window.angularInjector = $injector;
});
//Then use the injector to get access to the service.
//Make sure to wrap the code in a `$apply()` so an
//AngularJS digest cycle will run
function nonAngularEventHandler() {
angularInjector.invoke(function(myService, $rootScope) {
$rootScope.$apply(function() {
myService.message = "Now this is my message."
});
});
}
Edit: Alternatively, simplify the call like so.
//Instead of exposing the $injector directly, wrap it in a function
//which will do the $apply() for you.
app.run(function($injector, $window, $rootScope) {
$window.callInMyAngularApp = function(func) {
$rootScope.$apply(function() {
$injector.invoke(func);
});
}
});
//Then call that function with an injectable function like so.
function nonAngularClick() {
callInMyAngularApp(function(myService) {
myService.message = "Now this is my message."
});
}
//And remember if you're minifying, you'll want the minify-safe
//version of the injectable function like this
function nonAngularClick() {
callInMyAngularApp(['myService', function(myService) {
myService.message = "Now this is my message."
}]);
}
Update: (last one I promise!)
The above will work fine, but you might want to consider exposing a well-defined API instead of a generic injectable interface. Consider the following.
//Now I have a limited API defined in a service
app.service("myExternalApi", function($rootScope, myService) {
this.changeMyMessage = function(message) {
$rootScope.$apply(function() {
myService.message = message;
});
};
});
//And I just expose that API
app.run(function($window, myExternalApi) {
$window.myExternalApi = myExternalApi;
});
//And the call from outside of angular is much cleaner.
function nonAngularClick() {
myExternalApi.changeMyMessage("Now this is my message.");
}
I was able to get access to the controller using answer from this post - https://stackoverflow.com/a/21997129/7411342
var Chat = angular.element(document.querySelector('#chatModule')).scope();
if(!Chat) return;
if(Chat.chatCtrl.initChatFlag) {
Chat.chatCtrl.setInformation();
}else{
console.log('Chat has not been initialized');
}
Assume there is a module with only one factory (the shared service).
angular.module('sharedService', [])
.factory('sharedSrv', sharedService)
function sharedService() {
var number;
return {
getNum: getNumber
};
function getNumber() {
return number;
}
function setNumber(i) {
number = i;
}
}
I saw we can inject shared services by passing a dependency into module
angular.module('app', ['sharedService'])
.controller('theCtrl', function(sharedSrv) {
var self = this;
self.setSharedNumber = sharedSrv.setNumber;
}
However, How inject a shared service if controller use services from his own module?
angular.module('app', ['sharedService'])
.controller('theCtrl', theCtrlFun)
.service('theSrv', theSrvFun)
theCtrlFun.$inject = ['theSrv']
function theCtrlFun(localSrv) {
// How call sharedService ?
}
function theSrvFun() {
// Some fantastic features.
}
Thank you for your help.
You shouldn't be injecting service module as variable, you need to pass the name of module as string
angular.module('app', [sharedService])
.controller('theCtrl', function(sharedSrv) {
should be
angular.module('app', ['sharedService'])
.controller('theCtrl', function(sharedSrv) {
OR you could follow Inline Array annotation of DI
angular.module('app', ['sharedService'])
.controller('theCtrl', ["sharedSrv", function(sharedSrv) {
//code here
}]);
Remove sharedService from statement angular.module('app', [sharedService]) as your shared services are not part of this module.
You need to inject the theSrv and not theSrvFun in the statement theCtrlFun.$inject = ['theSrv']
In the theCtrlFun function just call the method of this service.
e.g someMethod is defined on the theSrv service,you need to call like this
theSrv.someMethod();
e.g.
angular.module('app', [])
.controller('theCtrl', theCtrlFun)
.service('theSrv', theSrvFun)
theCtrlFun.$inject = ['theSrv']
function theCtrlFun(localSrv) {
theSrv.someMethod(); // calll in this way
}
function theSrvFun() {
// Some fantastic features.
}
Its better to define DI using Array Annotation
angular.module('app', [])
.controller('theCtrl', ['theSrv', function(theSrv) {
theSrv.callMe();
}])
.service('theSrv', function() {
this.callMe = funtion() {
}
})
Try this:
angular.module('app', [])
.controller('theCtrl', theCtrlFun)
.service('theSrv', theSrvFun)
theCtrlFun.$inject = ['theSrv']
function theCtrlFun(localSrv) {
// How call sharedService ?
console.log(localSrv)
}
function theSrvFun() {
// Some fantastic features.
}
plnkr: http://plnkr.co/edit/lSosJbYfG6tuF4pCXYqR?p=preview
I have a simple controller that works just fine:
app.controller('IndexController', ['$scope', obj.indexPage]);
var obj = {};
obj.indexPage = function ($scope) { // do controller stuff };
I also have an event function that i want to use to load/create/instantiate this controller:
// some callback, doesn't really matter
app.onPage('index', function () {
// load and run controller logic in here
app.controller('IndexController', ['$scope', obj.indexPage]);
}, obj);
there are some issues, like Argument 'IndexController' is not a function, got undefined
Any ideas?
my solution:
app.controller('IndexController', ['$scope', function ($scope) {
var obj = {};
obj.indexPage = function (data) {
// do controller stuff
};
app.onPage('index', function (data) {
obj.indexPage(data);
}, obj);
});
Due to how the angular module system works, you can't instantiate controllers asynchronously like that. You can however, use the $controller service to create controllers on the fly. The same technique below is often used in unit testing.
For example:
angular.module('app', [])
.controller('MyCtrl', function($rootScope, CtrlFactory){
var dynamicCtrl = CtrlFactory.create({$scope: $rootScope.$new()});
console.log(dynamicCtrl.method()); //-> 123
})
.factory('CtrlFactory', function($controller) {
return {
create: function(locals) {
return $controller(
//this is the constructor of the new controller
function($scope){
console.log('Dynamic controller', $scope);
this.method = function() { return 123; };
},
//these are the injected deps
locals
);
}
};
})
For some example usage in a unit testing context, see: https://docs.angularjs.org/guide/controller.
I'll add that you may want to reconsider your reasons for doing this--I can't say I've seen $controller used outside testing.
app.onPage('index', function () {
app.controller('IndexController', obj.indexPage); // this would load the controller to the module
$controller('IndexController', { $scope: $scope }); // This would instantiate the controller, NOTE: $controller service should be injected
}, obj);
I'm trying to test a controller that uses a service. When I run the following test and breakpoint within the inject function, the actual service is injected instead of the mock service. Why is the service defined through the $provider.factory not being injected?
"use strict";
describe("contestantController", function () {
var dataService, rootScope, scope, passPromise, contestantController;
beforeEach(function(){
module(function ($provide) {
//mock service
$provide.factory('contestantDataService', ['$q', function ($q) {
function save(data){
if(passPromise){
return $q.when();
} else {
return $q.reject();
}
}
function getData() {
if (passPromise) {
return $q.when(smallDataSet());
} else {
return $q.reject();
}
}
return {
addContestant: save,
getContestants: getData,
};
}]);
});
module('contestantApp');
});
beforeEach(inject(function ($rootScope, $controller, contestantDataService) {
rootScope = $rootScope;
scope = $rootScope.$new();
dataService = contestantDataService;
spyOn(dataService, 'getContestants').and.callThrough();
contestantController = $controller('contestantController', {
$scope: scope,
contestantDataService: dataService
});
}));
it('should call getContestants method on contestantDataService on calling saveData', function () {
passPromise = true;
rootScope.$digest();
expect(dataService.getContestants).toHaveBeenCalled();
expect(scope.contestants).toEqual(smallDataSet());
});
});
Tyler's answer will work, but the reasoning is a little off.
The module() function simply registers modules or module initialization functions that the inject() function will later use to initialize the Angular injector. It in no way links the mock service with the module.
Your code doesn't work because order in which the services are registered matters. Your code first registers a mock contestantDataService, then registers the contestantApp module, which contains its own contestantDataService, overwriting the registered mock. If you just move the module('contestantApp') call to the top, it should work as expected.
So this means that the two blocks below are equivalent and will both work...
beforeEach(function(){
module('contestantApp');
module(function ($provide) {
...
});
);
and
beforeEach(function(){
module('contestantApp', function ($provide) {
...
});
);
Thx for #Yunchi . You pointed out my mistake.
You should invoke the module function before you mock the service contestantDataService.
Just update your code into this way,
//make sure contesttantDataService exist in this module in your production environment.
var dataService, rootScope, scope, passPromise, contestantController;
beforeEach(function(){
//Add module name here which means we define the mockService under this module
module('contestantApp', function ($provide) {
//mock service
$provide.factory('contestantDataService', ['$q', function ($q) {
function save(data){
if(passPromise){
return $q.when();
} else {
return $q.reject();
}
}
function getData() {
if (passPromise) {
return $q.when(smallDataSet());
} else {
return $q.reject();
}
}
return {
addContestant: save,
getContestants: getData,
};
}]);
});
beforeEach(inject(function ($rootScope, $controller, contestantDataService) {
rootScope = $rootScope;
scope = $rootScope.$new();
dataService = contestantDataService;
spyOn(dataService, 'getContestants').and.callThrough();
//no need to manually inject to our controller now.
contestantController = $controller('contestantController', {
$scope: scope
});
}));
Instead of using $provide.factory I think it's better to use $provide.value in our unit test. Because we only need to make sure the contestantDataService is an object and have the function.
Here are some similar questions you can check,
Injecting a mock into an AngularJS service.
Mock a service in order to test a controller.
Here is the jsfiddle I just created.
Have fun. : )
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.