Angular.js - Prevent Direct Access to Partial Views - angularjs

I want the users to not be able to view the partials directly when accessing their url in the browser. For example if my partial view is located in:
/partials/myPartial.html
I can still directly access it and see the odd markup. Is there a way to prevent it and make the partial views only available through angular? Thanks.

I put a <script> at the top of every view partial that checks if the variable containing the main app Module exists, if not redirect to index.html
If in app.js the appModule is defined in this way:
var appModule = angular.module('myApp',["ngRoute","ngResource"]);`
At the top of the partial insert:
<script type="text/javascript">
if(typeof appModule === 'undefined'){
document.location.href="index.html";
}
</script>

Well, not really.
At the end of a day a browser needs to be able to access a partial to download it. If a browser can do this your users will be able to do so as well. You might eventually make it a bit harder for them to hit partials directly (for example to by configuring your server so it only responds to request with a certain header and configure $http to send this header on each XHR request).
The other possibility is to pre-load partials up-front as described here and not expose them via HTTP at all (actually this is a good practice for production deployments anyway).
Otherwise it is hard to propose a meaningful solution without knowing what is the exact problem (functionally speaking) that you are trying to solve.

Related

Is it possible to intercept html, css and js files used in angularjs?

I have already created an http interceptor and based on my observation, it only applies to all that uses $http service. What if I want to intercept even the css, html and js files used in the template? Is it possible?
This is the code of the interceptor I mentioned above:
app.factory('redirectInterceptor', function($q,$location,$window){
return {
'response':function(response){
if (typeof response.data === 'string' && response.data.indexOf("My Login Page")>-1) {
$window.location.href = "/login.html";
return $q.reject(response);
}else{
return response;
}
}
}
});
app.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('redirectInterceptor');
}]);
I want to be able to do an interceptor for css, html and js (for template) just like the one above.
As you said,
it only applies to all that uses $http service
So, if you want to intercept the requests for html,css and js files. It is best done on the server, rather than the client.
you can intercept html files
var requestInterceptor = function (config) {
if (config.url.indexOf(".html") != -1) {
//custom logic
}
}
though above does not work with .js or .css because they are loading using tag.
.html files are saved in template-cache and fires $http requests...
though you should go ahead and test what all being requested by doing console.log(config.url) in the interceptor to be sure..
This is a classic X-Y problem. You're thinking of a solution to your problem but it's the wrong way to go about it.
This line, from your comment, explains the actual problem:
but the js files that I'm referring to are the business logic of the project. So making it public is not an option.
So, your problem is you don't want to expose business logic to people who haven't/can't login.
There are several solutions to this. But it all boils down to one thing: separate your business logic from common js code used for UI etc.
Once you've separated your js into two parts (not necessarily two files but it must be at least two files, it can be more) you can expose your common js file(s) as public. Then you can decide how to handle loading your business logic:
Letting it Fail
This is probably the simplest strategy. Just do nothing and it will fail to load. Presumably you only need your business logic on logged-in pages so not having it in the landing or login pages should not be an issue.
Dynamicly Include it in the Template
Just omit the script tag that loads the business logic js if user is not logged in.
Load Business Logic Dynamically in JS
Either load it using AJAX then eval it or use require.js or use jQuery getScript() or something similar to load the business logic only when the user is logged in.
If you're creative you can probably come up with a couple more ways to handle loading the business logic JS. Whatever it is, the key is you need to make the common js and css files public.

Angular loading specific 3rd party scripts in partial views ui-router

In my app I have ui-router set up, and in one particular state i.e. "MAP" I want to load a 3rd party Javascript file.
Right now I have it as part of my Index.html at the bottom of the page. But i thought it would be better practice to only load this in view which needs it.
What is the best way to include this script in only specific partial views ?
Turns out you can simply include the <script> tag in your partial. Works perfectly fine for me.
Also, if you have a window.onload function, you can replace it with a self invoking function like
(function(param){
alert("partial running");
bla bla....
})();
so it will run when the partial is pulled up.

how do I get ng-include directive to work with a Content Delivery Network?

Is it possible to get ng-include directive to work with a CDN?
In this case:
<div ng-include='http://cdn.somewebsite.com/templates/home.tpl.html'></div>
The cdn sub-domain of somewebsite.com is mapped through DNS/CName to a content delivery network.
However when loading the main site somewebsite.com, it does not render the template. I guess internally it is treating this as a cross-domain call.
Is there any work arounds to get Angular templates be hosted on third party CDN's and work with the local domain?
Yes, you are right - the problem is with cross-domain call.
Official docs say:
By default, the template URL is restricted to the same domain and protocol as the application document. This is done by calling $sce.getTrustedResourceUrl on it. To load templates from other domains or protocols you may either whitelist them or wrap them as trusted values. Refer to Angular's Strict Contextual Escaping.
and
Please note: The browser's Same Origin Policy and Cross-Origin Resource Sharing (CORS) policy apply in addition to this and may further restrict whether the template is successfully loaded. This means that without the right CORS policy, loading templates from a different domain won't work on all browsers.
Example:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow same origin resource loads.
'self',
// Allow loading from outer templates domain.
'http://cdn.somewebsite.com/templates/**'
]);
});
Helpful links:
ngInclude
$sce
So your problem may be solved by appropriate angular settings, but not for all browsers.
Perhaps you can alter this code a bit. I have my client's intranet in a datatbase and that's connected to a remote API server. I use an angular app to pull the HTML, but it is part of a JSON object, so obviously that's pretty specicific to my own needs.
I happen to have full control over the CORS attributes of my server, so I can do this. If you try it google or some other site... you're stuck with an iframe. (I have not idea what that's allowed by JS is so strict!)
So, here's what I do to get my remote HTML data.
1:
In my controller I add this:
<div ng-bind-html="content"></div>
then in the code, I add this
$http.get(url)
.then(function (data) {
$scope.content = $sce.trustAsHtml(data.data.PageData);
});
That's it. Just don't forget that the site in the URL has to be allowing you to get the data via your remote system.
NOW: Just for fun, I used to use IFRAMEs to bring data from other sites before CORS was even invented. It was a big hack. Before AJAX, I'd make a tiny form on one page with all the form values empty. On another page, I'd have an iframe for it and just fill those input boxes with javascript and post them back with javascript, keeping the main page without a reload.
If you need more control of your data, you could simply have a hidden iframe, rip the HTML you want from it, put it in a variable and drop it whereever you want on your page.
There's always some half-a$$ed way to get things done. :)

Different base url for page routing and templates/partials/RESTful API

My Angular app is splitted into several subapps. I'm working with laravel which is also responsible for the routing between the subapps.
So there are the following urls handled by laravel:
ModuleX: /moduleX
ModuleY: /moduleY
ModuleZ, Item n: /moduleZ/item-n (unlimited amount of items)
And finally on top of these there are my 3 Angular subapps.
So for example: /moduleZ/item-1/#/hello/world
Additionally the templates, partials and a RESTful API are served by laravel under the following urls:
/templates
/partials
/api
If I set the base url with the html tag <base href="http://www.example.com/app/"> I can use relative urls for templates, partials and the api but not for the routing. I'll allways have to add the module url part like that moduleX/#/hello/world.
If I set the base url like for example <base href="http://www.example.com/app/moduleX/"> I can write all links like #/hello/world but the templates, partials and api requests aren't working anymore.
The whole app also sits in a subfolder so I can't just use for example /templates.
So basically my problem or the question now is, what's the best way for handling the different base urls? I don't really like it to prepend the module name to every link.
Look it's might be a particular solution, but may be you should use filters for urls?
For example:
.filter('routeFilter', [function () {
return function (route) {
if (some condition mean this direct html link){
return 'MODULE-X/' + route;
}else{
return route
}
};
}])
I would suggest you let (sub-)apps know their real base not the parent's one, but you make the server responsible for climbing the path hierarchy to find higher level shared resources for missing local ones. This also makes it very easy to prototype new sub-apps, test changes to common pieces independently first, etc.
In a webserver like Apache, this would look like a rewrite rule (conditional on not finding a file) it just substitutes the same file in the parent hierarchy.
In laravel (according to the router docs) it looks like you can add optional 'directories' to your current rules, i.e.:
Route::get('/.../app/templates/{name}', function($name)
...
becomes:
Route::get('/.../app/{module?}/{item?}/templates/{name}', function($name, $module, $item)
...
You could then use $module and $item if you need to test a resource change with specific sub-app/items.
The drawback in making the server responsible for handling inheritance is the independent client fetch/cache of identical resources with different paths. But you can at least choose between large file inefficiency or access latency by using either rewrites or redirects. You can also always hardcode significant paths in production clients later and still benefit from having a hierarchy for testing and graceful handling of occasional errors in client side links.
You can do it in a simpler way if you use combination of service and directive.
You can implement a directive similar to ngHref which when given a link will transform it and append the given link back. It will be injected with a service which will give it the base url or relative url or anything module specific.
This service which is injected in directive will be configured using serviceProvider in app.config block of each sub-app. Since angular injector have only one instance of each service I think you will need more than one injector or one injector per sub-app. Its unclear whether they share injector among them in your app.
You can configure each service according to module to return different base paths. Which will be appended by directive to each link every time.
With this you can use <base href="http://www.example.com/app/"> and that should solve your problem.
I haven't written any code but I can help if you need it.

How to provide configuration to AngularJS module?

Currently I am combining a traditional application with some dynamic parts written using AngularJS. I would like to provide some configuration from my server to my module. In this case I would like to configure the application 'base' URL. All templates can be found at a specific location and this location is determined by the server.
So I tried something like this:
angularForm.config(
function($routeProvider, TemplateLocator) {
$routeProvider.when('/', {
controller : TestController,
templateUrl : TemplateLocator.getTemplate('FormOuterContainer')
});
});
At the server:
<script type="text/javascript">
angular.module('BoekingsModule.config', []).provider('TemplateLocator', function() {
this.$get = function() {
return // something
}
this.getTemplate = function(name) { return 'location...'; }
});
</script>
However, I am not sure this is the right way.
So in short: how can I provide some (external) configuration to a module without having to change the module itself?
There are several things you can do
Render the locations in the js on the server.
Pro: less requests -> better performance (usually)
Cons:
combining server side rendering with an SPA makes the system more complex, leading to higher maintenance costs.
it is harder to properly compress your js files if you want to mix in server side rendering, embedding js in html is also ugly.
Extract the template location to a service.
From this service you could use a $resource to fetch the data from the server in json format. This keeps things simpler, but it could also increase your web server's load.
Wow it has been a long time since. I completely forgot about this post.
So, in the end I decided it was better to use another approach. Like iwein said, combining SPA with server side rendering makes the application a lot more complex. Therefore I extracted the SPA part into a separate application which in turn is referenced from (in my case) the JSP.
When my module needs some additional configuration, it gets it using a REST API. This way the SPA part of the application is completely decoupled from the server-side rendered part.

Resources