angularjs call a service injecting in the parent in the children - angularjs

I'm finding a way to call a service
injecting in the parent from within a children.
I ended up with
<div ng-controller="ParentController">
<div ng-controller="ChildController">{{my}}</div>
</div>
<script>
var app = angular.module('myApp', []);
app.factory('Data',function(){
return {show:function(msg){return msg;}};
});
app.controller('ParentController',function($scope,Data){
$scope.shareService = Data;
});
app.controller('ChildController',function($scope){
$scope.my = $scope.$parent.shareService.show('Hey');
});
</script>
I'm wondering if could be a good practice (may be it's not very handy) or not
or if there is a better way.

If the service is independent of the parent controller, you can inject the service directly in the child controller like you did in parent controller. Why create a dependency on the parent scope.
Services by definition are meant to be shared across controllers, directive and other services.

Related

Access view controller data from app controller in Angular

I use ng-view to display a view inside my main web page. This view has it's own controller and in there I populate ui-grid data. In my outer page I have import/export buttons which seem like they use the app's controller. So how can I access the views data (it's grid object) inside it's controller from the app's controller?
I'm using Angular 1.
<body ng-app="myApp" ng-controller="myController" style="background-color: yellow;">
<!-- the header that the entire app will use no matter what view is displayed -->
<div ng-include="'Views/header.htm'"></div>
<div style="height: 100%;" ng-view></div>
</body>
I have a button inside header.htm. When it's pressed it seems to be at the myApp scope as that's the function that gets raised. Inside that I need to tell whatever view controller is loaded to do something.
You can use $broadcast service to let your view controller know about click event in app controller.
angular.module('app').controller('AppController', ['$scope', '$rootScope', function($scope, $rootScope){
$scope.exportList = function() {
$rootScope.$broadcast('exportData', { object: test}); // If data needs to be passed you can pass it in event
};
}])
angular.module('app').controller('ViewController', ['$rootScope', function($rootScope){
$rootScope.$on('exportData', function(event, data){
if(data.object) {
// Export logic here
}
});
}])
Call exportLink function on ng-click in your view.
You have mentioned in question that your data is populating in ui-grid from ViewController, so you don't need parameters to be passed from view as you already have it in your $scope variable. If needed, use event argument as shown.
You can use broadcast to send event to the child scope and listen back using on. There's another one , $emit. Since you want to pass the event/messages to the child scope/downward you must use broadcast, $emit is the reverse.
Keep in mind : $emit dispatches an event upwards through the scope hierarchy, while $broadcast dispatches an event downwards to all child scopes
myController.js
$scope.buttonClick = function() {
$rootScope.$broadcast('pass-event');
}
Received event
ViewController.js (eg)
$rootScope.$on('pass-event', function (event, args) {
// do what you want to do
});

Can't access $parent from ng-included controller

I'm pretty new to Angular and I'm trying to build an app.
I use ng-include to insert my view, depending on the currentURL variable of my main controller.
When I try to access the main controller via $parent from the ng-included file, all I get is undefined.
My goal is to change the currentURL variable to update the view.
Here is my code:
index.html
<body ng-controller="mainCtrl as main">
currentURL : {{main.currentURL}}
<div ng-include="main.currentURL"></div>
<script src="/vendors/angular.min.js"></script>
<script src="/modules/login.js"></script>
<script src="app.js"></script>
</body>
app.js
angular
.module('mcaApp', ['login'])
.controller('mainCtrl', mainCtrl);
function mainCtrl() {
var vm = this,
baseURL = 'views/';
vm.currentURL = baseURL + 'login.html';
}
views/login.html
<div ng-controller="loginCtrl as login">
<h1>LOGIN</h1>
</div>
modules/login.js
angular
.module('login', [])
.controller('loginCtrl', loginCtrl);
function loginCtrl() {
var vm = this;
console.log(vm.$parent); // undefined
}
As you want to access mainCtrl which was inside loginCtrl controller then you need use $parent to access parent controller scope.
But the thing is you are loading loginCtrl controller view using ng-include so your controller is loaded in the child scope of the mainCtrl, because ng-include create a child scope from current scope.
For that reason you need use $parent.$parent to access mainCtrl scope from loginCtrl
Code
function loginCtrl($scope) {
var vm = this;
console.log($scope.$parent.$parent); // this would contain mainCtrl
}
Better approach would be to use controllerAs syntax or follow dot rule while defining objects so that prototypal inheritance gets followed.

Different parent controllers for a group of views

I am migrating an AngularJS multiple-page app to a single-page app, and I am having some trouble to replicate the following behaviour:
Each HTML file has a different base controller and a ng-view. For example, file1.html looks like this:
<body ng-controller="BaseCtrl1">
<!-- Routes to /view11, /view12, etc. with their corresponding controllers -->
<div ng-view></div>
</body>
<script src="file1.js"></script>
file2.html uses BaseCtrl2, routing to /views21, /view22 and so on. Each of this controllers initialize the scope, and the corresponding subset of views share this part of the model:
file1.js:
module.controller('BaseCtrl1', function($scope, ServiceA, ServiceB, ServiceC) {
// Populate $scope with ServiceN.get() calls
ServiceA.get(function(response) {
$scope.foo = response.results;
});
});
// ...
file2.js:
module.controller('BaseCtrl2', function($scope, ServiceX, ServiceY) {
// Populate $scope with ServiceN.get() calls
});
// ...
However, with a single-page app I cannot use a fixed parent controller (declared in the body element) for each different group of views. I have tried using the $controller service like in the answer of this question, but I need to inject all the dependencies of the parent in the child controller, and does not look like a neat solution at all:
module.controller('View11Ctrl', function($scope, ServiceA, ServiceB, ServiceC) {
$controller('BaseCtrl1', {/* Pass al the dependencies */});
});
module.controller('View12Ctrl', function($scope, ServiceA, ServiceB, ServiceC) {
$controller('BaseCtrl1', {/* Pass al the dependencies */});
});
I would like to know if there is a way to replicate the original behaviour by initializing a "common" part of the scope of a group of views, and maintain it when changing the route.
You can use $injector.invoke() service method to achieve this.
module.controller('View11Ctrl', function($scope, ServiceA, ServiceB, ServiceC) {
$injector.invoke(BaseCtrl1, this, { $scope: $scope });
}
The third argument is defined as:
If preset then any argument names are read from this object first,
before the $injector is consulted.
This way you only need to pass the locals to your base controller that is specific to your child controller, and all other base controller dependencies will be resolved using the normal $injector DI.

Dynamically loading controllers with their own scopes

I'm having a problem which I'm not sure whether is a down to a limitation of Angular (possibly) or a limitation of my knowledge of Angular (probably).
I am trying to take an array of controllers, and dynamically create/load them. I have a prototype working to the point where the controllers run and the root scope can be accessed, but I cannot dynamically attach ng-controller to divs in order to encapsulate the controllers into their own local scopes.
The problem is that the templates are bound to the root scope but not to their own scopes.
My example will hopefully explain my quandary better.
JSFiddle: http://jsfiddle.net/PT5BG/22/ (last update 16:30 BST)
It may not make sense why I am doing it this way, but I have pulled this concept out of a larger system I am creating. In case you have other suggestions, these are the laws by which I am bound:
Controllers cannot be hard-coded, they must be built from an array
Scopes cannot be shared between controllers, they must have their own scopes
The docs on AngularJS are not exactly comprehensive so I'm hoping someone here can help!
You can just pass the controller name through and use the $controller service and pass the locals through to it. You'll need some sort of ModuleCtrl thing to co-ordinate all this. Here is a basic example that does what you want.
http://jsfiddle.net/PT5BG/62/
angular.module('app', [])
.controller('AppCtrl', function ($scope, $controller) {
$scope.modules = [
{ name: "Foo", controller: "FooCtrl" },
{ name: "Bar", controller: "BarCtrl" }]
})
.controller('ModuleCtrl', function ($scope, $rootScope, $controller) {
$controller($scope.module.controller, { $rootScope: $rootScope, $scope: $scope });
})
.controller('FooCtrl', function ($rootScope, $scope) {
$rootScope.rootMessage = "I am foo";
$scope.localMessage = "I am foo";
console.log("Foo here");
})
.controller('BarCtrl', function ($rootScope, $scope) {
$rootScope.rootMessage = "I am bar";
$scope.localMessage = "I am bar";
console.log("Bar here");
});
The way I finally got around this was quite simple, it was just a case of working it out.
So I have a list of modules, that I get from an API, and I want to instantiate them. I include the template file by building the path via convention, like so:
<!-- the ng-repeat part of the code -->
<div ng-repeat="module in modules">
<ng-include src="module.name + '.tpl.html'"></ng-include>
</div>
In each of the modules template files, I then declare the ng-controller and I declare a method to fire in ng-init. As the template is still within the ng-repeat loop, it has access to module, which has the data we want to pass to the child controller. ng-init runs on the local scope, so we pass in the module object:
<!-- the template of the module -->
<div ng-controller="ModuleCtrl" ng-init="init(module)">
...
</div>
And then we store it on the local scope and there you go, injected the object.
/* the controller of the module */
.controller('ModuleCtrl', function ($scope) {
$scope.init = function(module) {
this.module = module;
};
// this.module is now available inside the controller
});
It took a bit of hacking but it works perfectly for now.

Communication between modules in AngularJS

I guess it is possible to have many angular-modules attached to different regions within one shellpage.
But can modules in AngularJS "talk" to each other?
If yes, how?
There are various ways module can interact or share information
A module can be injected into another module, in which case the container module has access to all elements of the injected module. If you look at angular seed project, modules are created for directive, controllers, filters etc, something like this
angular.module("myApp", ["myApp.filters", "myApp.services", "myApp.directives", "myApp.controllers"])
This is more of a re usability mechanism rather than communication mechanism.
The second option is as explained by #Eduard would be to use services. Since services are singleton and can be injected into any controller, they can act as a communication mechanism.
As #Eduard again pointed out the third option is to use parent controller using $scope object as it is available to all child controllers.
You can also inject $rootScope into controllers that need to interact and use the $broadcast and $on methods to create a service bus pattern where controllers interact using pub\sub mechanism.
I would lean towards 4th option. See some more details here too What's the correct way to communicate between controllers in AngularJS?
Using the service mechanism to communicate among module's controllers.
(function () {
'use strict';
//adding moduleB as dependency to moduleA
angular.module('Myapp.moduleA', ['Myapp.moduleB'])
.controller('FCtrl', FCtrl)
.service('sharedData', SharedData);
//adding the dependency shareData to FCtrl
FCtrl.$inject = ['sharedData'];
function FCtrl(sharedData) {
var vm = this;
vm.data = sharedData.data;
}
//shared data service
function SharedData() {
this.data = {
value: 'my shared data'
}
}
//second module
angular.module('Myapp.moduleB', [])
.controller('SCtrl', SCtrl);
SCtrl.$inject = ['sharedData'];
function SCtrl(sharedData) {
var vm = this;
vm.data = sharedData.data;
}
})();
And the HTML as follows:
<html ng-app="firstModule">
<body>
<div ng-controller="FCtrl as xyz">
<input type=text ng-model="xyz.data.value" />
</div>
<div ng-controller="SCtrl as abc">
<input type=text ng-model="abc.data.value" />
</div>
</body>
</html>
You can use services and controllers inheritance (explained here http://docs.angularjs.org/guide/dev_guide.mvc.understanding_controller)
in any case, you shuold consider not having your controllers tighlty coupled.

Resources