I have a project where a lot of the models are going to be managed by almost the same controller-code with the only exception that they are calling different services.
The way I'm handling this now is by instantiating a Crud-Controller with common code into every custom controller and then redirecting the service-call by changing the variable inside the custom controller. I.e.
vm.service.get() inside the Crud-Controller changes by setting vm.service = teamService; in a custom controller.
This is how I instantiate the Crud-Controller into my custom controllers at the moment:
$injector.invoke(Crud, this, {$scope:$scope});
This works fine, however I don't know if this is the right way to share common controller code. Maybe it is possible to instantiate a service for this use? Because the question I have (if my way of doing it is correct), how do I access other controllers while using IIFE? Right now I am not using IIFE since I have not figured out a way to do so.
I have tried with angular.module('app').controller('Crud') but it does not work.
I.e: How do I get access to the PrimaryCtrl's edit function without using $injector or relying on the $scope inheritance?
http://jsfiddle.net/tcVhN/62/
It looks like your example uses Angular 1.0.x that supports global controllers out of the box. That's how it would be done with more recent Angular, without going too deep into the perils of JS inheritance.
'use strict';
(function (root, angular) {
root.ctrls = root.ctrls || {};
var primaryCtrl = function ($scope) {
var self = this;
console.log('primaryCtrl constructor', self, $scope);
};
primaryCtrl.prototype = {
items: ['Item 1', 'Item 2'],
edit: function (item) {
//do stuff
}
};
primaryCtrl.$inject = ['$scope'];
root.ctrls.primaryCtrl = primaryCtrl;
})(this, angular);
(function (root, angular) {
root.ctrls = root.ctrls || {};
var secondaryCtrl = function ($scope) {
var self = this;
console.log('secondaryCtrl constructor', self, $scope);
};
secondaryCtrl.prototype = angular.extend({},
root.ctrls.primaryCtrl.prototype,
{
items: ['Stuff 1', 'Stuff 2']
}
);
secondaryCtrl.$inject = ['$scope'];
root.ctrls.secondaryCtrl = secondaryCtrl;
})(this, angular);
var app = angular.module('app',[]);
app.controller('PrimaryCtrl', ctrls.primaryCtrl);
app.controller('SecondaryCtrl', ctrls.secondaryCtrl);
and
<div ng-controller="PrimaryCtrl as primary">
<p ng-repeat="item in primary.items">{{item}}</p>
</div>
<div ng-controller="SecondaryCtrl as secondary">
<p ng-repeat="item in secondary.items">{{item}}</p>
</div>
You may also check Angular Classy, which brings opinionated but viable extending syntax to Angular.
Related
I'm trying to use a simple Angular JS app to load data from a JSON file to a website but it does not work.
The JSON file is:
{"a": "a"}
The Angular app is:
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(data) {
vm.data = data;
});
}])
.service("ser", function() {
this.getInfo = function() {
return $.get("models/model.json");
};
});
The HTML is:
<div ng-controller="ctrl as ctrl">
<p>{{ctrl.data.a}}</p>
</div>
I'm not getting any console errors. I think the problem is related to the lexical scoping for the controller due to the asynchronous getInfo().then() call in the controller, I checked vm inside the function and it is being loaded correctly but doesn't seem to change the ctrl object or Angular is not updating when it does.
I'm serving the app locally.
It works sometimes but most times it doesn't. I can get it to work using $scope but I'm trying to figure out why it's not working now.
It appears you are using jQuery for the ajax. If you modify the scope outside of angular context you need to notify angular to run a digest
Change to using angular $http to avoid such issues
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(response) {
vm.data = response.data;
});
}])
.service("ser", ['$http', function($http) {
this.getInfo = function() {
return $http.get("models/model.json");
};
}]);
DEMO
If it works with $scope that means that without it, Angular is not aware that you performed an asynchronous operation.
I think the following line is using jQuery: return $.get("models/model.json");
So even if you get your data from your function getInfo, it isn't synchronized with the view via vm.data = data;
I'm facing a problem using Dependency Injection between modules.
I have a module that implements a directive I need to use in other applications.
I added the dependency from this module in another app declaration like this:
angular.module('mainApp', ['ngRoute', 'directiveApp']);
However, the methods implemented into directiveApp.controller, doesn't seem to be visible from a page of MainApp, since the directive can't run a method it needs from their controller.
I know it's a little confusing, so I put an example in this plunker, that shows the problem I'm facing.
When you inject another module into your own, the controllers and directives it implements become available, but you need to use them properly.
The way you are trying to do is not possible, you can do something like this:
http://plnkr.co/edit/peHH226vxtkI48RFZ3Eq?p=preview
<body ng-controller="MainCtrl">
<h1>Value: {{name}}!</h1>
<button ng-click="mainModule()">Call a function in the main module!</button>
<div ng-controller="SubCtrl">
{{name}}
<button ng-click="dependentModule()">Call a function in the dependent module!</button>
</div>
</body>
But notice that you have two different $scopes and consequently two different name variables.
That means your dependentModule() function belongs to your SubCtrl and you can only use it inside its own $scope
That's not recommended, but if you really need to, you can use the other controllers on your own methods and then copy the results:
http://plnkr.co/edit/ranK9n08NNVuSKIGX15G?p=preview
main.controller("MainCtrl", function($scope, $controller) {
$scope.name = "Initial value";
$scope.mainModule = function() {
$scope.name = "a function in the same module";
};
$scope.bridgeFunction = function(){
// Create a new scope
var injectedScope = $scope.$new();
// Use it on the other controller
$controller('SubCtrl',{$scope : injectedScope });
// Call the methdo on the controller
testCtrl1ViewModel.dependentModule(); //And call the method on the newScope.
// Copy the result from that scope into your own
$scope.name = testCtrl1ViewModel.name;
}
});
A third option is to merge the two scopes, although this can get very messy, it is possible:
http://plnkr.co/edit/1NKStMuYy0e00dhuWKUD?p=preview
main.controller("MainCtrl", function($scope, $controller) {
$scope.name = "Initial value";
//This can get very messy, but it is possible to merge the two scopes:
$controller('SubCtrl',{$scope : $scope });
$scope.mainModule = function() {
$scope.name = "a function in the same module";
};
});
Hope that helps
So let's say I have a controler that dppends on a news service. Whenever news is published, the controller would like to diisplay the latest news. I'd rather not use $broadcast and $on, because this does weird things to the way components are coupled. Components which don't depend on news could still listen for these events.
So, here's what I'd like to be able to do:
myApp.controller("myController", ["news", function(news){
news.onPublish.addListener(function(story){
... Show the latest juicy story.
});
}]);
news.onPublish would be an Event instance, with Event defined as follows:
Event = function(){
var listeners = [];
this.addListener = function(l){
listeners.push(l);
}
this.trigger = function(){
var params = arguments
listeners.map(function(l){
l.apply(undefined, params);
});
}
return this
}
Is this a good way to implement communication between services and other components? Also, would it be good to call $rootScope.$apply at the end of Event.trigger so that any changes the listeners made will be picked up?
The best is to write common data storing factories that do the job for you. Here's a working example: http://jsfiddle.net/9L5xL8sx/ which shows how this works. The NewsService factory can be used across several Angular modules, and within the same module, as shown in my example.
Here's the JS:
var app = angular.module("TestSharing", []);
app.factory("NewsService", [function() {
var articles = [];
var makeNews = function (text) {
articles.push({text: text});
};
var getNews = function() {
return articles;
};
return {
get: getNews,
make: makeNews
};
}]);
app.controller("FirstCtrl", ["$scope", "NewsService", function($scope, NewsService) {
$scope.articles = function () {
return NewsService.get();
};
}]);
app.controller("SecondCtrl", ["$scope", "NewsService", function($scope, NewsService) {
$scope.articletext = "";
$scope.submit = function () {
NewsService.make($scope.articletext);
};
}])
The HTML:
<div ng-app="TestSharing">
<div ng-controller="FirstCtrl">
<span ng-repeat="article in articles()">{{article.text}}<br/></span>
</div>
<div ng-controller="SecondCtrl">
<input type="text" ng-model="articletext"/>
<button ng-click="submit()">Make some news</button>
</div>
</div>
Also, services like these can also share events. For example, if you'd used the factory to expose an object called somethingNew, which merely contained if something new had happened in one of the Controllers it had been shared in, you could achieve the same effect. The idea would be to only listen for changes where you'd want (using something like $scope.$watch(NewsService.somethingNew, function (now, then) {…})) and that would be just as easy.
I have seen a few exmaples on stack overflow about this ng-init issue, although I cant seem to find one which references it with the use of a controller.
I have called the function in the controller by having the following in the html file
<div class="tab-container" ng-controller = "ExampleController" ng-init = "init()" >
In the controller:
$scope.init = function(){
alert("do something");
};
It does run, but it runs before the components have loaded on the screen.
Am i missing something?
Thanks
ng-init is supposed to work like this, because it's used to initialize data.
A very simple example:
<ul ng-init="list = [1,2,3,4]">
<li ng-repeat="l in list"></li>
</ul>
If you are trying to run something while your controller loads, it's actually much simpler than you thought:
app.controller('mainCtrl', function ($scope) {
var init = function ($scope) {
// do whatever you need to do to initialize your controller
$scope.someData = ["Hey", "I'm", "Alive"]
$scope.otherData = localStorage.getItem('myBackup')
}
init()
})
Or even simpler, if you don't need the function (no closures or whatever)
app.controller('mainCtrl', function ($scope) {
// do whatever you need to do to initialize your controller
$scope.someData = ["Hey", "I'm", "Alive"]
$scope.otherData = localStorage.getItem('myBackup')
})
Edit - assuming you're using ngView:
To have the code run on when the page is fully loaded you should set a watcher on the event $viewContentLoaded, like this:
$scope.$on('$viewContentLoaded', function(){
//Here your view content is fully loaded !!
});
app.controller('mainCtrl', function ($scope) {
// This event is triggered when the view has finished loading
$scope.$on('$viewContentLoaded', function() {
$scope.someData = ["Hey", "I'm", "Alive"]
$scope.otherData = localStorage.getItem('myBackup')
})
})
another option is using jquery. It would fit if you depend on many elements. But make sure to load jquery with a version of your choice to project.
loading jquery (insert version where it's ...):
<script src="https://code.jquery.com/jquery-..."></script>
the js code:
$(document).ready(function() {
alert("do something");
});
I am trying to call a method of second controller in first controller by using scope variable. This is a method in my first controller:
$scope.initRestId = function(){
var catapp = document.getElementById('SecondApp');
var catscope = angular.element(catapp).scope();
catscope.rest_id = $scope.user.username;
catscope.getMainCategories();
};
I am able to set the value of rest_id but I cannot call getMainCategories for some reason. The console shows this error:
TypeError: Object # has no method 'getMainCategories'
Is there a way to call the above method?
Edit:
I used the following approach to load two apps at the same time;
angular.bootstrap(document.getElementById('firstAppID'), ['firstApp']);
angular.bootstrap(document.getElementById('secondAppID'), ['secondApp']);
I could definitely use a service here, but I wanted to know if there are any other options to do the same!
The best approach for you to communicate between the two controllers is to use events.
Scope Documentation
In this check out $on, $broadcast and $emit.
In general use case the usage of angular.element(catapp).scope() was designed for use outside the angular controllers, like within jquery events.
Ideally in your usage you would write an event in controller 1 as:
$scope.$on("myEvent", function (event, args) {
$scope.rest_id = args.username;
$scope.getMainCategories();
});
And in the second controller you'd just do
$scope.initRestId = function(){
$scope.$broadcast("myEvent", {username: $scope.user.username });
};
Edit: Realised it was communication between two modules
Can you try including the firstApp module as a dependency to the secondApp where you declare the angular.module. That way you can communicate to the other app.
Here is good Demo in Fiddle how to use shared service in directive and other controllers through $scope.$on
HTML
<div ng-controller="ControllerZero">
<input ng-model="message" >
<button ng-click="handleClick(message);">BROADCAST</button>
</div>
<div ng-controller="ControllerOne">
<input ng-model="message" >
</div>
<div ng-controller="ControllerTwo">
<input ng-model="message" >
</div>
<my-component ng-model="message"></my-component>
JS
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
By the same way we can use shared service in directive. We can implement controller section into directive and use $scope.$on
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
And here three our controllers where ControllerZero used as trigger to invoke prepForBroadcast
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}
The ControllerOne and ControllerTwo listen message change by using $scope.$on handler.
Each controller has it's own scope(s) so that's causing your issue.
Having two controllers that want access to the same data is a classic sign that you want a service. The angular team recommends thin controllers that are just glue between views and services. And specifically- "services should hold shared state across controllers".
Happily, there's a nice 15-minute video describing exactly this (controller communication via services): video
One of the original author's of Angular, Misko Hevery, discusses this recommendation (of using services in this situation) in his talk entitled Angular Best Practices (skip to 28:08 for this topic, although I very highly recommended watching the whole talk).
You can use events, but they are designed just for communication between two parties that want to be decoupled. In the above video, Misko notes how they can make your app more fragile. "Most of the time injecting services and doing direct communication is preferred and more robust". (Check out the above link starting at 53:37 to hear him talk about this)