Get templateUrl from ui-router - javascript

Maybe the title isn't really clear.
I'm working on two nested Angular apps. Both have their own router built with ui-router. If I call a state that is unknown by the main router, I'd like it to search for this state in the sub-app router to get the templateUrl and url related to this state.
I thought about creating a service with a parser. It could parse the file to find the data I want. This solution is probably not the best option I have, that's why I wanted to know if there is a specific function/method in ui-router to achieve it. From what I read on ui-router doc, it seems not :/
Feel free to ask for more details or to suggest another solution which can match with my goal :)

You can achieve this by using dynamic parameters in your $stateProvider configuration defined on your sub-module. So you have some anchored routes on the main module, and if there is a match, ui-router will simply fetch the associated template. If there is no matched absolute route/url, then ui-router falls back on your parameterised route, like so:
// anchored
$stateProvider
.state('mainAppState', {
url: '/anchored',
controller: 'myCtrl'
})
// sub module
.state('subAppState', {
url: /:parameter
resolve: { // use the resolve and $stateParams to get some parameter with which you can make a request to your API }
templateProvider: { // inject a service and use it to call your backend }
You can use a the parameter, which gets passed to $stateParams, which gets passed to your resolve block functions, which can retrieve some data or process the data from $stateParams, that can then be used by templateProvider to provide a meaningful fallback URL. That's because templateProvider receives all resolves. It will always wait for all promises in the resolve block to resolve one way or another before executing.
That way you can catch any unmatched URLs from the main app in your sub app. Hope this helps.
See my issue here: ui-router: async data in templateUrl function
Oh and one more thing - if you are going to mangle your JS in your build process, make sure you define your templateProvider function with .$inject = [ ... ]. If not, you'll get an unknown provider error. ngAnnotate will NOT catch the dependencies in your templateProvider function. Alternatively you can just use /* #ngInject */, which worked for me.

Related

Node.js and angular user authorization

How can i block request in node.js for HTML files when user was not authorized. I have html tree something like this:
app.use('/templates', express.static(path.join(__dirname, 'templates')));
index.html (for all)
panel.html (only for authorized)
settings.html (only for authorized)
Is any good and clearcode solutions for block this content ?? I think about something like this:
router.get('/templates/panel.html', function(req, res, next) {
if (req.session && req.session.user) {
// return content
} else {
// block content or send error.html
}
});
My ng-router config:
when('/panel', {
templateUrl: 'templates/panel.html',
controller: 'panelController',
// maybe controls authorize here to
}).
But what will happen when we use $templateCache?
Assuming you don't want other users to access some information, not to hide html templates.
Probably the best way would be to compile all your templates to $templateCache. Then inside your routing you could use the resolve property read here that:
An optional map of dependencies which should be injected into the
controller. If any of these dependencies are promises, the router will
wait for them all to be resolved or one to be rejected before the
controller is instantiated. If all the promises are resolved
successfully, the values of the resolved promises are injected and
$routeChangeSuccess event is fired. If any of the promises are
rejected the $routeChangeError event is fired. For easier access to
the resolved dependencies from the template, the resolve map will be
available on the scope of the route, under $resolve (by default) or a
custom name specified by the resolveAs property (see below). This can
be particularly useful, when working with components as route
templates.
this would allow you to do requests to server side where you will check for user privileges to open desired page.
Plus you would like to have some kind of authorization check on the server side when serving information for those pages-that-need-special-privileges.
It is also good idea to namespace your resources so authorization can be done easier and in 1 place.

UI-Router. How can I navigate to a page in my project that I don't want to include as a State?

I'm still learning Angular and UI-Router... as I pretty much went the UI-Router route because everyone was telling me it's the way to go..
I originally had the login page as part of all the States.. but decided it's best to keep it as a separate stand-alone page
But I can't figure out how to redirect to the Login page...
app.config(['$urlRouterProvider', '$stateProvider', function ($urlRouterProvider, $stateProvider) {
$urlRouterProvider
.otherwise('');
//what i used to have, will remove this
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'Account/Login',
})..etc
so now if i want to redirect to the actual URL now.. how can i go about doing this?
I've tried:
$location.url('/Account/Login');
$location.path('/Account/Login');
but they just seem to append to the URL. How can I use UI-Router and go to a page in my project that is not a state?
You always have access to any of the native javascript window methods. Angular provides an injectable reference to it as $window
$window.location = '/non-angular-url';
// OR
window.location = '/non-angular-url';
$window: A reference to the browser's window object. While window is globally available in JavaScript, it causes testability problems, because it is a global variable. In angular we always refer to it through the $window service, so it may be overridden, removed or mocked for testing.

Run controllers only after initialization is complete in AngularJS

I have some global data that needs to be loaded before any controller is executed in my AngularJS application (i.e. resolve dependencies globally in AngularJS).
For example, I have a UserService with the getCurrentUser() method which does a request to the backend server in order to get data about the currently authenticated user. And I have a controller that needs this data in order to launch yet another request (for example to load user's balance).
How can I achieve that?
Update
Please consider using method specified in the «Asynchronously Bootstrapping AngularJS Applications with Server-Side Data» article if possible.
You can use the angular-deferred-bootstrap module to achieve that now!
I'm not sure about validity of this answer anymore, you can still use the ideas, but be sure to properly test it with your actual code. I will try to keep this answer up to date with never technologies.
Old answer
There are several approaches to the problem of asynchronous application initialization.
When it comes to data that must be resolved before a single controller is called - you can easily use resolve option of ngRoute's $routeProvider. However, when you need some global data to be loaded before ANY controller is called - you have to improvise.
I've tried to collect all possible solutions in this answer. I'm providing them in the order of preference.
1. Using ui-router
When using ui-router instead of native ngRoute you can create an abstract root state and resolve all data in it, before sub-states are activated.
I would recommend to use this approach. ui-router provides a lot of additional features including ability to resolve dependencies hierarchically and is well accepted by the developer community.
Example
module.config(function($urlRouterProvider, stateHelperProvider) {
$urlRouterProvider.otherwise('/404');
stateHelperProvider.setNestedState({
name: 'root',
template: '<ui-view/>',
abstract: true,
resolve: {
user: function(UserService) {
// getCurrentUser() returns promise that will be resolved
// by ui-router before nested states are activated.
return UserService.getCurrentUser();
}
},
children: [{
name: 'index',
url: '/',
templateUrl: '/partials/index'
}, {
name: 'not-found',
url: '/404',
templateUrl: '/partials/404'
}, {
name: 'balance',
url: '/balance',
templateUrl: '/partials/balance',
resolve: {
balance: function(UserService, user) {
// Using data resolved in parent state.
return UserService.getBalanceByAccountId(user.accountId);
}
}
}]
});
});
The stateHelper will help greatly to reduce the code when using abstract root scope approach.
Root scope is defined as abstract so can not be activated directly and it has no URL.
template: '<ui-view/>' is required for nested views to be properly rendered.
2. Making promises in root controller
You can make promises and add them to the $rootScope inside of your root controller, i.e. run() function.
I've created a Plunk to demonstrate the idea:
http://plnkr.co/edit/gpguG5Y2S4KOz1KOKzXe?p=preview
This is a perfectly working solution, however, it bloats the code and makes it harder to use and understand (callback hell). I would recommend it only if the first approach is not working for you.
3. Passing data with the application page
You can include all initialization data directly to the HTML page generated on the server and access it from your application.
Consider this example:
<html>
<body>
<script src="application.js"></script>
<script type="text/javascript">
application.init({
// Pass your data here.
userData: { ... }
});
</script>
</body>
</html>
And you can bootstrap AngularJS application manually in the init() method of your custom application object.
I don't really like this approach, as I do believe that frontend and backend of Web application should be highly separated. Ideally, your frontend should be a static website (e.g. bunch of HTML, CSS and JS that can be delivered via CDN) and your backend should be a strictly an API server without a presentation layer (i.e. it should know nothing about HTML, CSS and such). However, it's a working solution if you can live with tight integration between application components.

How to get routing parameters of simple single page app?

I have a single page AngularJS application which is backed by Ruby on Rails server. The routing definition is only defined in RESTful Rails route.rb config, such as:
http://localhost/pages/:page_id/comments/:comment_id
The client is AngularJS with no routeProvider configuration. So it is just a simple AngularJS controller with view.
When I am in http://localhost/pages/:page_id/comments/:comment_id.html, I want to extract the value of :page_id and :comment_id. What is the best way to do it in AngularJS?
Note: I don't mind if really needed to use routeProvider. But I would only have one templateUrl in that case.
The $routeParams service allows you to retrieve the current set of route parameters.
Requires the ngRoute module to be installed.
The route parameters are a combination of $location's search() and path(). The path parameters are extracted when the $route path is matched.
Note that the $routeParams are only updated after a route change completes successfully. This means that you cannot rely on $routeParams being correct in route resolve functions. Instead you can use $route.current.params to access the new route's parameters.
Example:
// Given:
// URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
// Route: /Chapter/:chapterId/Section/:sectionId
//
// Then
$routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'}
for more infor
EDIT :
If you are using angular-ui-router, you can inject $stateParams
check out this https://github.com/angular-ui/ui-router/wiki/URL-Routing
While routing is indeed a good solution for application-level URL parsing, you may want to use the more low-level $location service, as injected in your own service or controller:
var paramValue = $location.search().myParam;
This simple syntax will work for http://example.com/path?myParam=someValue. However, only if you configured the $locationProvider in the html5 mode before:
$locationProvider.html5Mode(true);
Otherwise have a look at the http://example.com/#!/path?myParam=someValue "Hashbang" syntax which is a bit more complicated, but have the benefit of working on old browsers (non-html5 compatible) as well. [via : Javarome]

What's the AngularJS "way" of handling a CRUD resource

I am interested in moving a lot of my client's "logic" away from Rails routing to AngularJS. I have slight confusion in one topic and that is linking. Now, I do understand there's more than one way to handle this, but what is the common practice in the AngularJS community for handling URLs on handling CRUD for resources. Imagine in the case of an athlete we have a URL such as the following to list all athletes:
http://example.com/athletes
To view an individual athlete:
http://example.com/athletes/1
To edit an individual athlete:
http://example.com/athletes/1/edit
To create a new athlete:
http://example.com/athletes/new
In AngularJS, is it common practice to reroute to similar URLs to create/edit/update? Would you just have one URL handle all of the CRUD type actions in one interface and never change the URL? If you were to change the URL, does that get handled via ng-click and in the click event would you use the $location object to change URLs? I'd love to be able to read up on common practices such as these, but having a difficult time in finding more recent literature on it in an AngularJS context.
** NOTE **
I totally get that you can still use RESTful routes to the backend in order to interact with server-side resources. My question is, what is the style that is recommended to use when updating URLs on the client-side. Are you using AngularJS to do that for each of the CRUD operations?
I would definitely recommend separate URLs for each operation (to enable direct linking). The ones you suggest look fine.
In AngularJS you can use the $route service in combination with the ngView directive to load the appropriate template for each operation and handle the browser location and history mechanics for you.
Step 7 of the AngularJS tutorial gives an example of using Views, Routing and Templates the way I describe here. The following is a simplified version for your example:
Define the routes
In your main application script (e.g. app.js):
angular.module('AthletesApp', []).
config(['$routeProvider', function($routeProvider, $locationProvider) {
// Configure routes
$routeProvider.
when('/athletes', {templateUrl: 'partials/athletes-list.html', controller: AthleteListCtrl}).
when('/athletes/:athleteId', {templateUrl: 'partials/athlete-detail.html', controller: AthleteDetailCtrl}).
when('/athletes/:athleteId/edit', {templateUrl: 'partials/athlete-edit.html', controller: AthleteEditCtrl}).
when('/athletes/:athleteId/new', {templateUrl: 'partials/athlete-new.html', controller: AthleteNewCtrl}).
otherwise({redirectTo: '/athletes'});
// Enable 'HTML5 History API' mode for URLs.
// Note this requires URL Rewriting on the server-side. Leave this
// out to just use hash URLs `/#/athletes/1/edit`
$locationProvider.html5Mode(true);
}]);
We also enable 'HTML Mode' for URLs, see note below.
2. Add an ngView directive to your HTML
In your main index.html you specify where the selected partial template will go in the overall layout:
<!doctype html>
<html ng-app="AthletesApp">
...
<!-- Somewhere within the <body> tag: -->
<div ng-view></div>
...
</html>
3. Create templates and controllers
Then you create the partial view templates and matching controllers for each of the operations. E.g. for the athlete detail view:
partials/athelete-detail.html:
<div>
... Athete detail view here
</div>
athleteDetailCtrl.js:
angular.module('AthletesApp').controller('AtheleteDetailCtrl',
function($scope, $routeParams) {
$scope.athleteId = $routeParams.athleteId;
// Load the athlete (e.g. using $resource) and add it
// to the scope.
}
You get access to the route parameter (defined using :athleteId in the route config) via the $routeParams service.
4. Add links
The final step is to actually have links and buttons in your HTML to get to the different views. Just use standard HTML and specify the URL such as:
Edit
Note: Standard vs Hash URLs
In older browsers that don't support the HTML5 History API your URLs would look more like http://example.com/#/athletes and http://example.com/#/athletes/1.
The $location service (used automatically by $route) can handle this for you, so you get nice clean URLs in modern browsers and fallback to hash URLs in older browsers. You still specify your links as above and $location will handle rewriting them for older clients. The only additional requirement is that you configure URL Rewriting on the server side so that all URLs are rewritten to your app's main index.html. See the AngularJS $location Guide for more details.
The angular way is the restful way:
GET all http://example.com/athletes
GET one http://example.com/athletes/1
POST new http://example.com/athletes
PUT edit http://example.com/athletes/1
DELETE remove http://example.com/athletes/1
Note that $resource also expects a few other things, like resource URLs not ending with a slash, PUT requests returning the updated resource, etc.
If your API doesn't meet these criteria, or you simply need more flexibility, you can build your own $resource-like CRUD service based on the lower-level $http service. One way of doing the latter is explained here
Option 1: $http service
AngularJS provides the $http service that does exactly what you want: Sending AJAX requests to web services and receiving data from them, using JSON (which is perfectly for talking to REST services).
To give an example (taken from the AngularJS documentation and slightly adapted):
$http({ method: 'GET', url: '/foo' }).
success(function (data, status, headers, config) {
// ...
}).
error(function (data, status, headers, config) {
// ...
});
Option 2: $resource service
Please note that there is also another service in AngularJS, the $resource service which provides access to REST services in a more high-level fashion (example again taken from AngularJS documentation):
var Users = $resource('/user/:userId', { userId: '#id' });
var user = Users.get({ userId: 123 }, function () {
user.abc = true;
user.$save();
});
Option 3: Restangular
Moreover, there are also third-party solutions, such as Restangular. See its documentation on how to use it. Basically, it's way more declarative and abstracts more of the details away from you.
In AngularJS you can definitely use RESTful server side data sources, there is build in service called $resource.
Alternatively you can also use restangular which has additional features over $resource.
If you want to have full control you can always use $http service which is low level angular component for interacting with http.
Simply implement something that is RESTful, that is the angularJS way. If you have no idea what RESTful is or, know a little and want to know a lot more, then I would recommend that you read this article.
Basically, REST is what is understood to be, an intuitive implementation of WEB URIs, it also makes use of all HTTP verbs, their correct use actually. REST is an approach, and architecture to building web apps.

Categories

Resources