Syncing data between controllers through a service - javascript

From this stackoverflow question, my understanding is that I should be using services to pass data between controllers.
However, as seen in my example JSFiddle, I am having trouble listening to changes to my service when it is modified across controllers.
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.status = App.data.status;
$scope.$watch('App.data.status', function() {
$scope.status = App.data.status;
});
})
.controller('Ctrl2', function ($scope, App) {
$scope.status = App.data.status;
$scope.$watch('status', function() {
App.data.status = $scope.status;
});
})
.service('App', function () {
this.data = {};
this.data.status = 'Good';
});
In my example, I am trying to subscribe to App.data.status in Ctrl1, and I am trying to publish data from Ctrl1 to App. However, if you try to change the input box in the div associated with Ctrl2, the text does not change across the controller boundary across to Ctrl1.

http://jsfiddle.net/VP4d5/2/
Here's an updated fiddle. Basically if you're going to share the same data object between two controllers from a service you just need to use an object of some sort aside from a string or javascript primitive. In this case I'm just using a regular Object {} to share the data between the two controllers.
The JS
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.localData1 = App.data;
})
.controller('Ctrl2', function ($scope, App) {
$scope.localData2 = App.data;
})
.service('App', function () {
this.data = {status:'Good'};
});
The HTML
<div ng-controller="Ctrl1">
<div> Ctrl1 Status is: {{status}}
</div>
<div>
<input type="text" ng-model="localData1.status" />
</div>
<div ng-controller="Ctrl2">Ctrl2 Status is: {{status}}
<div>
<input type="text" ng-model="localData2.status" />
</div>
</div>
Nothing wrong with using a service here but if the only purpose is to have a shared object across the app then I think using .value makes a bit more sense. If this service will have functions for interacting with endpoints and the data be sure to use angular.copy to update the object properties instead of using = which will replace the service's local reference but won't be reflected in the controllers.
http://jsfiddle.net/VP4d5/3/
The modified JS using .value
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, sharedObject) {
$scope.localData1 = sharedObject;
})
.controller('Ctrl2', function ($scope, sharedObject) {
$scope.localData2 = sharedObject;
})
.value("sharedObject", {status:'Awesome'});

I agree with #shaunhusain, but I think that you would be better off using a factory instead of a service:
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.localData1 = App.data;
})
.controller('Ctrl2', function ($scope, App) {
$scope.localData2 = App.data;
})
.factory('App', function () {
var sharedObj = {
data : {
status: 'Good'
}
};
return sharedObj;
});
Here are some information that might help you understand the differences between a factory and a service: When creating service method what's the difference between module.service and module.factory

Related

Angularjs ng-controller with resolve

I've ran into problem with ng-controller and 'resolve' functionality:
I have a controller that requires some dependency to be resolved before running, it works fine when I define it via ng-route:
Controller code looks like this:
angular.module('myApp')
.controller('MyController', ['$scope', 'data', function ($scope, data) {
$scope.data = data;
}
]
);
Routing:
...
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
...
when I go to /someUrl, everything works.
But I need to use this controller in other way(I need both ways in different places):
<div ng-controller="MyController">*some html here*</div>
And, of course, it fails, because 'data' dependency wasn't resolved. Is there any way to inject dependency into controller when I use 'ng-controller' or I should give up and load data inside controller?
In the below, for the route resolve, we're resolving the promise and wrapping the return data in an object with a property. We then duplicate this structure in the wrapper service ('dataService') that we use for the ng-controller form.
The wrapper service also resolves the promise but does so internally, and updates a property on the object we've already returned to be consumed by the controller.
In the controller, you could probably put a watcher on this property if you wanted to delay some additional behaviours until after everything was resolved and the data was available.
Alternatively, I've demonstrated using a controller that 'wraps' another controller; once the promise from Service is resolved, it then passes its own $scope on to the wrapped controller as well as the now-resolved data from Service.
Note that I've used $timeout to provide a 1000ms delay on the promise return, to try and make it a little more clear what's happening and when.
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><div ng-controller="ResolveController">Using ng-controller: <strong>{{data.data}}</strong></div>',
controller: 'HomeController'
})
.when('/byResolve', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><p>Resolved: <strong>{{data.data}}</strong></p>',
controller: "ResolveController",
resolve: {
dataService: ['Service',
function(Service) {
// Here getData() returns a promise, so we can use .then.
// I'm wrapping the result in an object with property 'data', so we're returning an object
// which can be referenced, rather than a string which would only be by value.
// This mirrors what we return from dataService (which wraps Service), making it interchangeable.
return Service.getData().then(function(result) {
return {
data: result
};
});
}
]
}
})
.when('/byWrapperController', {
template: '<h1>Wrapped: {{title}}</h1><p>{{blurb}}</p><div ng-controller="WrapperController">Resolving and passing to a wrapper controller: <strong>{{data.data ? data.data : "Loading..."}}</strong></div>',
controller: 'WrapperController'
});
})
.controller('HomeController', function($scope) {
$scope.title = "ng-controller";
$scope.blurb = "Click 'By Resolve' above to trigger the next route and resolve.";
})
.controller('ResolveController', ['$scope', 'dataService',
function($scope, dataService) {
$scope.title = "Router and resolve";
$scope.blurb = "Click 'By ng-controller' above to trigger the original route and test ng-controller and the wrapper service, 'dataService'.";
$scope.data = dataService;
}
])
.controller('WrapperController', ['$scope', '$controller', 'Service',
function($scope, $controller, Service) {
$scope.title = "Resolving..."; //this controller could of course not show anything until after the resolve, but demo purposes...
Service.getData().then(function(result) {
$controller('ResolveController', {
$scope: $scope, //passing the same scope on through
dataService: {
data: result
}
});
});
}
])
.service('Service', ['$timeout',
function($timeout) {
return {
getData: function() {
//return a test promise
return $timeout(function() {
return "Data from Service!";
}, 1000);
}
};
}
])
// our wrapper service, that will resolve the promise internally and update a property on an object we can return (by reference)
.service('dataService', function(Service) {
// creating a return object with a data property, matching the structure we return from the router resolve
var _result = {
data: null
};
Service.getData().then(function(result) {
_result.data = result;
return result;
});
return _result;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script>
<div ng-app="myApp">
By ng-controller |
By Resolve |
By Wrapper Controller
<div ng-view />
</div>
Create a new module inside which you have the service to inject like seen below.
var module = angular.module('myservice', []);
module.service('userService', function(Service){
return Service.getData();
});
Inject newly created service module inside your app module
angular.module('myApp')
.controller('MyController', ['$scope', 'myservice', function ($scope, myservice) {
$scope.data = data;
// now you can use new dependent service anywhere here.
}
]
);
You can use the mechanism of the prototype.
.when('/someUrl', {
template : '<div ng-controller="MyController" ng-template="some.html"></div>',
controller: function (data) {
var pr = this;
pr.data = data;
},
controllerAs: 'pr',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
angular.module('myApp')
.controller('MyController', ['$scope', function ($scope) {
$scope.data = $scope.pr.data; //magic
}
]
);
Now wherever you want to use
'<div ng-controller="MyController"></div>'
you need to ensure that there pr.data in the Scope of the calling controller. As an example uib-modal
var modalInstance = $modal.open({
animation: true,
templateUrl: 'modal.html',
resolve: {
data: ['Service', function (Service) {
return Service.getData();
}]
},
controller: function ($scope, $modalInstance, data) {
var pr = this;
pr.data = data;
pr.ok = function () {
$modalInstance.close();
};
},
controllerAs:'pr',
size:'sm'
});
modal.html
<script type="text/ng-template" id="modal.html">
<div class="modal-body">
<div ng-include="some.html" ng-controller="MyController"></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary pull-right" type="button" ng-click="pr.ok()">{{ 'ok' | capitalize:'first'}}</button>
</div>
</script>
And now you can use $scope.data = $scope.pr.data; in MyController
pr.data is my style. You can rewrite the code without PR.
the basic principle of working with ng-controller described in this video https://egghead.io/lessons/angularjs-the-dot
Presuming that Service.getData() returns a promise, MyController can inject that Service as well. The issue is that you want to delay running the controller until the promise resolves. While the router does this for you, using the controller directly means that you have to build that logic.
angular.module('myApp')
.controller('MyController', ['$scope', 'Service', function ($scope, Service) {
$scope.data = {}; // default values for data
Service.getData().then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
Now this works great when using the controller directly, but in your routing example, where you want to delay rendering a page until data is resolved, you are going to end up making two calls to Service.getData(). There are a few ways to work around this issue, like having Service.getData() return the same promise for all caller, or something like this might work to avoid the second call entirely:
angular.module('myApp')
.controller('MyController', ['$scope', '$q', 'Service', function ($scope, $q, Service) {
var dataPromise,
// data might be provided from router as an optional, forth param
maybeData = arguments[3]; // have not tried this before
$scope.data = {}; //default values
// if maybeData is available, convert it to a promise, if not,
// get a promise for fetching the data
dataPromise = !!maybeData?$q.when(maybeData):Service.getData();
dataPromise.then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
I was trying to solve the problem using ng-init but came across the following warnings on angularjs.org
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
So I started searching for something like ng-resolve and came across the following thread:
https://github.com/angular/angular.js/issues/2092
The above link consists of a demo fiddle that have ng-resolve like functionality. I think ng-resolve can become a feature in the future versions of angular 1.x. For now we can work around with the directive mentioned in the above link.
'data' from route resolve will not be available for injection to a controller activated other than route provider. it will be available only to the view configured in the route provider.
if you want the data to the controller activated directly other than routeprovider activation, you need to put a hack for it.
see if this link helps for it:
http://www.johnpapa.net/route-resolve-and-controller-activate-in-angularjs/
Getting data in "resolve" attribute is the functionality of route (routeProvider) , not the functionality of controller.
Key( is your case : 'data') in resolve attribute is injected as service.
That's why we are able fetch data from that service.
But to use same controller in different place , you have fetch data in controller.
Try this
Service:
(function() {
var myService = function($http) {
var getData = function() {
//return your result
};
return {
getData:getData
};
};
var myApp = angular.module("myApp");
myApp.factory("myService", myService);
}());
Controller:
(function () {
var myApp = angular.module("myApp");
myApp.controller('MyController', [
'$scope', 'myService', function($scope, myService) {
$scope.data = myService.getData();
}
]);
//Routing
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: $scope.data,
}
})
}());

Call angular controller's method from outside

Here's the code: http://jsbin.com/rucatemujape/1/edit?html,js,console,output
My question is how do I manually call method changeUser from JavaScript so the output HTML changes?
I can do this by executing (relies on jQuery)
angular.element('body').scope().changeUser({fullName: 'John Doe'});
angular.element('body').scope().$apply()
But I want to know is there any better way?
For example, in knockout.js I can execute viewModel.someFunction() any time and knockout correctly handles this.
Why do I want to do this: because I want be able to change model from browser's console when debugging a code.
Edit: Another reason why I need this it's getting information from Restful Services and updating a model. Yes I can trigger that event by clicking a button with "ng-click" attribute but how to deal with events which are not initiated by user? For example, repeating ations from setInterval or something
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function($scope, $timeout) {
$scope.user = {};
$scope.count = 0;
$scope.changeUser = function(user) {
$scope.user = "MyName";
$scope.count++;
// call function after 1 sec.
$timeout($scope.changeUser, 1000);
};
// initiate function
$scope.changeUser();
});
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script>
</head>
<body ng-controller="MyController"
<span>Hello, {{user}}</span>
<span>count, {{count}}</span>
</body>
</html>
Use ng-click="changeUser()" to call the fucntion
http://jsbin.com/rucatemujape/2/edit
Controllers do not really expose "APIs" outside their own 'scope' (and below). So either you do the Ajax call within the controller or you expose the "user data" to display.
For example: Kostia Mololkin's answer's (in main comments) uses a global variable to share the data.
Angular Way
Here I present a more "angular way": the user is being handled by a "Service". Not clear which direction you wanted exactly. Anyway, this cannot hurt.
Angular works best when you use "data" to "communicate state" instead of "events". So the idea again is to arrange things so your data instance (i.e. "user") is the same between "{{user.fullname}}" and where the Ajax call is taking place. There are many ways to skin a cat and it highly depends on the needs of your application. For example, if you build a singleton service for the ajax call, you can also have the data own by it AND exposed to the controller in some form or another (again, many ways of doing this).
NOTE: If you use angular's system to perform ajax calls (i.e. $http or $resource for instance) then you should never need to manually call "$apply()". Btw, you should "wrap" calls with $apply() instead of calling it "afterwards" reason being to properly handle "throws".
Plunker example with Ajax/Rest call:
http://plnkr.co/edit/SCF2XZCK5KQWkb4hZfOO?p=preview
var myApp = angular.module('myApp', []);
myApp.factory('UserService', ['$http',function($http){
// Extra parent object to keep as a shared object to 'simplify'
// updating a child object
var userData = {};
userData.user = { fullName:'none' };
function loadUserData(userid) {
$http.get('restapi_getuserdata_'+userid+'.json').
success(function(data, status, headers, config) {
// update model
userData.user = data;
}).
error(function(data, status, headers, config) {
console.log("error: ", status);
});
}
return {
userData: userData,
loadUserData: loadUserData
};
}]);
myApp.controller('MyController', ['$scope', 'UserService', '$timeout',
function($scope, UserService, $timeout) {
// shared object from the Service stored in the scope
// there are many other ways, like using an accessor method that is
// "called" within the HTML
$scope.userData = UserService.userData;
}]);
myApp.controller('SomeOtherController', ['UserService', '$timeout',
function(UserService, $timeout) {
// $timeout is only to simulate a transition within an app
// without relying on a "button".
$timeout(function(){
UserService.loadUserData(55);
}, 1500);
}]);
HTML:
<html ng-app="myApp">
...
<body ng-controller="MyController">
<span>Hello, {{userData.user.fullName}}</span>
<!-- simulating another active piece of code within the App -->
<div ng-controller="SomeOtherController"></div>
...
A variant using a getter method instead of data:
http://plnkr.co/edit/0Y8gJolCAFYNBTGkbE5e?p=preview
var myApp = angular.module('myApp', []);
myApp.factory('UserService', ['$http',function($http){
var user;
function loadUserData(userid) {
$http.get('restapi_getuserdata_'+userid+'.json').
success(function(data, status, headers, config) {
console.log("loaded: ", data);
// update model
user = data;
}).
error(function(data, status, headers, config) {
console.log("error: ", status);
user = undefined;
});
}
return {
getCurrentUser: function() {
return user || { fullName:"<none>" };
},
userLoggedIn: function() { return !!user; },
loadUserData: loadUserData
};
}]);
myApp.controller('MyController', ['$scope', 'UserService', '$timeout',
function($scope, UserService, $timeout) {
// getter method shared
$scope.getCurrentUser = UserService.getCurrentUser;
}]);
myApp.controller('SomeOtherController', ['UserService', '$timeout',
function(UserService, $timeout) {
// $timeout is only to simulate a transition within an app
// without relying on a "button".
$timeout(function(){
UserService.loadUserData(55);
}, 1500);
}]);
HTML:
<html ng-app="myApp">
...
<body ng-controller="MyController">
<span>Hello, {{ getCurrentUser().fullName }}</span>
<!-- simulating another active piece of code within the App -->
<div ng-controller="SomeOtherController"></div>
...

update scopes in multiple controllers after $http

I'm struggling to figure out how to do this. Hope anyone can help :)
I have multiple controllers in my Angular app. Like titleCtrl and SettingsCtrl
I have a service which holds a variable like this:
var myVar = {title: 'test', settings: {color: 'black', font: 'verdana'}};
I'm making a $http.get request to update the "myVar" variable from the server.
The question is, how do I update the $scope.title in titleCtrl and $scope.settings in SettingsCtrl AFTER the http request has finished? I know how to do it in a single controller, but how do I update the $scopes in multiple controllers?
Use a watch on that variable in the service. When its updated, then update your values in controller scope. Here's an example:
Inside your controller, you can watch a var myVar on YourService and when it changes, update a variable called myVarInController with the value it changed to.
$scope.$watch(
// This function returns the value being watched.
function() {
return YourService.myVar;
},
// This is the change listener, called when the value returned above changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
$scope.myVarInController = newValue;
}
}
);
Just in you service create a object when you get data from you server copy it to that object, so all your controllers can reference to that object.
Please see here http://plnkr.co/edit/j25GJLTHlzTEVS8HNqcA?p=preview
JS:
var app = angular.module('plunker', []);
app.service('dataSer', function($http) {
var obj = {};
getData = function() {
$http.get("test.json").then(function(response) {
angular.copy(response.data, obj);
});
}
return {
obj: obj,
getData: getData
};
});
app.controller('MainCtrl', function($scope, dataSer) {
$scope.data = dataSer;
$scope.get = function() {
$scope.data.getData()
}
});
app.controller('SecondCtrl', function($scope, dataSer) {
$scope.data = dataSer;
});
HTML:
<div ng-controller="MainCtrl">
<button ng-click="get()">get data</button>
<p>Fist Controller:
<br/>{{ data.obj.title}}</p>
</div>
<div ng-controller="SecondCtrl">
<p>Second Controller:
<br/>{{data.obj.settings}}</p>
</div>
Use both factory and service to pass value to two controllers. This is the only way to pass value
angular.module('mulipleCtrlApp', [])
.service('shareService', function () {
return {
title: 'test',
settings: {
color: 'black',
font: 'verdana'
}
};
})
.controller('titleCtrl', function ($scope, shareService) {
$scope.myVar = shareService;
$scope.testchange = function () {
$scope.myVar.title = 'Completed test';
};
})
.controller('settingCtrl', function ($scope, shareService) {
$scope.myVar = shareService;
});
Egghead Link
Jsfiddler Link example
Make your service return promise object.
Then in controller you can define a success call back to fetch title in one and settings in
another controller once the promise is resolved.
Code to use promises
In your service class:
var factory = {};
var factory.fetchData = function () {
return $http({method: 'GET', url: '/someUrl'});
}
return factory;
In controller 1:
$scope.getData = function(){
factory.fetchData().success(response){
$scope.title = response.title;
}
}
Similarly you can update controller 2, to set settings data.
I've found a better and easier maintainable solution in my opinion. Simply do the following to achieve to-way data-binding between one (or more) controller(s) with a service:
Lets assume you fetch (i.e. $http) and store data in your service (serviceName) in the variable serviceData.
In your controller reference the service like this to achieve to-way data-binding:
$scope.data = serviceName
In your view/html bind to the data properties like this:
<input ng-model="data.serviceData.title">
Thats it! :) When your serviceData variable updates the view/scope does as well. This will work with multiple controllers.

update angular model from a service

angular.module('app.services', []).service("test", function($http, $rootScope){
this.test=function(){
$rootScope.name="test1";
};
};
angular.module('app.controllers', []).controller('TestController', function ($scope, test) {
test.send();
})
I dont get an error but the changes don't get applied to the UI. I tried $scope.apply() and got an error.
We need to tell Angular which modules your module depends on, In our case the main module is app.controllers.
To call service from different model we need tell to controller where is our service:
['app.services']
JS
var appServices = angular.module('app.services', []);
var appCtrl = angular.module('app.controllers', ['app.services']);
appServices
.service("test", function ($http, $rootScope) {
this.send = function () {
$rootScope.name = "test1";
};
});
appCtrl.controller('TestController', function ($scope, test) {
test.send();
});
Demo Fiddle
I think you should change ".service" by ".factory".
As I can see in the creating services docs there are 3 ways of creating custom services. One of then is using factory way, as the following:
var myModule = angular.module('myModule', []);
myModule.factory('serviceId', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
Hope to help.

What's the recommended way to extend AngularJS controllers?

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.

Categories

Resources