I am new to javascript, typescript and AngularJS and am currently trying to learn the language by building a sample app.
I am an experienced programmer coming from an Actionscript / Flex background.
As I come from Actionscript I really like the 'controller as' syntax as I don't need to deal with the untyped $script object floating around and doing magic stuff that I can't control (my opinion as someone who distrusts javascript ;-).
In this example though I want to make an html page that displays loading info for a variety of different services that will load data. I envisage using this html fragment as a header at the top of a page which could show loading info for a list of albums, a list of images or even just when you're logging in.
This example is not what I would do in production as there are a number of things that I do not like about this approach but this is a learning exercise so I am trying to figure out how this would work.
I have the following TypeScript code:
LoadingModule
export interface ILoadable
{
isLoading : boolean;
isLoaded : boolean;
loadingMessage : string;
errorMessage : string;
}
export class LoadingController implements ILoadable
{
// Constructor
constructor( private service? : ILoadable )
{
}
// Properties
public get isLoading() : boolean
{
return this.service ? this.service.isLoading : true;
}
public get isLoaded() : boolean
{
return this.service ? this.service.isLoaded : false;
}
public get loadingMessage() : string
{
return this.service ? this.service.loadingMessage : "Loading...";
}
public get errorMessage() : string
{
return this.service ? this.service.errorMessage : "There was a fault.";
}
}
App
app.config( ($routeProvider) => {
$routeProvider
.when( '/albums', { templateUrl: './pages/albums.html' } )
.when( '/album', { templateUrl: './pages/album.html' } )
.when( '/pictures', { templateUrl: './pages/pictures.html' } )
.when( '/loggingIn', { templateUrl: './pages/loading.html', controller : "loginController" } )
.when( '/loadingAlbums', { templateUrl: './pages/loading.html', controller : "loadingAlbumsController" } )
.when( '/404', { templateUrl: './pages/404.html' } )
.otherwise( { redirectTo: '/loggingIn' } )
});
app.controller( "loginController", [ "authenticator", Picasa.LoadingController ] );
app.controller( "loadingAlbumsController", [ Picasa.LoadingController ] );
Loading.html
<div ng-controller="loginController as loadable">{{loadable.loadingMessage}}</div>
This all works great but I always get the loginController passed into the html page. This is obviously because I refer to it in the html page so this overrides me specifying a controller in the route provider setup.
My question is can I use the "controller as" setup in an html page that will have different controllers injected?
I want the html page setup to work with an ILoadable.
I don't want to have to inject $scope into my controller and set properties on it.
Ideally I'd be able to inject a service directly into the html page so that I don't have to have the controller wrapper.
Many Thanks
You can use the controllerAs syntax for the routing:
$routeProvider
.when( '/loggingIn', { templateUrl: './pages/loading.html', controller : "loginController", controllerAs: 'login' } )
Your Index.html should have ng-view="" which is where the view will be substituted in.
This will enable you to do {{login.message}} in the views.
Related
I have an app where a module looks at the URL. If the URL has anything past the "Document" section then one state is set using the information beyond that URL. If the URL has nothing beyond that point, then another state is set. So the two URLs are ...
www.xyz.com/Document/
and
www.xyz.com/Document/someData
I am currently solving the problem as below. This works, but I really need the two states to be in the same module and I can't figure out how to make that happen.
So, instead of the second state applying to app.documentEmpty, I want it to apply to app.document.empty.
angular
.module('app.document', [
'app.document.worksheet',
'app.document.tableOfContents',
'app.document.properties',
'app.document.bibliography',
'app.document.inputs',
'app.document.datasets',
'app.document.fileAsFunction',
'app.document.importedFunctions',
'app.document.directory'
])
.config(config);
/** #ngInject */
function config($stateProvider, $translatePartialLoaderProvider, msApiProvider, msNavigationServiceProvider)
{
$stateProvider.state('app.document', {
url : '/Document/{path:.*}/',
views : {
'content#app': {
templateUrl: 'app/main/apps/document/worksheet/worksheet.html',
controller : 'DocumentController as vm'
}
},
resolve : {
Documents: function (msApi)
{
return msApi.resolve('document.documents#get');
},
emptyDocuments: function (msApi)
{
return msApi.resolve('document.emptyList#get');
}
},
bodyClass: 'worksheet'
}).state('app.documentEmpty', {
url : '/Document/',
views : {
'content#app': {
templateUrl: 'app/main/apps/document/documentEmpty.html',
controller : 'DocumentController as vm'
}
},
resolve : {
Documents: function (msApi)
{
return msApi.resolve('document.documents#get');
},
emptyDocuments: function (msApi)
{
return msApi.resolve('document.emptyList#get');
}
},
bodyClass: 'document'
});
The problem is that whenever I remove the second state from above and replace it with something like what's below in the directory submodule, the URL is not recognized and the proper page is not loaded. There are several other submodules that do not depend upon URL and they work fine.
$stateProvider.state('app.document.directory', {
url : '/Document/',
views : {
'content#app': {
templateUrl: 'app/main/apps/document/directory/directory.html',
controller : 'DocumentController as vm'
}
},
bodyClass: 'directory'
});
Is it not possible to route to submodules via URLs?
You are confused with state and module.
The dot notation in ui-router indicates child states. So when you are doing this: $stateProvider.state('app.document.directory',{}), the views will only be populated IF your app.document exists and it has a ui-view directive in it. Added to that, the url parameters set in the state object will be appended AFTER the url defined in app.document state.
Say for your example above, you have defined a state like this:
$stateProvider.state('app.document', {
url : '/Document/{path:.*}/',
//... omitted for brevity
Now, this will work:
.state('app.documentEmpty', {
url : '/Document/',
//... omitted for brevity
because app.document and app.documentEmpty are sibling states.
On the other hand, this will not work:
$stateProvider.state('app.document.directory', {
url : '/Document/',
//... omitted for brevity
because app.document.directory is the child state of app.document (child states are defined by the dot notation, i.e .directory)
So in order to work, you have to rethink of your states hierarchy. In your app.document.directory module, define a state app.documentDirectory (note that there is no dot after the document):
angular.module("app.document.directory",[])
.config('$stateProvider',function($stateProvider){
$stateProvider.state('app.documentDirectory',{ //not app.document.directory, because this will make it a child state of app.document
url:'/directory'
//omitted for brevity
})
});
I am getting parse error
Syntax Error: Token '{' is an unexpected token at column 8 of the expression [user= ${user}] starting at [{user}].
home.html
<body ng-controller="mainCtrl" ng-init="user= ${user}">
Referring this example ,I am sending model to angularjs
Getting data from Spring MVC in Angular JS in the initial view call.
controller.js
angular.module('userSystem', [ 'ngRoute' ]).config(
function($routeProvider, $httpProvider) {
$routeProvider.when('/', {
templateUrl : 'home.html',
controller : 'home'
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation'
}).otherwise('/');
}).controller('mainCtrl',
function($rootScope, $scope, $http, $location, $route) {
})
});
Spring Controller
#RequestMapping(value = "/", method = RequestMethod.GET)
public String getIndex(Model model)
{
model.addAttribute("user","user");
return "home";
}
Please let me know what is wrong here and how to fix it. Thanks
Try changing your controller, so instead of sending just a String with the view name, you send a ModelAndView object:
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView getIndex() {
ModelAndView mv = new ModelAndView("home");
mv.addObject("user", "USER_NAME");
return mv;
}
On the other hand, are you using any templating framework (thymeleaf, velocity...)? Cause in that case, your problem may be in how you pick up the model attribute in front-end.
EDIT (as an answer to the templating framework question):
In case you're using thymeleaf, you would need to do something like this in your index.hmtl:
<body ng-controller="mainCtrl as main" data-th-attr="ng-init='main.user=\''+${user}+'\''>
I found myself with this problem in the past.
If you remove the curley braces around user and change to this it will remove your syntax error for you:
<body ng-controller="mainCtrl" ng-init="user= $user">
Taken that you're spring code and population of $user is working correctly.
I had used resolve from angularjs to populate with user details.
$routeProvider.when('/', {
templateUrl : 'home.html',
controller : 'home',
resolve: {
message: function(messageService){
var promise= messageService.getMessage();
promise.then(data){
allConstants.user=data;
}
return promise;
}
}
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation'
}).otherwise('/');
Here allConstants is a varible accessible by entire application controllers
Ive been trying to setup my Angular webApp to work with prerender.io however my app does not have any of the built in middleware they desire; I have decided I want to work with express.js as most the tutorials set up the project using this.
Example setup
I am able to create a project that works with prerender if I make it new using the express.js generator plugin. Thats all good and dandy but it doesn't really help me out; as I am not creating a project, rather I want to add prerender to my existing project. Is there a way to create a new express.js project and import an entire angular app (with all folder structure; etc, basically moving the app's root directory over to the new express app)into it and have all the routing etc be unaffected?
How would one go about adding Prerender to an already created angularJS webapp? (like the basic one seen below) I've tried a bunch of times throughout the day and just ended up installing a ton of ugly dependancies into my project and having to delete them all; any help is appreciated. ::
app.js:
// app.js
var app = angular.module('myPremadeApp', ['ngRoute'])
$locationProvider.hashPrefix('!');
.config(function($routeProvider, $locationProvider){
$routeProvider.when('/', {
templateUrl : 'views/homeView.html',
controller: 'homeController'
})
.when('/about', {
templateUrl : '/views/aboutView.html',
controller: 'aboutController'
})
.when('/features', {
templateUrl : '/views/featuresView.html',
controller : 'featuresController'
})
.otherwise({
redirectTo : '/'
});
});
function mainController($scope) {
$scope.seo = {
pageTitle : '', pageDescription : ''
};
}
function homeController($scope) {
$scope.$parent.seo = {
pageTitle : 'setup the thing',
pageDescripton: 'stuff for description'
};
}
function aboutController($scope) {
$scope.$parent.seo = { pageTitle : 'About',
pageDescripton: 'We are a content heavy website so we need to be indexed.'
};
}
function featuresController($scope) {
$scope.$parent.seo = { pageTitle : 'Features', pageDescripton: 'Check out some of our awesome features!' };
}
so, following best practice I've started using ng-strict-di. It's worked well so far, but I have hit the following problem using ui-router
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: function($scope) {
$scope.dogs = ['Bernese', 'Husky', 'Goldendoodle'];
}
})
this causes angular to barf with the "Error: error:strictdi
Explicit annotation required" error.
I know that I should be using the inline bracket notation, or $inject, but obviously can't put it in this code as is.
I was thinking that I could declare the controller in another part of the script, with $inject and then just reference it in the code ?
function GoodController1($scope) {
}
GoodController1.$inject = ["$scope"];
and then
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: GoodController1
})
would this work ? Are there any problems with this approach ?
There are no problems, with this approach. I am using typescript, and the generated syntax of controlelr class is almost the same as yours.
Here is a working plunker
...
// the contoller funciton to be instantiated
// by angular using new
var GoodController1 = function($scope){
$scope.title = "good title";
};
// set of dependencies
// (in typescript that would be a static property)
GoodController1.$inject = ["$scope"];
// before angular 2.0, this is the must
// we still have to register controller in the module
app
.controller('GoodController1', GoodController1)
...
and later in state:
.state('good', {
url: "/good",
templateUrl: 'tpl.html',
controller: "GoodController1",
})
check it here
I am building the front-end app for a REST service, and most of the resources are located at long urls where most of the segments are dynamic based on records created in the app by users. Obviously I won't be able to know or create hardcoded routes for most of these records.
My question I suppose is how to handle urls like this with ui-router:
<semester>/<program>/<class>/enrollment
or
<semester>/myclasses/<class>/assignments
There is always at least one static, predictable segment in every resource url, and the segments are always in a predictable order.
Do I make abstract states for each segment in the url like:
$stateProvider.state(semester)
.state(program)
.state(class)
.state(assignments);
??
I've tried building routes that look like this:
param = {
name: "param",
url: "/:hue/:temp/param",
templateUrl: "http://localhost:81/route/tpl/param.tpl.html",
controller: "paramController"
};
but it ends up sending me back to the .otherwise() state when I link to the "param" state.
Thanks for any help, I'm a bit stumped.
I had a similar problem and I quickly coded this:
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state('app', {
url : "/app",
abstract : true,
templateUrl : "layout/navigation-drawer.tpl.html"
}).state('app.help', {
url : "/help",
views : {
'menuContent' : {
templateUrl : "layout/help.html"
}
}
}).state('app.settings', {
url : "/settings",
views : {
'menuContent' : {
templateUrl : "layout/settings.html"
}
}
}).state('app.rate-us', {
url : "/rate-us",
views : {
'menuContent' : {
templateUrl : "layout/rate-us.html"
}
}
}).state('app.projects', {
url : "/projects",
views : {
'menuContent' : {
templateUrl : "layout/projects.html",
controller : 'ProjectsCtrl'
}
}
}).state('app.forms', {
url : "/:project_name/forms",
views : {
'menuContent' : {
templateUrl : "layout/forms.html",
controller : 'FormsCtrl'
}
}
}).state('app.entries', {
url : "/:project_name/:form_name/entries/:form_id",
views : {
'menuContent' : {
templateUrl : "layout/entries.html",
controller : 'EntriesCtrl'
}
}
});
which is working, "/:project_name/:form_name/entries/:form_id" will resolve to something like app/Mirko_test/University/entries/1
Ok so I tested this out and it works in my case. It fails when the state is only a parameter, but it seems as long as each state has a non-parameterized bit, ui-router is able to parse down to children states. I haven't seen this case demonstrated or explained anywhere before. Most tutorials only cover simple hardcoded nested states and not parameterized ones.
It's not ideal, but it works.
I hope this helps someone else facing this issue. :)
var app = angular.module('app', ['ui.router'])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function ( $stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("/");
$locationProvider.html5Mode(true);
var semester = {
name: "semester",
abstract: true,
url: "semester/:sem",
templateUrl: "http://localhost:81/route/to/semtemplate.tpl.html",
controller: "semesterController"
},
program = {
name: "program",
parent: sem,
url: "program/:prg",
templateUrl: "http://localhost:81/route/to/prgtemplate.tpl.html",
controller: "programController"
},
classes = {
name: "classes",
parent: prg,
url: "/classes",
templateUrl: "http://localhost:81/route/to/clstemplate.tpl.html",
controller: "classesController"
};
$stateProvider.state(sem)
.state(prg)
.state(classes);
}]);
app.controller('paraController', ['$scope', '$stateParams', '$state',function($scope, $state, $stateParams){
console.log('paraController instantiated');
$scope.sem = $stateParams.params.sem;
$scope.prg = $stateParams.params.prg;
}]);
As this is a hierarchical REST api this pattern works perfectly, and when also taking advantage of scope inheritance from each controller it should be a good fit for my project. I haven't tested extremes of nested states, but it would be interesting to see how it behaves under even more parameterized states. The only limitation I have found is that each state needs to have a non-parameterized part as well. So /:sem fails but semester/:sem works fine.
It's not ideal as it makes URLs longer, but I haven't found a workable alternative.
I know this question is old, but I had essentially the same question recently and found the official answer. Apparently, angular ui-router now supports the notion of URL Parameters in URL Routing, which allow you to specify parameters, along the lines of the following:
$stateProvider
.state('contacts.detail', {
url: "/contacts/:contactId",
templateUrl: 'contacts.detail.html',
controller: function ($stateParams) {
// If we got here from a url of /contacts/42
expect($stateParams).toBe({contactId: 42});
}
})
For more info, go here: https://github.com/angular-ui/ui-router/wiki/URL-Routing#url-parameters