I cam across this blog posting from Dan Wahlin about dynamically loading controllers and views. I downloaded the source from github and tried to reproduce the project on a smaller scale to understand how it all worked. I can get the project to load with the views but where I am stuck is on figuring out why the controller does not seem to bind to the view. Stepping through the code I can see the controller being initialized and injected into the app:
here you can see the app initialize and routes are established
'use strict';
define(['services/routeResolver'], function () {
var app = angular.module('myApp', ['ngRoute', 'ngAnimate', 'ui.bootstrap', 'breeze.angular', 'routeResolverServices']);
app.config(['$routeProvider', 'routeResolverProvider', '$controllerProvider',
'$compileProvider', '$filterProvider', '$provide', '$httpProvider',
function ($routeProvider, routeResolverProvider, $controllerProvider,
$compileProvider, $filterProvider, $provide, $httpProvider) {
app.register = {
controller: $controllerProvider.register,
directive: $compileProvider.directive,
filter: $filterProvider.register,
factory: $provide.factory,
service: $provide.service
};
//From Dan Whalin project comments: route.resolve() now accepts the convention to use (name of controller & view) as well as the
//path where the controller or view lives in the controllers or views folder if it's in a sub folder.
// first param is the name of the controller, second param is the directory it and the view exist in, third param is the alias (controller as) optional third param is true false for security
var route = routeResolverProvider.route;
$routeProvider
.when('/', route.resolve('main', '', 'vm'))
.otherwise({ redirectTo: '/' });
}
]);
////support for lodash
// app.factory('_', ['$window', function ($window) {
// return $window._;
// }]);
// breeze factory manager
app.factory('entityManagerFactory', ['breeze', emFactory]);
function emFactory(breeze) {
// Convert properties between server-side PascalCase and client-side camelCase
breeze.NamingConvention.camelCase.setAsDefault();
// Identify the endpoint for the remote data service
var serviceRoot = window.location.protocol + '//' + window.location.host + '/';
var serviceName = serviceRoot + 'breeze/breeze'; // breeze Web API controller
// the "factory" services exposes two members
var factory = {
newManager: function () { return new breeze.EntityManager(serviceName); },
serviceName: serviceName
};
return factory;
};
app.service("httpDataLoader", ["$http", function ($http) {
this.load = function () {
return $http();
}
}]);
//global filter to allow html to render in the UI and bypass SCE (secure content expression)
//usage: ng-html-bind="properyExpresson | html"
app.filter('html', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
}
}
]);
app.run(['breeze', function (breeze) { }]);//currently doing nothing
return app;
});
UPDATE
A question was asked about the route resolver and how it was supposed to work:
From Dan Wahlin Bolg:
The routeResolver.js script creates an AngularJS provider. It’s loaded by RequireJS and used in app.js within the config() function to define routes and resolve them dynamically at runtime.
AngularJS already comes with built-in support for loading views dynamically and with a little more work controllers can be loaded dynamically as well. Loading controller scripts can be done by assigning the resolve property mentioned earlier to a function that handles loading the controller. What’s unique about routeResolver is that it doesn’t accept hard-coded paths to the target view or controller. Instead, you define a base name such as “main” and the resolver will generate the path to the appropriate view and controller based on a standard convention.
within main.js I define the files to load with require
requirejs.config({
baseUrl: 'app',
urlArgs: 'v=1.0',
});
require([
'app',
'services/routeResolver',
'services/config',
'services/dataService'
],
function () {
angular.bootstrap(document, ['myApp']);
});
and within my mainController I setup a basic controller
'use strict';
define(['app'], function (app) {
var injectParams = ['$window', 'dataService'];
var mainController = function($window, dataService){
var vm = this;
vm.message = 'we are wired up';
vm.connect = function () {
alert('hello')
};
};
mainController.$inject = injectParams;
app.register.controller('mainController', mainController);
});
index.html is setup as
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="Content/bootswatch-slate.css" rel="stylesheet" />
<title></title>
</head>
<body ng-cloak>
<div ng-view></div>
<script src="Scripts/angular.min.js"></script>
<script src="Scripts/angular-route.min.js"></script>
<script src="Scripts/angular-animate.min.js"></script>
<script src="Scripts/angular-sanitize.min.js"></script>
<script src="Scripts/angular-ui/ui-bootstrap.min.js"></script>
<script src="Scripts/breeze.min.js"></script>
<script src="Scripts/breeze.bridge.angular.js"></script>
<script src="Scripts/require.js" data-main="Scripts/main"></script>
</body>
</html>
and the main view is pretty basic
<div>
<p>static text</p>
<p>{{vm.message}}</p>
<button type="button" ng-click="vm.connect()">click</button>
</div>
What I am seeing in the project is the page/view load fine and within the dev tools I can see the controller as well as all scripts initialize. However the scoped items within the controller vm.message or the function call vm.connect are not bound or recognized from the view. Dev Tools show no error in the console and the view with static content renders. I think I may be getting fooled with the scope of the controllers is either getting duplicated or I am somehow not injecting it correctly. I tried to use the Angular extension to observe the scope and watches but it would error out. I got the same error when I ran the source project from gitHub, but with the angular extension turned off the source project runs fine.
I tried to setup a plnkr but due to the configuration/routing of the project it wouldn't run so here is a link to the full solution from VS. The code is on a GoogleDrive share if I need to move it to another repository please let me know. I would appreciate any code review and suggestion on what I have missed or overlooked. In comparing my code to what is in Dan's solution it appears to be the same.
I'd appreciate any suggestions or ideas
thanks
There were differents between the code in blog post and github.
As routeResolverProvider.route.resolve() on the blog post only support 3 parameters
function (baseName, path, secure) {...}
And on github, it is instead support 4 parameters
function (baseName, path, controllerAs, secure) {...}
So the only possiblily for controllerAs mis-registered with $routeProvider is the code on blog post was used. And routeResolverProvider.route.resolve() is not supporting controllerAs configuration.
Related
SOLVED
So after viewing your responses and making minor changes to my code, I found a simple typo which prevented me to reach the result I was after. So thank you all to helping with where I was going wrong, I've now got the controllers seperated and everything's working as planned!
----
I am currently developing a hybrid mobile application using Cordova, Ionic and AngularJS within Visual Studio 2015. Due to the vast amount of code I have in my single controller.js file, I want to separate the code so I have a .js controller file per template; instead of everything in one file. Unfortunately, I I do not understand how to pull this off (learning AngularJS still). I've done some researched but the majority of examples I have seen show a very simple demo, which I replicate with my own code but it still doesn't work. So I was hoping if someone can give me an insight where I may be going wrong.
File Structure in /www
index.html
/js
app.js
controllers.js
/js/controllers
login.js
sales.js
/templates
login.html
sales.html
/js/app .js
angular.module('main', ['ionic', 'main.controllers', 'chart.js', 'ngCordova', 'ngIOS9UIWebViewPatch', 'angular.filter'])
.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$stateProvider
.state('login', {
cache: false,
url: "/login",
templateUrl: "templates/login.html",
controller: "LoginCtrl"
})
.state('sales', {
cache: false,
url: "/sales",
templateUrl: "templates/sales.html",
controller: "SalesCtrl"
})
$urlRouterProvider.otherwise('/login')
$ionicConfigProvider.views.swipeBackEnabled(false);
});
/js/controllers.js
angular.module('main.controllers', ['ionic', 'ngCordova']);
/js/controllers/login.js
angular.module('main.controllers', [])
.controller("LoginCtrl", function ($scope, $state, $cordovaSQLite, $timeout, $ionicPopup, $cordovaDevice, $ionicLoading, $cordovaKeyboard, $cordovaToast) {
$ionicLoading.show({
template: 'Loading...'
});
// DO STUFF
/js/controllers/sales/js
angular.module('main.controllers', [])
.controller("SalesCtrl", function ($scope, $state, $http, $ionicLoading, $cordovaSQLite, $cordovaToast) {
$ionicLoading.show({
template: 'Loading data...'
});
// DO STUFF
Following this structure, I get this error (quote below): https://docs.angularjs.org/error/ng/areq?p0=LoginCtrl&p1=not%20a%20function,%20got%20undefined
Argument 'LoginCtrl' is not a function, got undefined
I managed to get it to work slightly, only when I had login.js and not sales.js, but of course $state.* stopped working when trying to change template. So I know that wasn't 100% either. Hopefully this will make sense, fi it doesn't just clarify what I may be not making sense in, and I shall explain more if need be, appreciate any help. :)
EDIT
index.html
<!-- App references -->
<link href="css/ionic.css" rel="stylesheet" />
<link href="css/angular-chart.css" rel="stylesheet" />
<link href="css/index.css" rel="stylesheet" />
<script src="lib/ionic/ionic.bundle.js"></script>
<script src="lib/ngCordova/ng-cordova.js"></script> <!-- Must be after Ionic but before Cordova-->
<script src="cordova.js"></script>
<script src="scripts/index.js"></script>
<script src="lib/angular-cookies/angular-cookies.min.js"></script>
<script src="lib/angular-chart/Chart.min.js"></script>
<script src="lib/angular-chart/angular-chart.min.js"></script>
<script src="lib/angular-ios9-uiwebview.patch.js"></script>
<script src="lib/angular-filter/angular-filter.min.js"></script>
<script src="js/directives/favourite.js"></script>
<script src="js/controllers.js"></script>
<script src="js/controllers/login.js"></script>
<script src="js/controllers/sales.js"></script>
<script src="js/app.js"></script>
You are redefining your module again & again in your each controller file. Which is clearing out old registered controller from that module.
You have already defined that module in your /js/controllers.js.
angular.module('main.controllers', ['ionic', 'ngCordova']);
So reuse that module in other Javascript files when your are binding any component to it as below.
angular.module('main.controllers')
The best approach is that you clearly separate module creation from module usage.
modules.js:
angular.module('main', ['main.sales']);
angular.module('main.sales', []);
src/sales/scripts/sales-controller.js:
angular.module('main.sales').controller(function() {});
If you concat and minify your js files via grunt or gulp, you should always explicitly include modules.js first, afterwards you can include the rest via a pattern like 'src/**/*.js' for example.
This way the modules are always defined before they are used. If that's not the case, angular will complain about a non existing module.
PS: it's way better to create functional modules (sales related functionality in 1 module) instead of technical modules (all controllers in 1 module)
You are declaring the main.controllers module twice, once for each controller. Also, it's not strictly required to declare a separate module for the controllers, you could just declare the controllers within your main module. Some would argue that you lose reusability - and they would be right - but depending on the size of your project and how tightly coupled your controllers are with your application (90% of the time the answer is: very) you could go that way. Since you are probably just starting out, try doing something like this:
js/app.js
angular.module('main', ['ionic', 'chart.js', 'ngCordova', 'ngIOS9UIWebViewPatch', 'angular.filter'])
.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$stateProvider
.state('login', {
cache: false,
url: "/login",
templateUrl: "templates/login.html",
controller: "LoginCtrl"
})
.state('sales', {
cache: false,
url: "/sales",
templateUrl: "templates/sales.html",
controller: "SalesCtrl"
})
$urlRouterProvider.otherwise('/login')
$ionicConfigProvider.views.swipeBackEnabled(false);
});
Note that I am no longer depending on main.controllers? That's because next I'm doing this:
angular.module('main')
.controller("LoginCtrl", function ($scope, $state, $cordovaSQLite, $timeout, $ionicPopup, $cordovaDevice, $ionicLoading, $cordovaKeyboard, $cordovaToast) {
$ionicLoading.show({
template: 'Loading...'
});
// DO STUFF
And this:
angular.module('main')
.controller("SalesCtrl", function ($scope, $state, $http, $ionicLoading, $cordovaSQLite, $cordovaToast) {
$ionicLoading.show({
template: 'Loading data...'
});
// DO STUFF
The different controllers can (and should) be declared each in a separate file, so that you have a clear structure. It might be more correct to have a separate module for the controllers, and I fear my opinion might be unpopular, but I don't really see the point - whereas I strongly urge you to separate your services and your directives into different modules, since it's much more likely that you're going to use them again in other projects.
My suggest we will organize code by split to the module, and inject it in the app.js file
This is detail my way: https://mymai91.github.io/ionic/2016/07/01/ionic-structure-code-for-project.html
Code demo: https://github.com/mymai91/mymaiApp
to anyone looking at the "solved code"...
The Square brackets in the inner controlleres should be removed:
angular.module('main.controllers', [])
-->
angular.module('main.controllers')
Some I'm new to routing and single page web apps, but I've been trying to learn Angular correctly. There's some trouble I'm experiencing with it however and a few weird questions. I followed a guide on structuring your directory and mine looks something like this:
app/
components/
profile/
profile.html
ProfileModel.js
ProfileController.js
ProfileFactory.js
app.module.js
app.routes.js
My main module is located in app.module.js and is dependency injected with ngRoute and profile.app (the module for profile view from ProfileModel.js). It is declared like this:
angular
.module('app', ['ngRoute', 'profile.app'])
.controller('MainController', MainController);
function MainController() {
this.message = 'This is the home page';
}
Then in my app.routes.js file, I have declared all the routes the applications needs (so far only one, which is profile):
angular
.module('app')
.config(routeConfig);
routeConfig.$inject = ['$locationProvider', '$routeProvider'];
function routeConfig ($locationProvider, $routeProvider) {
$routeProvider
.when('/user/:user_id', {
templateUrl: '/app/components/profile/profile.html',
controller: 'ProfileController',
controllerAs: 'profile'
}
)
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
This is my ProfileController.js:
angular
.module('app.profile')
.controller('ProfileController', ProfileController);
ProfileController.$inject = ['ProfileFactory', '$routeParams'];
function ProfileController(ProfileFactory, $routeParams) {
var vm = this;
vm.user_id = $routeParams.user;
console.log($routeParams.user_id);
vm = ProfileFactory.userProfile(vm.user_id); //Gets the profile of the user and sets to to vm
}
So I have two main questions. $routeParams.user_id is logged as nothing despite I have defined the route in app.routes.js. This is weird because I have an ng-view directive in my index.html (the HTML file that includes every single .js file). Which means that I should have immediate access to the routing parameters once the controller and its dependencies are instantiated. However, when I go to http://example.com/user/1, I get nothing logged (undefined).
My second question is I included ngRoute as a dependency in profile.app and my main module (in which profile.app is a dependency). However, I later removed ngRoute from profile.app as a dependency, yet I left the injection of $routeParams inside my ProfileController and AngularJS didn't complain at all. This seems weird because the dependency is no longer explicitly present inside profile.app. So how come I can still seemingly inject $routeParams despite not having ngRoute in my profile.app module? Is it because it is getting ngRoute from the main module?
I believe the problem is that you are setting controllerAs to 'profile' but then in the controller you write var vm = this;
These two need to be the same so you could write controllerAs: 'vm', or var profile = this;
I am getting the unknown provider error and I am not sure why. My angular controller is not finding the service that I have created. My Service is defined as:
var app = angular.module('losApp');
app.service('ClientService', ['$scope','$http','$q',function($scope,$http,$q){
var client = {};//empty oject that will store multiple function
return client; //return the client object
}]);
Also, my controller is defined as:
var app = angular.module('losApp');
app.controller('DashboardController', ['$scope', '$modal','ClientService',function($scope, $modal,ClientService){}
In my index.html, the scripts tags are declare as follows:
<script src="js/app.js"></script>
<script src="js/services/ClientService.js"></script>
<script src="js/controllers/DashboardController.js"></script>
In app.js, I am using ui-router module to handle routing for the application.
var app = angular.module('losApp', ['ngMessages', 'ui.bootstrap', 'angularFileUpload', 'ui.router', 'summernote', 'angucomplete-alt', 'ngCookies']);
app.config(function($stateProvider,$urlRouterProvider,$locationProvider, $interpolateProvider){
$urlRouterProvider.otherwise('/'); //redirects to this page is
$stateProvider.state('/',{
url:'/',
views : {
// the main template will be placed here (relatively named)
'':{
templateUrl: '/js/pages/dashboard.html',
controller: 'DashboardController'
}
}
});
});
In browser console log and clicked on the angular error message. Additionally, I followed what angularjs docs suggested, but still no success.
What I am doing wrong and thanks in advance.
I found out why I was getting unknown provider.It was because I had injected the $scope object into the factory, which is not allowed.
I can't get angular to accept my custom service, which is in a seperate file. I know this is all over stack overflow, but I just can't tell where I'm messing up.
Html:
<!DOCTYPE html>
<html lang="en" ng-app="angularApp">
<head>
<!-- ANGULAR -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.6/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular-sanitize.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular-route.js"></script>
<link href="styles/angularApp.css" rel="stylesheet">
<script src="scripts/controllers/angularApp.js"></script>
<script src ="scripts/services/clearCodemirror.js"></script>
Main angular javascript file (contains my controller)
var myApp = angular.module("angularApp", ["ui.codemirror","ui.bootstrap", "ngSanitize", "ngRoute"]);
myApp.controller("mainController", ["$scope", "$timeout", "clearCodemirror", function($scope, $timeout, clearCodemirror){
Service I'm trying to inject:
var myApp = angular.module("angularApp");
myApp.service("clearCodemirror", function(cm){
Error I keep getting:
Uncaught Error: [$injector:modulerr] http://errors.angularjs.org/1.2.6/$injector/modulerr?p0=angularApp&p1=Error…oogleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.2.6%2Fangular.min.js%3A29%3A56)
I only included the beginnings of each file so you guys wouldn't have to wade through tons of code. Where am I screwing up?
Thanks!
By calling angular.module("angularApp") from your 2nd file, you are recreating a angular module, and NOT getting a reference to the module that was created on the previous file.
There are a few ways to solve that. The simplest one (on my point of view) is to set your angular app to a window.VARIABLE.
window.myApp = angular.module("angularApp", ["ui.codemirror","ui.bootstrap", "ngSanitize", "ngRoute"]);
Then, from your 2nd file you can:
var myApp = window.myApp;
myApp.service("clearCodemirror", function(cm){
There are better ways to do that. However, this one you get you going. Cheers!
Hi you are not re creating the module you are getting the existing module by var myApp = angular.module("angularApp");
as the docs states :-
https://docs.angularjs.org/api/ng/function/angular.module
When passed two or more arguments, a new module is created. If passed only one argument, an existing module (the name passed as the first argument to module) is retrieved.
i tried with 1.3.14 , i didnt got any error, which version are you using:-
this is a jsbin showing the problem, however, I will explain it here. I am trying to use resolve to run some code before entering a route, however, it doesn't run (as the console.log statement provides more detail about). (Since it doesn't run, I'm not sure if I'm setting what would be the return value from resolve in the template correctly). Why isn't resolve running?
var app = angular.module('jsbin', ['ngRoute'])
.config(function ($routeProvider) {
'use strict';
console.log("this runs");
var routeConfig = {
controller: 'DemoCtrll',
resolve: {
store: function (demoStorage) {
console.log("this doesn't log");
return demoStorage;
}
}
};
$routeProvider
.when('/', routeConfig)
.otherwise({
redirectTo: '/'
});
});
app.controller('DemoCtrl', function($scope, $routeParams, store) {
this.name = store;
});
app.factory('demoStorage', function () {
'use strict';
return "World";
});
html
<body ng-app="jsbin">
<div ng-controller="DemoCtrl as demo">
<h1>Hello {{name}}</h1>
</div>
Some changes you would need:
You cannot use ng-controller directive along with ng-view's route bound controller, i.e set controller alias in the controller property of the route config controller: 'DemoCtrl as demo',.
When you use ng-controller directive to instantiate the controller it does not know what is store provider, because no such provider/service exists. It is a special dependency injected by the angular router and only the router knows how to inject store as dependency in your controller. That is the reason for the error when you used ng-controller.
You need to define a template html or a template and bind it to templateUrl property on the routeConfig. i.e templateUrl:'demo.html',
Remove ng-controller from the view.
You need to use ng-view directive to render the view. i.e in your index.html <ng-view></ng-view>
Since you are using controller alias you would refer to the property as demo.name in the view rendered by the route.
You also had a typo in the controller name #DemoCtrl1.
Overall it will look like this.
Define your template in say Demo.html (In the demo example i have used the convenient script type="text/ng-template")
<div>
<h1>Hello {{demo.name}}</h1>
</div>
Set the routeConfig:
var routeConfig = {
templateUrl:'demo.html',
controller: 'DemoCtrl as demo',
resolve: {
store: function (demoStorage) {
console.log("this doesn't log");
return demoStorage;
}
}
};
JSBin
Also see this answer though it uses angular ui-router, but the concept is the same.