Modular Angular app using Angular UI Router and Browserify - angularjs

I've never used Angular UI Router before but I want to build an application that has nested views so it looks like the most sensible choice. However. I just can't get my head around it. My application is modular so I have an element on my page that I want the other modules, with their view templates, to load into.
Then when some action is taken inside one of the nested views, say a button click, I would like the state to change so that module to becomes the only one inside the main view, and for the URL to change:
I'm writing in CoffeeScript and using Browserify to tie the app together, so all the modules are in separate files and required in. This is where I've got so far but it's not working and I can't figure it out.
app.coffee
require...
require...
require...
app = angular.module("darrylsnow", [
"ngAnimate"
"ui.router"
"submodule1"
"submodule2"
"templates"
]).config [
"$stateProvider"
"$urlRouterProvider"
"$locationProvider"
($stateProvider, $urlRouterProvider, $locationProvider) ->
$urlRouterProvider
.otherwise "/"
$stateProvider
.state "main",
abstract: true # because the main module requires the submodules
url: "/"
$locationProvider.html5Mode true
]
submodule1.coffee
submodule1 = angular.module("submodule1", [
"ui.router"
]).config [
"$stateProvider"
"$urlRouterProvider"
"$routeProvider"
($stateProvider, $urlRouterProvider, $routeProvider) ->
$stateProvider
.state "main.submodule1",
url: ""
templateUrl: "submodule1.html"
.state "main.submodule1-expanded",
url: "/submodule1" # template shouldn't change
]
submodule2.coffee
submodule2 = angular.module("submodule2", [
"ui.router"
]).config [
"$stateProvider"
"$urlRouterProvider"
"$routeProvider"
($stateProvider, $urlRouterProvider, $routeProvider) ->
$stateProvider
.state "main.submodule2",
url: ""
templateUrl: "submodule2.html"
.state "main.submodule2-expanded",
url: "/submodule2" # template shouldn't change
]
Is it even possible to have child states in different modules? If not how would you recommend I do it? Thanks.

There is a working example, where I tried to show how to put angular, ui-router and coffee together. While I am not 100% sure what exactly you were trying to achieve ... you can find some answers and inspiration there.
Firstly the (simplified) index.html
<head>
...
<script src="app.js"></script>
<script src="submodule1.js"></script>
<script src="submodule2.js"></script>
</head>
<body>
<ul>
<a ui-sref="main.submodule1.expanded">main.submodule1.expanded</a>
<a ui-sref="main.submodule2({id:22})">main.submodule2</a>
<a ui-sref="main.submodule2-expanded({id:22})">main.submodule2-expanded</a>
<div ui-view=""></div>
</body>
Now, this would be the app.coffee, where the most important part is the template. That will allow each child to inject its view into this unnamed view template. The other option would be to use absolutely named views, but this keeps it simple:
app = angular.module("darrylsnow", [
"ui.router"
"submodule1"
"submodule2"
]).config [
"$stateProvider"
"$urlRouterProvider"
"$locationProvider"
($stateProvider, $urlRouterProvider, $locationProvider) ->
$stateProvider
.state "main",
template: "<div ui-view />"
abstract: true # because the main module requires the submodules
url: "/"
...
The other files represents really different modules.
The example of submodule1.coffeee shows that even here we are using nesting (the main.submodule1.expanded is child of main.submodule1):
submodule1 = angular.module("submodule1", [
"ui.router"
]).config [
"$stateProvider"
"$urlRouterProvider"
"$locationProvider"
($stateProvider, $urlRouterProvider, $locationProvider) ->
$stateProvider
.state "main.submodule1",
template: "<div ui-view />"
abstract: true
.state "main.submodule1.expanded",
url: "/submodule1" # template shouldn't change
templateUrl: "submodule1.html"
controller: 'Some1Ctrl'
]
submodule1.controller 'Some1Ctrl', [
"$scope"
"$stateParams"
"$state"
($scope, $stateParams, $state) ->
$scope.params = $stateParams;
$scope.state = $state.current;
]
As a different approach we can use siblings as the submodule2.coffee shows:
submodule2 = angular.module("submodule2", [
"ui.router"
]).config [
"$stateProvider"
"$urlRouterProvider"
"$locationProvider"
($stateProvider, $urlRouterProvider, $locationProvider) ->
$stateProvider
.state "main.submodule2",
templateUrl: "submodule2.html"
controller: 'Some2Ctrl'
.state "main.submodule2-expanded",
url: "/submodule2/{id}" # template shouldn't change
templateUrl: "submodule2.html"
controller: 'Some2Ctrl'
...
Well how that all fits together is the best to observe in this plunker

Related

Phoenix Framework and AngularJS templates

How can I load AngularJS templates from a Phoenix framework application? I am using the ui-router to load a template using templateUrl. This is with angular 1.5.
myAppModule.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', function ($stateProvider, $urlRouterProvider, $httpProvider) {
$stateProvider
.state('event', {
url: '/event',
templateUrl: '/templates/event/event.html'
// template: '<h1>Stuff</h1>'
})
$urlRouterProvider.otherwise('/event');
}]);
Loading an inline template works, so it is clearly the mechanism for loading templates which I am struggling with.
Are there any settings in the brunch config I need to change?
Thank you
Eamon
Well, it's not too simple. I fall into the same problem, so here is my solution. Phoenix Framework has a directory where you can put your HTML files, the name of the directory is web/static/assets, you can set your files there, but if you need to create a directory like web/static/assets/templates you need to add exception in the endpoint.ex, like:
plug Plug.Static,
at: "/", from: :app, gzip: false,
only: ~w(css fonts images templates js favicon.ico robots.txt)
Now you can recompile and run your Elixir code and load your file like:
myAppModule.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', function ($stateProvider, $urlRouterProvider, $httpProvider) {
$stateProvider
.state('event', {
url: '/event',
templateUrl: 'templates/event/event.html'
})
$urlRouterProvider.otherwise('/event');
}]);
I hope that works for you too.

AngularJS splitting routes results in module is not available

I'm using AngularJS 1.3 on a large single page app and I'm trying to split up my routes file.
I'm following the advice from the following question and have run into problems finding my new module
How might I move AngularJS Routes into separate file
My routes-work-desc.js.coffee is
angular
.module('workDesc')
.config ['$stateProvider', '$urlRouterProvider', ($stateProvider, $urlRouterProvider) ->
$stateProvider
.state "work_descs",
parent: "default"
url: "/work_descs"
views:
"":
controller: "WorkDescsController"
templateUrl: "/assets/work_descs/index.html.erb"
]
My routes.js.coffee file is
angular
.module('paisApp', ['workDesc'])
.config ['$stateProvider', '$urlRouterProvider', ($stateProvider, $urlRouterProvider) ->
$urlRouterProvider
.otherwise("/")
$stateProvider
.state "default",
abstract: true # Parent route/template
views:
"":
controller: "ApplicationController"
templateUrl: "/assets/layouts/default.html.erb"
]
The error I get is
Error: [$injector:nomod] Module 'workDesc' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.3.20/$injector/nomod?p0=workDesc

Angular routing not working for a link but working for all others

I got the routing working for all my links but one and don't understant what happens.
I include dependency to ui-router :
var app = angular.module('CMT', ['ui.router', 'angularCharts', 'uiSwitch']);
configure a new state :
app.config([
'$stateProvider',
'$urlRouterProvider',
function ($stateProvider, $urlRouterProvider){
$stateProvider.state('writeareview', {
url: '/#/writeareview',
templateUrl: 'partials/writeareview.html',
controller: 'writeAReviewController'
});
Declare the controller :
app.controller('writeAReviewController', ['$scope', function ($scope){
}]);
And my template is located in the folder "partials" with following code :
<div ng-controller="writeAReviewController"></div>
My link in index.html :
<li ng-class="getClass('/writeareview')">Donner un avis</li>
Any help would be much appreciated.
The routing url shouldn't have a hash in it... that is done internally.
Change:
url: '/#/writeareview'
To
url: '/writeareview'
And change the href to only include hash with no leading /:
<a href="#/writeareview">
Or use
ui-sref="writeareview"
Also you will be invoking 2 instances of your controller when you include the controller in routing and in ng-controller. Remove the ng-controller duplicate

Angular-Meteor - How can I include ng template in package-based design?

I have an Angular-Meteor application working. I would like to package Angular templates and associated controller into a Meteor package, and inject these templates into my main application by adding that package.
What is best approach?
Update 2015-08-26 - I figured out how to add a template, documented below. But how to have a Meteor package inject the template's Angular controller into the base application?
A key tie-in is Angular UI-router.
I have a base application that includes my package named packageprefix:packagename. Inside this package I have my code in the root of the package folder:
myPackagedPage.ng.html - the Angular HTML template
myPackagedPage.js - the associated Angular controller
From my main application, I tried creating a route to my Angular template like so:
angular.module('parentModule',[
'angular-meteor',
'ui.router',
'angularify.semantic.sidebar'
])
.config(['$urlRouterProvider', '$stateProvider', '$locationProvider',
function($urlRouterProvider, $stateProvider, $locationProvider){
console.log("app.js config!");
$locationProvider.html5Mode(true);
$stateProvider
.state('home', {
url: '/',
templateUrl: 'client/views/home/home.ng.html',
controller: 'HomeCtrl'
})
.state('myPackagedPage', {
url: '/myPackagedPage',
templateUrl: 'packageprefix_packagename/myPackagedPage.ng.html',
controller: 'MyPackagedPageCtrl'
})
;
$urlRouterProvider.otherwise('/');
}])
The application successfully finds the myPackagedPage.ng.html file and renders it. But how to add the controller?
I tried adding this in my package but the controller functions does not get called.
console.log("myPackagedPage.js loaded");
angular.module('parentModule')
.controller('MyPackagedPageCtrl', ['$scope',
function($scope){
console.log("MyPackagedPageCtrl");
}])
;
I get an error:
Argument 'MyPackagedPageCtrl' is not a function, got undefined
I have this working now. Here is the approach that works for me, to inject an Angular Controller + template in a Meteor package, into the containing application.
In my package.js, I have this
Package.onUse(function(api) {
api.versionsFrom('1.1.0.3');
api.use('angular:angular#1.4.4', 'client');
api.use("urigo:angular#0.9.3", 'client');
api.use("session#1.1.0", 'client');
//api.use('angularui:angular-ui-router#0.2.15', 'client');
api.addFiles('interests.js', 'client');
api.addFiles('interests.ng.html', 'client');
api.export("InterestsCtrl", "client")
});
Note you must export your controller, so that the parent application may access it.
In my package, called ramshackle:bigd-interests, I have these files at the root level: package.js, interests.ng.html, and interests.js. interests.js injects the Angular controller, the Anguilar UI-router route to the template, and a sidebar link into the parent application. It accomplishes this by using the Meteor Session. I played with other means of doing this but Session was the only thing that worked. Just be careful to properly scope your session variable names.
//add controllers
var controllers = Session.get("BIGD.controllers");
if (!controllers) controllers = {};
var interestsCtrlSpec = "'$scope', InterestsCtrl";
InterestsCtrl = function($scope){
console.log("InterestsCtrl running");
};
controllers.InterestsCtrl = interestsCtrlSpec;
Session.set("BIGD.controllers", controllers);
//add routes
var routes = Session.get("BIGD.routes");
if (!routes) routes = {};
routes.interests = {
url: '/interests',
templateUrl: 'ramshackle_bigd-interests_interests.ng.html',
controller: 'InterestsCtrl'
};
Session.set("BIGD.routes", routes);
//add sidebar links
//the key determines sorting order
var sidebar = Session.get("BIGD.sidebar");
if (!sidebar) sidebar = {};
sidebar["interests"] = {
url: '/interests',
templateUrl: 'ramshackle_bigd-interests_interests.ng.html',
controller: 'InterestsCtrl',
rank: 5
};
Session.set("BIGD.sidebar", sidebar);
var interestsItem = {label: 'Interests', link: '/interests', icon: "rocket"};
In my parent application's app.js , I dynamically loaded the controllers and routes from the session like this:
angular.module('bigdam',[
'angular-meteor',
'ui.router',
'angularify.semantic.sidebar',
'nvd3',
'leaflet-directive',
'ui.router.history'
])
.config(['$urlRouterProvider', '$stateProvider', '$locationProvider',
function($urlRouterProvider, $stateProvider, $locationProvider){
//console.log("app.js config!");
$locationProvider.html5Mode(true);
//add a static state
$stateProvider
.state('home', {
url: '/',
templateUrl: 'client/views/home/home.ng.html',
controller: 'HomeCtrl'
});
//add the dynamic routes/states from other Meteor packages
for (var stateId in routes) {
var route = routes[stateId];
$stateProvider
.state(stateId, route);
}
$urlRouterProvider.otherwise('/');
}])
;
//Declare the controllers from plugins
for (var controllerId in controllers) {
var controllerSpec = controllers[controllerId];
var controllerSpecArray = eval("[" + controllerSpec + "]")
angular.module('bigdam').controller(controllerId, controllerSpecArray);
}
So now, when I create a new Meteor package, and follow the convention described above, its controllers, routes, and sidebar links get loaded into the main application.

angularjs ui-router: Unknown provider: $stateProvider

I'm having troubles using the ui-router plugin of AngularJS:
angular.module('myApp', []).
config(['$routeProvider', '$stateProvider', function($routeProvider, $stateProvider) {
$stateProvider
.state('mandats', {
url: '/domiciliations/mandats',
templateUrl: 'domiciliations/views/mandats.html',
controller: 'mandatsCtrl'
});
}])
I then get this error at startup:
Unknown provider: $stateProvider
I've included the javascripts in this order:
<script src="/Scripts/libs/angular/angular.js"></script>
<script src="/Scripts/libs/angular/angular-resource.js"></script>
<script src="/Scripts/libs/angular/angular-ui-states.js"></script>
What could be the issue ?
[EDIT]
I've got rid of the error message by adding 'ui.compat' as a dependency of myApp. I saw that in the sample code of ui-router but nowhere in the documentation. What is this about ?
Nevertheless, it still does not work. I've added ui-view to a div in the application index file. But the page remains blank.
The following piece of code should do the job. At least in my case, so let me know if it works or not.
angular.module('myApp', ['ui.compat']).
config(['$routeProvider', '$stateProvider', function($routeProvider, $stateProvider) {
$stateProvider
.state('mandats', {
url: '/domiciliations/mandats',
templateUrl: 'domiciliations/views/mandats.html',
controller: 'mandatsCtrl'
});
}])
Now about your issue with the page being empty. For sure the URL you have in the browser is not matched with any defined in your state. Try this '#/domiciliations/mandats' in your browser and see if the view gets rendered appropriately. Not that you absolute url should be something similar with http://[HOST_NAME]/home.html#/domiciliations/mandats.
You just need to include ui-router module as dependency.
like following
angular
.module('myApp', ["ui.router"])
.config(['$routeProvider', '$stateProvider', function($routeProvider, $stateProvider) {
...
}]);
Including the angular-ui v0.0.2 will fix the problem.

Resources