I have a very simple website which uses Angular.js to display its content. I started learning it 2 days ago, and following the official tutorial gave no issues at all.
This is my js file:
var Site = angular.module('Website', []);
Site.config(function ($routeProvider) {
$routeProvider
.when('/home', {templateUrl: 'parts/home.html', controller: 'RouteController'})
.when('/who', {templateUrl: 'parts/who.html', controller: 'RouteController'})
.when('/what', {templateUrl: 'parts/what.html', controller: 'RouteController'})
.when('/where', {templateUrl: 'parts/where.html', controller: 'RouteController'})
.otherwise({redirectTo: '/home'});
});
function AppController ($scope, $rootScope, $http) {
// Set the slug for menu active class
$scope.$on('routeLoaded', function (event, args) {
console.log(args);
$scope.slug = args.slug;
});
}
function RouteController ($scope, $rootScope, $routeParams) {
// Getting the slug from $routeParams
var slug = $routeParams.slug;
var pages = {
"home": {
"title": "Samuele Mattiuzzo",
},
"who": {
"title": "All you'll get, won't blog"
},
"what": {
"title": "Shenanigans about this website"
},
"where": {
"title": "Where can you find me on the net?"
}
};
$scope.$emit('routeLoaded', {slug: slug});
$scope.page = pages[slug];
}
As you can see, it's very simple, it just need to return a page title based on the page slug. In the template (where I load my app with <body ng-controller="AppController">), inside the <ng-view> directive I have one of those partial templates loaded (which is currently working and displaying static content) but I cannot see the content of {{page.title}}.
I have Batarang enabled on my browser and I'm testing my website with web-server.js, but I've read that Batarang has some issues with variables and scopes and always returns undefined, so that's why I added that console.log statement. Doesn't matter what I try to print (args, slug or page, obviously in different parts of the js), it's always undefined.
What am I exactly doing wrong here? Thanks all
None of your controllers are being associated with your "Site".
I believe if you change your free functions to be associated with Site this should get you on the right track. Also, you can simplify your code slightly since the information you're looking for is contained in the $location and not $routeParams.
Site.controller("RouteController", function($scope, $location) {
var slug = $location.path();
var pages = {
"/home": {
"title": "Samuele Mattiuzzo",
},
"/who": {
"title": "All you'll get, won't blog"
},
"/what": {
"title": "Shenanigans about this website"
},
"/where": {
"title": "Where can you find me on the net?"
}
};
$scope.page = pages[slug];
});
Additionally, in your AppController you can watch for $routeChangeSuccess instead of notifying on a location change from your RouteController:
Site.controller("AppController", function($rootScope) {
$rootScope.$on("$routeChangeSuccess", function() { \\do something }
});
Related
I have an app in Angular which will become large over time and I do not want users to load all the controllers upfront inside the index.html. To overcome some scaling issues I would like to load the controller for a given view when the user visits that given view. I do not want to statically define the controller and view in my router.js but rather source them dynamically. My router.js looks as follows:
var app = angular.module('MyApp', ['ngRoute', 'ui.bootstrap']);
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
controller: 'HomeController',
templateUrl: 'views/home.html'
})
.when('/portfolio/:page', {
controller: 'TopLevelController',
templateUrl: function(params){ return 'views/portfolio/'+params.page+'.html';}
})
.otherwise({
redirectTo: '/'
});
});
There will be a large number of view inside the portfolio folder and each view will have its own controller. Each view is very different due to visualisations and the usage of directives.
One solution I thought would help was found on this link, where I have a top level controller and then source the required JS through this controller.
My top level controller looks like:
app.controller('TopLevelController', ['$scope', '$route', '$controller', function($scope, $route, $controller) {
$scope.loadScript = function(url, type, charset) {
if (type===undefined) type = 'text/javascript';
if (url) {
var script = document.querySelector("script[src*='"+url+"']");
if (!script) {
var heads = document.getElementsByTagName("head");
if (heads && heads.length) {
var head = heads[0];
if (head) {
script = document.createElement('script');
script.setAttribute('src', url);
script.setAttribute('type', type);
if (charset) script.setAttribute('charset', charset);
head.appendChild(script);
}
}
}
return script;
}
};
var controllerToLoad = $route.current.params.page+'Controller.js';
$scope.loadScript('js/controllers/portfolio/'+controllerToLoad, 'text/javascript', 'utf-8');
}]);
An example view in side portfolio is 'PDB.html' and looks like as follows:
<html>
<body ng-controller="PDBController">
{{test}}
Static Text
</body>
</html>
The PDBController looks like:
app.controller('PDBController', ['$scope', '$http', function($scope, $http) {
$scope.test = "inside PDBController";
console.log("i exist");
}]);
But when I visit then view all I get is:
I do not see the text "inside PDBController" nor do I see "i exists" in the console output. Could anyone point out where I cam going wrong or provide a solution?
I'm currently working on an app where a button triggers a method that will emit an event to elsewhere. This works great, however I also want to add a url to trigger this action.
So currently my button looks like this
<a class="addJob" ng-click="addNewJob()" ng-controller="AddJobController"></a>
But what I really want is it to just be
<a class="addJob" href="/new"></a>
Now, I can't figure out how to do the routing for this. It would mean that when I go to /new, the AddJobController should be triggered.
When I go directly to http://www.example.com/new, it should still load the page properly and trigger that action.
I don't want to create a separate page for this route as it is an essential part of the app flow.
(Think of it like when you create a new note in trello.com)
One Option
If you are willing to move to uiRouter, this is a common pattern.
Copied and pasted directly from the uiRouter FAQ
https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state
$stateProvider.state("items.add", {
url: "/add",
onEnter: ['$stateParams', '$state', '$modal', '$resource', function($stateParams, $state, $modal, $resource) {
$modal.open({
templateUrl: "items/add",
resolve: {
item: function() { new Item(123).get(); }
},
controller: ['$scope', 'item', function($scope, item) {
$scope.dismiss = function() {
$scope.$dismiss();
};
$scope.save = function() {
item.update().then(function() {
$scope.$close(true);
});
};
}]
}).result.then(function(result) {
if (result) {
return $state.transitionTo("items");
}
});
}]
})
Second Option
The second options would be to launch the modal the constructor of your controller. I have included a modalFactory. This is a common pattern. It allows your modals to be reusable, and cleans up your controllers. The uiRouter example above should use the factory pattern as well to abstract the modal setup out of the state config.
This example should work with ngRouter.
app.controller('addJobModalController', ['modalFactory', function(modalFactory) {
modalFactory.addJob();
}]);
app.factory('modalFactory', ['$modal', function($modal) {
return {
addJob: function() {
return $modal.open({
templateUrl: 'views/addjob-modal.html',
controller: 'AddJobController',
size: 'md'
});
}
}
}]);
The addJob() method returns the modal's promise. If you want, you can store this promise in the factory to be returned by another method so that another controller or service can act on the result of the modal.
1. Html:
<html>
<head><title>MyApp</title></head>
<body data-ng-app="app">
<ul><li><a data-ui-sref="home" style="cursor:pointer;">Home</a></li></ul>
<div data-ui-view="header"></div>
<div data-ui-view="container"></div>
<div data-ui-view="footer"></div>
<script src="/Scripts/angular-1.2.9/angular.min.js"></script>
<script src="/Scripts/angular-1.2.9/animate/angular-animate.js"></script>
<script src="/Scripts/angular-1.2.9/ui-router/angular-ui-router.js"></script>
<script src="/Scripts/angular-1.2.9/ui-bootstrap/ui-bootstrap-custom-tpls-0.11.0.js"></script>
<script src="/_temp/app/app.js"></script>
</body>
</html>
2. App.js:
'use strict';
var $stateProviderRef = null;
var $urlRouterProviderRef = null;
var app = angular.module('app', ['ui.router']);
app.factory('menuItems', function ($http) {
return {
all: function () {
return $http({
url: '/_temp/app/jsonData/statesJson.js',
method: 'GET'
});
}
};
});
app.config(function($locationProvider, $urlRouterProvider, $stateProvider) {
$urlRouterProviderRef = $urlRouterProvider;
$stateProviderRef = $stateProvider;
$locationProvider.html5Mode(false);
$urlRouterProviderRef.otherwise("/");
});
app.run(['$q', '$rootScope', '$state', 'menuItems',
function ($q, $rootScope, $state', menuItems) {
menuItems.all().success(function (data) {
angular.forEach(data, function (value, key) {
$stateProviderRef.state(value.name, value);
});
**$state.go("home"); <--- SOLUTION**
});
}]);
3. Data:
[
{
"name": "root",
"url": "/",
"parent": "",
"abstract": true,
"views": {
"header": { "template": "header.html" },
"footer": { "template": "footer.html" }
}
},
{
"name": "home",
"url": "/home",
"parent": "root",
"views": {
"container#": { "template": "home page" }
}
}
]
PROBLEM:
The nested views were suppose to show up on load not the click event. I tried to play with the $urlRouterProviderRef.otherwise("/") to no avail.
What must I correct to have the 3 nested views appear on load not a click event.
Thanks.
UPDATE:
I made a Plunker to help show the problem PLUNKER LINK
Radim Köhler
Sweet!! I updated my plunker to reflect multiple states if anyone wants to see for ideas.
The issue here is: timing. The $urlRouterProviderRef.otherwise("/") instruction is used/executed too soon, before the $http.get() returns the state definition, i.e. before states are defined in foreach: $stateProviderRef.state(value.name, value);
If this would preceed the otherwise trigger, it would work (as you experienced for example with local variable, which is available soon enough).
But we can use another feature: $state.go (older doc link, but like it more than new)
So, with this "dynamic" state definition and $state.go ... we can achieve the desired
app.run(['$q', '$rootScope', '$state', 'menuItems',
function ($q, $rootScope, $state, menuItems) {
menuItems
.all()
.success(function (data)
{
angular.forEach(data, function (value, key) {
$stateProviderRef.state(value.name, value);
});
$state.go("home")
});
}]);
See working plunker.
NOTE: the above const "home" could be just anohter part of the passed JSON definition... e.g. instead of [] use an object with default : "home" and states : [...]. Because do not send array anyway. See Anatomy of a Subtle JSON Vulnerability, cite:
One common mitigation is to make sure that your JSON service always returns its response as a non-array JSON object.
I have an AngularJS app that works fine in the browser when served by 'grunt serve'. However, trying to run the app as a Chrome extensions fails. The initial page (the '/' route) displays, but when I press a link to '/#/products/add' I get a 'Webpage not found' error.
No webpage was found for the web address: chrome-extension://acekeiblhdhhbgoagmeegclohfncadjg/#/products/add
Error code: ERR_FILE_NOT_FOUND
Why does the first route work, but the second route fails?
My manifest is bare bones currently, but I've tried various permissions (but the fact that the root route is working makes me think that other routes should work).
{
"manifest_version": 2,
"name": "My app",
"description": "Does stuff",
"version": "1.0",
"permissions": [
"<all_urls>"
],
"browser_action": {
"default_icon": "app/images/icon.png",
"default_popup": "app/index.html"
}
}
The app.js file looks like this:
'use strict';
angular.module('myApp', [
'ngResource',
'ngSanitize',
'ngRoute',
'ngStorage'
])
.config(function ($routeProvider) {
$routeProvider.
when('/products', {
templateUrl: 'views/product_pages/product_list.html',
controller: 'ProductListCtrl'
}).
when('/products/add', {
templateUrl: 'views/product_pages/product_add.html',
controller: 'ProductAddCtrl'
}).
when('/products/:productId', {
templateUrl: 'views/product_pages/product_edit.html',
controller: 'ProductEditCtrl'
}).
otherwise({
redirectTo: '/products'
});
})
.run(function () {
// removed for brevity
});
And the product add controller reads as follows:
.controller('ProductAddCtrl', ['$scope', '$location', 'productPageCollection',
function ($scope, $location, productPageCollection) {
$scope.showError = function(ngModelController, error) {
return ngModelController.$error[error];
};
$scope.processForm = function() {
productPageCollection.add({ 'name' : $scope.product.name });
$location.path('/products');
};
}])
Any ideas?
Your link should point to #/products/add, not /#/products/add. The second option attempts to navigate to the root folder of your extension.
I am building a really simple demo Angular application, to see how it compares to Backbone, and I've got stuck when it comes to routing, although I've read the routing tutorial.
I'd like the route in my demo app to update whenever the <select> element changes, to /LHR or /SFO or whatever the new value of the select is (and presumably /#LHR etc in browsers without the History API).
I'd also like the router to handle the initial path when the page loads, and set up the default value of the <select> option.
Here is my HTML - I only have one template:
<html ng-app="angularApp">
[...]
<body ng-controller="AppCtrl">
<select ng-model="airport" ng-options="item.value as item.name for item in airportOptions" ng-change='newAirport(airport)'></select>
<p>Current value: {{airport}}</p>
</body></html>
And here is my JS in full:
var angularApp = angular.module('angularApp', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/:airportID', {templateUrl: 'angular.html', controller: AppCtrl}).
otherwise({redirectTo: '/'});
}]);
angularApp.controller('AppCtrl', function AppCtrl ($scope, $http, $routeParams) {
$scope.airportOptions = [
{ name: 'London Heathrow', value: 'LHR' },
{ name: 'San Francisco International', value: 'SFO' },
{ name: 'Paris Charles de Gaulle', value: 'CDG' }
];
$scope.airport = $scope.airportOptions[0].value; // Or the route if provided
$scope.newAirport = function(newAirport) {
console.log('newAirport', newAirport);
};
});
I'm not at all sure that I've set up the route provider correctly, and currently it's giving me Uncaught ReferenceError: AppCtrl is not defined from angularApp. What am I doing wrong?
UPDATE: I fixed the ReferenceError by putting AppCtrl in quotes, thanks commenter. Now my problem is that $routeParams is empty when I try to navigate to /#LHR. How can I get the route parameter?
UPDATE 2: Got the route working - feels a bit hacky though. Am I doing it right?
var angularApp = angular.module('angularApp', []);
angularApp.config(function($routeProvider, $locationProvider) {
$routeProvider.
when('/:airportID', {templateUrl: 'angular.html', controller: "AppCtrl"}).
otherwise({redirectTo: '/'});
});
angularApp.controller('AppCtrl', function AppCtrl ($scope, $routeParams, $location) {
$scope.airportOptions = [
{ name: 'London Heathrow', value: 'LHR' },
{ name: 'San Francisco International', value: 'SFO' },
{ name: 'Paris Charles de Gaulle', value: 'CDG' }
];
var starting_scope = null;
($location.path()) ? starting_scope = $location.path().substr(1) : starting_scope = $scope.airportOptions[0].value;
$scope.airport = starting_scope;
$scope.newLeague = function(newLeague) {
$location.path("/" + newLeague);
}