Background: Let's suppose for the sake of argument that you have 100,000 views (partials). Let's also suppose you have accompanying view-scoped controllers, and potentially view-scoped services and filters as well. Try to envision an aggregating application that hosts 100,000 disparate small applications.
Issue: When you have "partials" that require accompanying controllers, the typical solution is to do something like this:
$routeProvider.when('/app1', {
templateUrl: 'partials/view1.html',
controller: 'controller1'
});
The controller is typically loaded from index.html via:
<script src="js/directives/Controller1.js"></script>
The problem with this approach is that it doesn't scale. There are solutions out there for dynamically loading controllers, but they still require adding touch points in various config.
Ideal Solution: Ideally - again for very small applications whose numbers are in the 000's, the controller could be loaded dynamically, and from within the partial itself. This would alleviate the need to manage several files and several configuration touch points (not to mention network requests), and keep each partial very well contained.
It would look something like this:
In router:
$routeProvider.when('/apps/:appId', {
templateUrl: 'partials/app-frame.html',
controller: 'AppCtrl'
});
In containing html (app-frame) include the relatively disparate "mini app":
<h1>Currently hosting {{appId}}</h1><hr>
<div class="ng-include: appUrl"></div>
In partial resolved with appUrl, define controller and markup in one:
<script>
myApp.controller('controller1', ['$scope', function ($scope) {
$scope.foo = "bar";
}]);
</script>
<div ng-controller="controller1">
{{foo}}
</div>
For cases like this, where there are a lot of partials and a 1-1 mapping for controller and view, it can make sense to couple the two for development efficiencies and maintenance. It's a lot cleaner than using several files and additional configuration touch points.
The problem is, this doesn't work. It could be as simple as forcing the script to load prior to applying the directive... but not sure how to do that?
Here are some similar explanations of the problem:
https://groups.google.com/forum/#!topic/angular/H4haaMePJU0
Loading Partial Page With Angular and Compile The Controller
Igor from the AngularJS team says:
I see.. we looked into supporting script tags in jqlite, but what needs to be done to get a cross-browser support involves a lot of black magic. For this reason we decided that for now we are just going to recommend that users use jquery along with angular in this particular case. It doesn't make sense for us to rewrite one third of jquery to get this working in jqlite.
But I don't know what he means by "use jquery" ... JQuery is already loaded into the application from index.html (and prior to angularjs), but it sounds like I need to do something specifically within the partial itself.
You cannot add new controllers through module('app').controller(name, function() { .. }) after AngularJS bootstrap. In order make it work you should use $controllerProvider.register(name, function() { .. }).
You can override the original controller registering function in following way to be able to add controllers pre and pos bootstrap:
var app = angular.module('app', [
'ui.router'
]);
app.config(function($controllerProvider) {
app.controller = function (name, controller) {
$controllerProvider.register(name, controller);
};
});
Related
Learning angularjs, the curve is steep but I can see that it's going somewhere.
Given the following:
var app = angular.module('theApp',[]);
app.controller('Controller1', function($scope) {
var self=this;
$scope.thing = "hi.";
this.thang = "yo.";
$scope.doIt=function(){
return self.thang;
};
});
thing and doIt() are exposed through $scope, but thang is not:
<body ng-app="theApp">
<div ng-controller="Controller1">
<div>{{thing}}</div>
<div>{{thang}}</div>
<div>{{doIt()}}</div>
</div>
</body>
Is there any way to bind or otherwise expose thang? Or is this. completely pointless inside an ngController?
No problem if it is, just trying to get it straight in my head. I'm getting the idea that and ngController only communicates through $scope (and other ng service objects), they aren't independently accessible to other code; and I see that as a basically good thing, within anjularjs.
But it means you have to go all-in with angularjs; legacy/external code has to be wrapped in angularjs services and stuff in order to communicate across an ngController, would that be a fair statement?
I'm using angularjs 1.2.13, does the behavior change across versions?
Is there any way to bind or otherwise expose thang?
It depends how you want it to be exposed. If you want it to be available in the view; you'll have to put it into the $scope.
If you want to share the value with other controllers, you'll probably want to use a service or to store the value.
Or is this. completely pointless inside an ngController?
I consider variables, or methods, not in the $scope to be similar to a protected variable [or method]. Depending what your building their can be uses for that. I would not consider it pointless.
But it means you have to go all-in with angularjs; legacy/external
code has to be wrapped in angularjs services and stuff in order to
communicate across an ngController, would that be a fair statement?
No, it doesn't mean that. For example, I once wrote an app with a login form, and needed to hash the password before calling a remote service. I just Googled and found a JAvaScript hash library; then imported it into the html page (AKA Used the script tag), and was able to access the hash function from within the controller without doing any other work. This is due to the nature of JavaScript and how browsers work.
However, by doing this I added an external dependency into my controller which would make it difficult to write tests against. If I had wrapped the library in an Angular service, then passed it into the controller I would have built a semi-self-documenting API for the controller that could be tested with known dependencies.
So, you don't have to wrap external services in AngularJS. But, you may want to. As long as you understand the trade offs your making, you can make good decisions for your use case. (Just like any 'best practice').
I'm using angularjs 1.2.13, does the behavior change across versions?
All versions of Angular I've used have the same "Controller / Scope / Dependency Injection" approach.
The ng-controller tag can put the controller on the scope. These are equivalent:
<body ng-app="theApp">
<div ng-controller="Controller1 as c">
<div>{{thing}}</div>
<div>{{c.thang}}</div>
<div>{{doIt()}}</div>
</div>
</body>
And:
app.controller('Controller1', function($scope) {
var self=this;
$scope.thing = "hi.";
$scope.c = this;
this.thang = "yo.";
$scope.doIt=function(){
return self.thang;
};
});
Personally I think putting the name in the tag usually makes more sense as the template reads better.
Remember you can also access controllers which aren't on $scope from the link function of a directive. When you do that you refer to the name you gave the controller when you registered it, Controller1 in this case. See the require attribute here: http://docs.angularjs.org/guide/directive
Is it more effective to use a Service or rely on Scope Inheritance when updating parameters outside an AngularJS ng-view?
I have an AngularJS page with an ng-view and a common header. When moving between the ng-views I would like to:
update the common header title
select the appropriate navigation menu item.
In order to do this I've read two common solutions. The first is to provide a Service that can pass variables between controllers. A simple and straight-forward solution described in many places, including here: AngularJS: How can I pass variables between controllers?
The second method I've found was to take advantage of Scope Inheritance and wrap my ng-view in a parent controller, like so:
<body ng-controller="MainCtrl">
<!-- stuff -->
<h1>{{ common.pageTitle }}</h1>
<ng-view></ng-view>
<!-- more stuff -->
</body>
Then in my controllers I can do the following:
myApp.controller('MainCtrl', function ($scope)
{
$scope.common = [];
$scope.common.pageTitle = "Set by MainCtrl";
});
myApp.controller('SplashCtrl', function ($scope)
{
$scope.common.pageTitle = "Set by SplashCtrl!"
});
Is there a performance hit between using one over the other? Am I going to discover a hidden "gotcha" if I use one, that I haven't realized yet?
I recommend using services or an even pattern over inheritance. It's more difficult to 'figure out' where things are coming from and what is modifying what unless you have some explicit way of stating that
As far a performance goes, I'm not sure what would be faster, however don't worry about performance until you need to, then look for a solution to your specific performance issue
If you wanted to go the event pattern route then you could have your controller broadcast it did something
$rootScope.broadcast('eventname', { event data });
and consume the message like this in your menu/main controller
$scope.$on('eventname', function(event, data){
// update menu state...
});
The hidden 'gotcha' for me was after going several levels deep in scope inheritance I found that when I started writing my test code I had to instantiate all of the 'parent' controllers and my tests got overly complex very quick
I'm using an angularJS and requireJS seed which you can download here: LINK
In that seed only the controllers that are called download the relevant controller which is then triggered. I've been trying to call a factory from my controller (with no luck) plus I would like the services/factories only to download the relevant factory if it has been called.
I've attempted to require a function within the factory method (much like the controller) but it is not working.
This is where I left off: Plunkr link
user971824.
I've put together something I call couchPotato that lazy-registers just about anything in angular using requirejs and the resolution features of $routeProvider (or any other router that does lazy promise-based resolution.
I've created a plunker based on yours demonstrating how you could do it with couchPotato. If you take a look at it, I think you'll see that it's a bit simpler because you don't actually create modules for all of the things you register lazily.
couchPotato grew out of some other example apps I found on the web. What I wanted was a tight way to do the lazy registration and a provider/service combo seemed ideal. I also wanted to maintain the ability for one component to depend on another, like in your example, you want the controller to depend on the factory... couchPotato lets you specify those dependencies in requirejs syntax.
So your controller, in my rendition, looks like this:
define(['app', 'myFactory'], function(app) {
app.couchPotato.registerController([
'mycontroller',
[
'myFactory',
function(myFactory) {
var message = myFactory.getCustomers();
alert(message);
}
]
]);
});
In this example, I made the controller, the factory and the value all lazy, but you could pick and choose and have some registered the "old fashioned way" at configuration time and others registered with couchPotato when they're needed for a given route.
http://plnkr.co/edit/Z3v1mszQiiq024po8Ocp?p=preview
A couple of things to note:
1) I put in a default route in order to trigger the lazy loading of your controller, your service (factory) and the version value.
2) I modified your service to append the version just to show how one component can depend on another (the service depends on the version value, the controller depends on the service).
So, within the require configuration, you don't actually specify any of this... it's all done lazily within your route.
$routeProvider.when('/',
$couchPotatoProvider.resolveDependenciesProperty({
templateUrl:'home.html',
controller: 'mycontroller',
dependencies: [
'mycontroller'
]
})
);
Since mycontroller depends on myFactory and myFactory depends on version, by the time your route is displayed they are all available and hooked up. I put some dummy text in the home.html partial just for kicks, but the controller is assigned by the $routeProvider so you don't actually need to specify it in the template.
couchPotato lives at https://github.com/afterglowtech/angular-couchPotato if you'd like to see a couple of other samples. I shim'ed it in dependent on angular because I designed it to be usable in cases where an entire application doesn't necessarily use requirejs... thus if you are loading angular with requirejs, you need to make couchPotato dependent on angular using the shim/deps technique.
LMK if you have any questions/comments... hope this helps!
New to Angular. I feel like I'm missing something obvious: Shouldn't I easily be able to run to separate AngularJs apps (modules) in the same html page? Something like this:
<section ng-app="HelloWorldApp" ng-controller="HelloWorldController">
Hello {{name}}!
</section>
<br />
<section ng-app="MyNameIsApp" ng-controller="MyNameIsController">
My Name is {{FirstName}} {{LastName}}!
</section>
Javascript:
var HelloWorldApp = angular.module('HelloWorldApp', []);
HelloWorldApp.controller('HelloWorldController', function($scope) {
$scope.name = 'World';
});
var MyNameIsApp = angular.module('MyNameIsApp', []);
MyNameIsApp.controller('MyNameIsController', function($scope) {
$scope.FirstName = 'John';
$scope.LastName = 'Smith';
});
This only runs the first module, while the second doesn't appear to do anything. I want to do this so that I can build reusable, encapsulated directives for multiple pages that don't have to name their modules the same thing.
Live Example: http://plnkr.co/edit/cE6i3ouKz8SeQeA5h3VJ
We ended up building small hierarchy of modules, however my original question can done, with just a bit of work (see below).
It is possible, but it requires a little bit coding by hand. You need to bootstrap the angular apps on your own. Don't worry, it is not that complicated
Do not add ng-app attributes in your HTML
Make sure you can fetch the DOM elements holding the app
When DOM is loaded you need to start the apps on your own: angular.bootstrap( domElement, ['AppName']);
Fork of you plunker which works: http://plnkr.co/edit/c5zWOMoah2jHYhR5Cktp
According to the Angular docs for ngApp:
Use this directive to auto-bootstrap an application. Only one
directive can be used per HTML document. The directive designates the
root of the application and is typically placed at the root of the
page.
Seems it's by design.
You can specify any nested apps in the module def of the main one.
angular.module("myapp", ['statusapp', 'tickerapp']).controller(....
and in a separate file, you have the other apps defined. We're using a template engine which hides some of this, but you'll end up with HTML that contains nested ng-apps and javascript for each one that defines the module/controller. The code above is the trick to getting more than one bootstrapped.
A similar question was asked here but it did not help me.
I am learning angularjs and I noticed the controller is executed twice.
I have a very simple fiddle example that shows the behavior here
I built the example as I was learning about services and at first I thought it was the injecting of the services into the controller but I commented all the code related to the services and still the controller is executed twice.
My example works but I am afraid I am doing something wrong.
<div ng-app="MyApp">
<div ng-controller="MyCtrl">
{{data1}}
</div>
</div>
var app = angular.module('MyApp', [])
app.service('Service1', function(){
return {
ajxResponse1: 'dataFromService1'
};
});
function MyCtrl($scope, Service1){
alert('Entering MyCtrl');
$scope.data1 = Service1.ajxResponse1;
alert('Exiting MyCtrl');
}
One possible source is this: if you are using Routing and specify the controller in routes - you must not specify it in template that the route uses. We had that problem when this was overlooked.
Your controller was running twice in the fiddle because angular is referenced twice (once via the Frameworks & Extensions drop down and another as an External Resource).
See this updated fiddle where I removed the External Resource and the alerts only show up once.
The code remains unchanged:
function MyCtrl($scope, Service1, Service2, Service3){
alert('Entering MyCtrl');
$scope.data1 = Service1.ajxResponse1;
$scope.data2 = Service2.ajxResponse2;
$scope.data3 = Service3.ajxResponse3;
alert('Exiting MyCtrl');
}
I had a similar problem and it was due to slashes in my routing.
I had something like /post{slug:[a-z0-9-]*/} for my route and when visiting the page at domain.com/post it would redirect to the version with a slash on the end.
Took me ages to work it out!
Edit:
Actually, just took a more detailed look at your code and noticed there is no routing in there, so this is probably not the cause in your case.
Might be useful for people like me who were looking for a different solution though.
For all the people using rails and angularjs:
The rails framework that maps URLS to views and loads them clashes with the angularjs $route even when you have a single-view application.
To prevent the double-loading of your controller:
go to application.js and remove "//= require turbolinks
You're welcome.