How can I have an AngularJS module routes use its own controllers? - dependency-injection

I'm trying to learn AngularJS' view and routing mechanism, following AngularJS' own tutorial.
My problem is the tutorial is declaring all its controllers in the global scope, and I belive this is a bad practice because we're polluting it as we add more controllers.
This is a quick working page I've been able to build following the aforementioned tutorial (there's a fiddle, too):
<!doctype html>
<html>
<head>
<title>Test</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js"></script>
<script>
"use strict";
var MyFirstController = function ($scope) {
// Do something here.
};
var MySecondController = function ($scope) {
// Do something here.
};
var myModule = angular.module("MyModule", []);
myModule.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/first-page", {
template: "<p>My first controller.</p>",
controller: MyFirstController
});
$routeProvider.when("/second-page", {
template: "<p>My second controller.</p>",
controller: MySecondController
});
}]);
$(document).ready(function () {
angular.bootstrap(document, ["MyModule"]);
});
</script>
</head>
<body>
<h1>Test</h1>
<div data-ng-view></div>
<p>Click me!</p>
<p>Click me too!</p>
</body>
</html>
Being naïve, I tried to move the controllers inside the module:
myModule.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/first-page", {
template: "<p>My first controller.</p>",
controller: MyFirstController
});
$routeProvider.when("/second-page", {
template: "<p>My second controller.</p>",
controller: MySecondController
});
}]);
myModule.controller("MyFirstController", ["$scope", function ($scope) {
// Do something here.
}]);
myModule.controller("MySecondController", ["$scope", function ($scope) {
// Do something here.
}]);
Alas, it doesn't (obviously) work, throwing a ReferenceError: MyFirstController is not defined exception.
How can I have an AngularJS module use its own controllers in its own routes configuration?

Once you know the solution, it's really simple: just specify the controller as strings instead of objects:
myModule.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/first-page", {
template: "<p>My first controller.</p>",
controller: "MyFirstController"
});
$routeProvider.when("/second-page", {
template: "<p>My second controller.</p>",
controller: "MySecondController"
});
}]);
This way AngularJS will resolve the controller name to the one you've defined inside the module.
And it's minification safe too!
I've created a fiddle demonstrating it.

Related

AngularJS loading more than once when using nested states

I'm getting a WARNING: Tried to load angular more than once. when trying to use nested views on a Ruby on Rails and Angular app. I'm using ui-router and angular-rails-templates.
Angular config and controllers:
var app = angular.module('app', ['ui.router', 'templates']);
app.factory('categories', ['$http', function($http) {
var o = {
categories: []
};
o.get = function (id) {
return $http.get('/categories/' + id + '.json').then(function (res) {
return res.data;
});
};
return o;
}]);
app.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home/_home.html',
controller: 'MainCtrl',
});
$stateProvider
.state('home.categories', {
url: '/categories/:categoryId',
templateUrl: 'categories/_categories.html',
controller: 'CategoriesCtrl',
resolve: {
category: ['$stateParams', 'categories', function($stateParams, categories) {
return categories.get($stateParams.categoryId);
}]
}
});
$urlRouterProvider.otherwise('/home');
}]);
app.controller('MainCtrl', function ($state) {
$state.transitionTo('home.categories');
});
app.controller('CategoriesCtrl', [
'$scope',
'categories',
'category',
function($scope, categories, category) {
$scope.posts = category.posts;
}
]);
And the templates:
_home.html (*edited: deleted body and ng-app from this template, following #apneadiving advice, and used the right path, as pointed by #Pankaj Now, views are rendered properly, but the 'loading more than once' persists)
Category One
Category Two
<ui-view></ui-view>
_categories.html
<ul ng-repeat="post in posts">
<li>{{post.title}}</li>
</ul>
app/views/layouts/application.html.erb
<!--<rails head>-->
<body ng-app="app">
<ui-view></ui-view>
</body>
What is happening is that the state 'home' appears and when I click on the links, the URL changes but nothing else happens. And I get the message that Angular is being loaded more than once.
EDITED: Got it working. It was a <%= yield %> conflict in another part of the app that should have nothing to do with the Angular part.
As you are defined the child states, you need to change your URL's to /home as they are child of home state, the URL gets inherited from parent state.
Category One
Category Two
You shouldnt boot your app in a template like you do.
Add the ng-app to your html's body and remove it from the template (actually your template shouldnt bear the body either)

Can't get child views to load in AngularJS with ui-router

I'm trying to understand the concept of using the ui-router (v0.2.10) with AngularJS (v1.2.15) through converting an old project that previously used ngRoute and have been unable to figure out why it isn't working. I have an ASP.NET MVC website that shows the base page with something similar to the following:
index.cshtml
<!DOCTYPE html>
<html lang="en-US" ng-app="home">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
#Styles.Render("~/Content/css")
</head>
<body>
<div ui-view></div>
#Scripts.Render(*all the angular, jquery and other script references here)
</body>
</html>
app.js
angular.module('home') ['ui.router'])
.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.when("", "/");
$urlRouterProvider.otherwise("/");
var root = {
name: 'root',
url: '/',
abstract: true,
templateUrl: '/Scripts/home/templates/root.html',
controller: 'RootController'
};
var home = {
name: 'root.home',
url: '/home',
parent: root,
templateUrl: 'Scripts/home/templates/home.html',
controller: 'HomeController'
};
$stateProvider
.state(root)
.state(home);
$locationProvider.html5Mode(true);
};
rootCtrl.js
angular.module('home').controller('RootController', function() {
});
homeCtrl.js
angular.module('home').controller('HomeController', function() {
});
root.html
<navbar/>
<div class="wrapper" style="padding-top:25px;margin-bottom:20px">
<div ui-view></div>
</div>
<footer/>
navbar.js
angular.module('home').directive('navbar', function() {
return {
restrict: 'E',
replace: true,
templateUrl: '/Scripts/home/templates/directives/navbar.html'
};
});
The navbar directive template simply contains the bootstrap navbar.
footer.js
angular.module('home').directive('footer', function() {
return {
restrict: 'E',
replace: true,
templateUrl: '/Scripts/home/templates/directives/footer.html'
};
});
Except that nothing shows up on the page except for the CSS references at the top, the following, and then the javascript references at the bottom:
Why doesn't the root.html view show up on the screen when I load up the page? Thanks!
As per another answer on this site, one cannot self-close the tags for custom directives. Thus, if I change the tags in root.html to the following:
<navbar></navbar>
<div class="wrapper" style="padding-top:25px;margin-bottom:20px">
<div ui-view></div>
</div>
<footer></footer>
It works fine.

angular directive compile order

I was trying to write a simple directive to generate a (potentially) more complex dom element. I am quite confused about what is going on here but I think the directive I use inside my directive get linked first? Anyway the element I am generating is not visible where it should.
Sorry for all that confusion, here is the plunkr:
http://plnkr.co/edit/vWxTmA1tQ2rz6Z9dJyU9?p=preview
I think the directive I use inside my directive get linked first?
Yes. A child directive's link function will execute before the parent's link function.
Here is a fiddle that shows two nested directives,
<div d1>
<div d2></div>
</div>
and it logs when the directives' controller and link functions are called.
There are a few issues with your Plunker:
Since you are using # for your isolate scopes, you need to use {{}}s in your attribute values:
<visible value='{{visible}}'>plop</visible>
<invisible value='{{visible}}'>plop</invisible>
Since $scope.visible is defined in your controller, I assume you meant to use that value, and not test.
In the invisible directive, you need to use isolate scope property value in your link function. Property visible is available to the transcluded scope (which is in affect if you use a template in your directive like #Langdon has) but not the isolate scope, which is what the link function sees.
var template = "<span ng-show='value'>{{value}}</span>";
Plunker.
If you want a simple directive, you're better off letting Angular do most of the work through ngTransclude, and $watch.
http://plnkr.co/edit/xYTNIUKYuHWhTrK80qKJ?p=preview
HTML:
<!doctype html>
<html ng-app="app">
<head>
<meta charset="utf-8">
<title>trying to compile stuff</title>
<script src="http://code.angularjs.org/1.1.1/angular.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="AppCtrl">
<input type="checkbox" ng-model="test" id="test" /><label for="test">Visibility (currently {{test}})</label>
<br />
<br />
<visible value='test'>visible tag</visible>
<invisible value='test'>invisible tag</invisible>
</div>
</body>
</html>
JavaScript:
angular
.module('app', [])
.controller('AppCtrl', function($scope) {
$scope.test = false;
})
.directive('visible', function() {
return {
restrict: 'E',
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
value: '='
},
link: function(scope, element, attrs) {
console.log(attrs);
scope.$watch('value', function (value) {
element.css('display', value ? '' : 'none');
});
console.log(attrs.value);
}
};
})
.directive('invisible', function() {
return {
restrict: 'E',
transclude: true,
template: '<span ng-transclude></span>',
replace: true,
scope: {
value: '='
},
link: function(scope, element, attrs) {
scope.$watch('value', function (value) {
element.css('display', value ? 'none' : '');
});
}
};
});

Where would the on-load handler be in MVC?

I'm looking to add code to my Onload handler but am unsure where it would be in an MVC application?
// You may want to place these lines inside an onload handler
CFInstall.check({
mode: "overlay",
destination: "http://localhost:1414/"
});
});
The code above needs to be placed in the onload handler.
If I understand you correctly, you just need this expression below, if you are using jQuery:
<script>
$(document).ready(function() {
// Handler for .ready() called. Put your logic here.
});
</script>
or this one, without usage of jQuery:
<script>
window.onload = function(){
// Put your logic here.
}
</script>
to be included on your view.cshtml.
Here your meaning Adding Window Onload Event.
You can try this inside the js file:
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(nameOfSomeFunctionToRunOnPageLoad);
addLoadEvent(function() {
/* more code to run on page load */
});
For more information Simon Willison’s Weblog
I think you can add it just like in any other html page into your cshtml..
#{
ViewBag.Title = "Authenticate";
}
<script type="text/javascript">
$(document).ready(function () {
CFInstall.check({
mode: "overlay",
destination: "http://localhost:1414/"
});
});
</script>
<h2>Congrats..</h2>

Underscore.js template with Backbone.js error

The line below is completely failing.
template: _.template($('#test').html()),
Trying to follow a simple example from https://github.com/ccoenraets/backbone-jquerymobile to use jQuery mobile along with Backbone.js. The error I'm getting in the web inspector is: TypeError: 'null' is not an object (evaluating 'str.replace') which is in line 913 of the underscore.js. Using is _.template in this fashion:
template: _.template("<h1>To Do</h1>"),
works, but in order to incorporate the jQuery mobile styles, that way won't do.
todo.js
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template($('#test').html()),
render: function() {
$(this.el).html(this.template());
return this;
}
});
main.html
<script type = 'text/template' id = 'test'> <h1>To Do</h1> </script>
The DOM isn't ready when your view is being parsed:
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template($('#test').html()),
//...
});
so $('#test').html() is null and you're actually doing this:
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template(null),
//...
});
The internals of _.template use replace while converting the template to a JavaScript function.
You have a few options:
Put the TodoBd.Views.ComCentersTodo definition inside a $(document).ready() handler:
$(function() {
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
template: _.template($('#test').html()),
//...
});
});
Don't compile the template until you need it:
TodoBb.Views.ComCentersTodo = Backbone.View.extend({
//... no template in here
render: function() {
var html = _.template($('#test').html(), this.model.toJSON());
this.$el.html(html);
return this;
},
//...
});
As a variation of 2, you could cache the compiled template functions somewhere and only call _.template($('#test').html()) the first time you use it.

Resources