AngularJS routing doesn't work in Safari Extension - angularjs

I have a very simple AngularJS app that I'm using to test the creation of a new Safari Extension. When I run it from localhost in a regular Safari page all works, but when I run the same code in the context of an extension routing is not working.
I have spent some time tracing the Angular routing code in both scenarios (which is kind of fun) to try to find differences but have not been able to except for the redirect path (http://localhost/.../helloworld.html#/login vs. safari-extension://com.yourcompany.../helloworld.html#/login). Hitting the extension path directly in a browser window renders the same thing I'm seeing in the extension - basic AngularJS functionality is working (updating "sometext") but routing is not (the route specification doesn't replace the ng-view). No errors are being thrown.
helloworld.html
<!DOCTYPE html>
<html ng-app="obApp">
<head>
<title>Hello World, AngularJS</title>
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="angular-route.js"></script>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript" src="controllers.js"></script>
</head>
<body>
Write some text in textbox:
<input type="text" ng-model="sometext" />
<h1>Hello {{ sometext }}</h1>
<div ng-view></div>
</body>
</html>
app.js
var obApp = angular.module('obApp', ['ngRoute', 'obControllers']);
obApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'login.html',
controller: 'LoginCtrl'
}).
otherwise({
redirectTo: '/login'
});
}]);
controllers.js
var obControllers = angular.module('obControllers', []);
obControllers.controller('LoginCtrl', ['$scope',
function($scope) {
// do stuff
}
]);
login.html
HERE I AM!
UPDATE:
It has something to do with the XHR request to get the routing page - from a regular web page the XHR.send() returns readyState=4 and status=200, from the extension it returns readyState=3 and status=0 (when it moves to readyState=4 status is still 0). It makes me suspect cross-origin XMLHTTPRequest restrictions but it's definitely requesting the page from the exact same domain (safari-extension://...)
HELP! :-)

I'm answering my own question here because it looks like this is how I'm going to need to proceed, but if someone has a better idea please let us know.
I've hacked the AngularJS core library (ugh) to return a status of 200 if there is a response to the request under the condition of the location protocol being "safari-extension" (otherwise return 404). The authors did the same for the condition of the protocol being "file" because it also always returns a 0 status in that case too so I don't feel too bad.
I'll see if I can bring Google's attention to it, either so they can enlighten me as to what I'm missing or else possibly have them add it in a future rev.
UPDATE:
Looking at the history of this code in the GitHub project I see that they implemented the identical fix I was going to propose in a later version of AngularJS. :-)

Related

Why won't Angular-ui-router's <templateUrl> work when all my files are hosted in S3 bucket?

I'm building a sample AngularJS single-page application that uses Angular-UI-Router. But instead of the usual setup where the Angular application is served up through a web-server, I am running a server-side application that just serves up the index.html page in response to HTTP requests. All other resources needed are just pointers to my AWS S3 bucket or somewhere else.
I am doing this because I want to pre-populate the tags on the server-side with og:title from my database so that the Facebook scraper sees the proper values (as I discussed here). This can't be done purely on the user's client. That's why I have to jump through these hoops.
So when you curl my endpoint, it responds with the following HTML from my index page:
<!DOCTYPE html>
<html>
<head>
<base href="/">
<meta property="og:title" content="Saqib's Pre-filled OG Title!"/>
</head>
<body ng-app="pangolinApp">
<table>
<tr>
<td><a ui-sref="splash"><button>Splash</button></a></td>
<td><a ui-sref="blue"><button>Blue</button></a></td>
<td><a ui-sref="green"><button>Green</button></a></td>
<td><a ui-sref="red"><button>Red</button></a></td>
</tr>
</table>
<ui-view></ui-view>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.3/angular-ui-router.min.js"></script>
<script src="http://static.predictagram.com/js/app.js" type="text/javascript"></script>
<script src="http://static.predictagram.com/js/splashController.js" type="text/javascript"></script>
<script src="http://static.predictagram.com/js/blueController.js" type="text/javascript"></script>
<script src="http://static.predictagram.com/js/greenController.js" type="text/javascript"></script>
<script src="http://static.predictagram.com/js/redController.js" type="text/javascript"></script>
</body>
</html>
It renders onto the page as expected. Great:
Here are the ui-router states specified in http://static.predictagram.com/js/app.js:
var app = angular.module("pangolinApp", ["ui.router"]);
app.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('splash', {url:'/', templateUrl : "html/splash.html", controller : "SplashCtrl"})
.state('blue', {url:'/blue', template: '<p>Hello From Blue!!</p> <p>Color = {{color}}</p>', controller : "BlueCtrl"})
.state('green', {url:'/green', templateUrl: 'html/green.html', controller : "GreenCtrl"})
.state('red', {url:'/red', templateUrl: 'html/red.html', controller : "RedCtrl"});
$locationProvider.html5Mode(true);
});
However, the problem is that only the Blue button actually works. The other ones don't. I can see that when you press each button it enters the respective controller (because I have put a console.log line in each one). But only the template specified for the blue state is working.
Clearly this is because the blue state uses template and all the other states uses templateUrl. So I suspect it cannot find the files html/red, html/green. html/splash.html. Maybe they are not being served? I don't know. How can I ensure my AngularJS Single-Page-App can access those html templates??
EDIT:
Here is pic of the structure of my angular application. But it is stored in my S3 bucket. So replace angular_app/ with http://static.predictagram.com/:
When you set templateUrl as: templateUrl: 'html/green.html' you are telling angular, that your templates can be found at that local folder (html).
As you are trying to take the templates from a remote server, you have to get them with absolute routes. For example: templateUrl: 'http://yourserver/templates/green.html'
As it is not meant to work like that, you are going to have CORS errors, you can take a look to this question/answer and it may help you:
How get a template from a remote URL with AngularJS

AngularJS controller reference becomes undefined

I've just started learning AngularJS and built a small yet simple application, which consists of multiple controllers as shown here:
I have a SiteMaster.html page, inside this page I use ng-view as shown here:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="Scripts/angular.min.js"></script>
<script src="Scripts/angular-route.min.js"></script>
<script src="Scripts/app.js"></script> <!-- Consists of Routing -->
<script src="Templates/Params/paramsController.js"></script>
<title>Angular Routing Tutorial</title>
</head>
<body ng-app="mainApp">
<h1>sitemaster Template</h1>
<div ng-view></div>
</body>
</html>
As you can see the last script tag is the controller for the params page, Ideally I don't want this script tag reference on the SiteMaster instead I would like to place it inside the params.HTML page that way it's only loaded when it's required, now I've moved this script tag from the SiteMaster.Html to the params.Html page as shown here:
<script src="paramsController.js"></script>
<div ng-controller="paramsCtrl">
<h1>
Passed a parameter via url
</h1>
</div>
Yet when I run the project I get the following error:
Error: [ng:areq] http://errors.angularjs.org/1.5.2/ng/areq?p0=paramsCtrl&p1=not%20a%20function%2C%20got%20undefined
So my question is, how do I get this paramsController.js reference to work within the params.html page? instead of having it on the SiteMaster.html page?
Please note, when the paramsController.js is placed inside the SiteMaster.Html page it works as expected.
Any help would be appreciated.
Update
After adding ocLazyLoad I have done the following:
Added the script reference to sitemaster.html
Inside my app.js I have done the following:
var app = angular.module('mainApp', ['ngRoute', "oc.lazyLoad"]);
Inside my paramsController.js I have the following:
angular.module("mainApp").controller('paramsCtrl', function ($scope, $routeParams, $ocLazyLoad) {
console.log($routeParams);
var param1 = $routeParams.page_number;
alert(param1);
});
And now I've hit a road block I'm unsure where or how I go about loading this controller via ocLazyLoad ? I following this documentation : https://oclazyload.readme.io/docs
First of all, your path is wrong. Change
<script src="paramsController.js"></script>
to
<script src="Templates/Params/paramsController.js"></script>
Also, include the following in your <head> section:
<base href="/">
And the second thing, I'm not sure, even that will not work as Angular has already been initialized. So you need to use a library like ocLazyLoaded to dynamically load resources.
Also, consider reading this post https://stackoverflow.com/a/17675432/2405040
Update:
In your params.html
<!-- Remove this line
<script src="paramsController.js"></script>
-->
<div ng-controller="paramsCtrl">
<h1>
Passed a parameter via url
</h1>
</div>
And now modify your $routeProvider configuration:
(From the docs https://oclazyload.readme.io/docs/with-your-router)
$routeProvider.
when('/foo', {
templateUrl: 'Templates/Params/params.html',
resolve: {
loadMyCtrl: ['$ocLazyLoad', function($ocLazyLoad) {
// you can lazy load files for an existing module
return $ocLazyLoad.load('Templates/Params/paramsController.js');
}]
}
})

How can I use angularjs ui-router without a server i.e. running it from a folder on my computer?

I want to be able to quickly build an AngularJS application using ui-router without having to set up a server. So that I can send this folder to anyone and the application will still work.
I have tried putting a # infront of the route as adviced here. I have also tried to follow other advice related to the ngroute, but nothing seems to work and since I want to use ui-router rather than ng-route I'm not sure whether the advice is even applicable to my situation.
I have also tried to create a manifest file to store the files offline according to this page.
In firefox it kind of works but for some reason it double up what is on the index.html page. But in chrome which is my preferred browser it doesn't work at all. I get an error about a failed ajax call; why don't the manifest file download all pages straight away?
How should I accomplish building an AngularJS app with ui-router without needing a server?
Here is simple snippet of my code:
index.html
<!DOCTYPE html>
<html lang="en" ng-app="app" manifest="mycache.manifest">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<a ui-sref="home.messages">Messages</a>
<div ui-view></div>
<script type="text/javascript" src="bower_components/angular/angular.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="js/routes.js"></script>
<script src="js/main.js"></script>
</body>
</html>
messages.html
<button ng-click="contextCtrl.getAllMessages()">Get Messages</button>
<div ng-repeat="message in contextCtrl.messages">
{{message}}
</div>
routes.js
'use strict';
angular.module('app', ['ui.router']);
//Setting up route
angular.module('app').config(['$stateProvider',
function($stateProvider) {
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'index.html',
controller: 'ContextCtrl',
controllerAs:'contextCtrl'
})
.state('home.messages', {
url: '/messages',
templateUrl: 'messages.html',
controller: 'ContextCtrl',
controllerAs:'contextCtrl'
});
}
]);
main.js
'use strict';
/*jslint latedef:false*/
//Controller declaration
angular.module('app').controller('ContextCtrl', ContextCtrl);
function ContextCtrl() {
var vm = this;
vm.messages = [];
vm.getAllMessages = getAllMessages;
function getAllMessages(){
vm.messages = ['m1', 'm2', 'm3', 'm4'];
};
}
mycache.manifest
CACHE MANIFEST
js/main.js
js/routes.js
messages.html
bower_components/angular-ui-router/release/angular-ui-router.js
bower_components/angular/angular.js

Cannot manually navigate to urls

Whenever I manually enter a url into my browser to a site built with Angular, I get:
'HTTP 404
The resource cannot be found
Requested URL: /register'
The only navigable url is http://localhost:XXXX/index.html, from there I have to navigate around the site with anchor tags.
My config file for Angular looks like this:
app.config(function ($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);
//.hashPrefix('!')
$routeProvider
.when('/', {
templateUrl: '/Client/AngularViews/home.html'
})
.when('/register', {
templateUrl: '/Client/AngularViews/register.html',
controller: 'registerController'
})
.when('/post-register', {
templateUrl: '/Client/AngularViews/postRegister.html',
controller: 'registerController'
})
.otherwise({ templateUrl: 'Client/AngularViews/home.html' });
});
Index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" data-ng-app="app">
<head>
<script type="text/javascript" src="Scripts/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="Scripts/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.3/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.js"></script>
<link href="Content/bootstrap.min.css" rel="stylesheet" />
<script type="text/javascript" src="Scripts/mainController.js"></script>
<base href="/" />
</head>
<body>
<div class="container-fluid">
<div ng-view></div>
</div>
Can someone explain to me what I'm doing wrong? I want to be able to manually enter urls into the address bar and navigate to anything other than index.html
According to documentation:
Server side
Using this mode requires URL rewriting on server side,
basically you have to rewrite all your links to entry point of your
application (e.g. index.html). Requiring a tag is also
important for this case, as it allows Angular to differentiate between
the part of the url that is the application base and the path that
should be handeled by the application.
https://docs.angularjs.org/guide/$location
So you need some additional support from server-side: configure your http server to reply with index.html for all url you have in your apps.
I use ASP.Net MVC for server side URL rewriting using controllers. Probably not its intended use but works well

AngularJS writing an app with no server

I'm trying to write an AngularJS client side only app.
I thought I might be able to load it from chrome by typing in the address bar:
file:///C:/path/to/project//index.html
I also tried to invoke chrome with the flag --allow-file-access-from-files
Unfortunatly nothing happened - just the busy sign on the tab name is working.
Why does is not loading my app?
I'm using the following code:
index.html:
<!doctype html>
<html ng-app="app">
<head>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
<script src="app.js"></script>
<title></title>
</head>
<body style="background-color: white;">
<h1>Index</h1>
<a id="link" href="/login">Go to login</a>
<ng-view></ng-view>
</body>
</html>
app.js:
angular.module('app', ['ngRoute']).
config(function($routeProvider) {
$routeProvider.
when('/', {controller: HomeCtrl, templateUrl: 'app.html'}).
when('/login', {controller: LoginCtrl, templateUrl: 'login.html', resolve: function() {}}).
otherwise({redirectTo:'/'});
});
function HomeCtrl($scope) {
$scope.numbers = [1,2,3,4,5];
}
function LoginCtrl($scope) {
}
app.html:
<div ng-controller="HomeCtrl">
<ul ng-repeat="number in numbers" >
<li>{{number}}</li>
</ul>
</div>
Edit:
2 possible solutions:
Close all chrome instances and run from command line: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files --allow-file-access
Run Firefox which does not have this restriction (Like #GeekBoy mentioned)
As far as I know google chrome does not allow javascripts to be run from file system. But I did a quick google search and found this. Might be useful
Link
On the flipside you can use firefox. Firefox doesn't have such restrictions as far as I know
Try changing the following lines:
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
to
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-route.js"></script>
I think since you're using a file-based way to get at index.html, it's assuming the // also points to a local system resource. By specifically indicating http://, it will look at the actual locations.
I know that this is an old question but for anyone that might still be interested, here is a small project that demonstrates how to write an angularjs client-side app. It is a complete AngularJS 1.63 single page application with routing that does not need a web server: https://github.com/jlinoff/aspa-nows.
There were two key challenges to getting it working: getting the the page href references right because of the newly introduced default hash prefix: ! (see aa077e8 for details), and embedding the template HTML code into index.html as ng-template scripts to avoid CORS errors. Once those fixes were made, it worked as expected.
The project README.md explains what needed to be done in detail and the full source code is available.

Resources