I'm using Grails 2.4.4 and AngularJS 1.3.15 to create a simple application. To make templates work with Grails I use the AngularJS Template Asset-Pipeline Plugin 2.0.7.
My first prototype was not using any routing logic from ngRoute. Now I want to extract parts of the growing application and put them in separate templates. This almost worked but I have a problem with images. Previously I used constructs like this to load images as assets:
<img src="${assetPath(src: 'image.png')}">
That did work because the whole app was a .gsp page. Extracting the code into templates this processing does not happen anymore and the images cannot be loaded anymore.
My current workaround is:
<img src="../assets/image.png">
Obviously this technique has some drawbacks. Hardcoding the asset location with a relative path can be very fragile. Did I miss some nice feature or command that could help me here merging AngularJS with Asset Pipeline?
I 'got around' this issue by adding an 'AssetService' to my Angularjs project.
angular.module('myApp.assets')
.service('AssetService', [ function () {
this.getAssetDir = function() {
return window.grailsSupport.assetsRoot;
};
this.getRootDir = function() {
return window.grailsSupport.root;
};
}]);
This requires some code in your Grails Layout File....
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title><g:layoutTitle default="MyApp"/></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="${assetPath(src: 'favicon.ico')}" type="image/x-icon">
<link rel="apple-touch-icon" href="${assetPath(src: 'apple-touch-icon.png')}">
<link rel="apple-touch-icon" sizes="114x114" href="${assetPath(src: 'apple-touch-icon-retina.png')}">
<!-- add this to your layout -->
<g:javascript>
window.grailsSupport = {
assetsRoot : '${ raw(asset.assetPath(src: '')) }',
root : '${createLink(uri: '/')}'
};
</g:javascript>
<asset:stylesheet src="application.css"/>
<asset:javascript src="application.js"/>
<g:layoutHead/>
</head>
I got the window.grailsSupport code in an answer to another stack overflow question.
You can now use the AssetService to prefix your URLs in your controllers...
angular.module('MyApp.profile')
.controller('MyCtrl', [ 'AssetService', '$http', function(AssetService, $http) {
var self = this;
self.pathToAnonProfile = AssetService.getAssetDir() + "ppt_Anonymous.jpg";
$http.get(AssetService.getRootDir() + 'api/v1/users').then(function(response) {
....
}, function(errResponse) {
....
});
}]);
It could most likely be improved and you still need to mix gsp and pure Angular templates but it keeps the Asset path in a single service which can easily be modified.
It looks like the tag and the corresponding assetPath() are broken in this respect. They do not appear to respect the serverURL setting in config.groovy or calculate the base path properly when rendering in a template.
I was able to work around the issue in a less fragile way using the createLink() tag to generate my path. It fixes the name of the asset controller, but I think it unlikely that they will change that.
In my case I was resolving some flag images within an AJAX callback.
<img src='${createLink(controller: "assets", action: "flags/16/US.png")}' />
Hopefully this works for you as well.
Related
I have an angularjs (version 5) app running in laravels public folder.
angularjs should serve the ui aka frontend. whilst laravels "only" route is the backend api access to the data(base) serving the ui at /api/....
How do I marry both?
My angularjs app resides in /public/ui/ so I currently just have a route in laravel like so:
Route::get('/', function () {
return Redirect::to('/ui/dist');
});
This works partially. It works from within the angularjs app like expected. But when I call anuglarjs routes they will fail to display because of course they don't exist in laravel.
Going with the angularjs tutorial for example:
If I call /ui/dist/heroes it will display a 404 instead of the angularjs app.
Note: Laravels public folder is symlinked to the webroot.
Edit:
I redirect to /ui/dist as I use angulars builder to build its files and these are reflected in the index.html also generated with the builder.
The index.html looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ui</title>
<base href="./">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="styles.5cf98968a6d57e778c48.bundle.css" rel="stylesheet"/><!-- the hash changes on every build -->
</head>
<body>
<app-root></app-root>
<script type="text/javascript" src="inline.2379cc013d19d70a8003.bundle.js"></script> <!-- the hash changes on every build -->
<script type="text/javascript" src="polyfills.ad37cd45a71cb38eee76.bundle.js"></script> <!-- the hash changes on every build -->
<script type="text/javascript" src="main.99c0191843a51dda5d54.bundle.js"></script> <!-- the hash changes on every build -->
</body>
</html>
Here is how you can integrate laravel and angular routing.
In your routes.php, add this route:
Route::get('{slug}', function () {
return view('index');
}); // this will ensure all routes will serve index.php file
Route::get('/', function () {
return view('index');
});
In your index.php of laravel view, add all necessary angular js config and controller like normally you do with angular projects.
Note: for each $http request urls you have to define it in laravel route file.
Now when any url is hit, it will serve the index.php file and then angular routing will serve the correct file.
In order to make the routing systems of Angular (7+) and Laravel (5.8) coexist effectively is to redirect all the 404 errors that Laravel intercepts to the Angular router.
You have to edit the render function in app/Exceptions/Handler.php:
public function render($request, Exception $e)
{
// handle Angular routes
if ($e instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
$url = parse_url($request->url());
$angular_url = $url['scheme'] . '://' . $url['host'] . '/#' . $url['path'];
return response()->redirectTo($angular_url);
}
return parent::render($request, $e);
}
Now, let's say that you want to show a login form using angular navigating to /auth/login. When you hit that route Laravel will kick in, and not recognising the route will then pass the control to the Exception Handler. What we're doing in the render() function is simply tell Laravel to rewrite the URL and redirect to /#/auth/login.
Then, you have to enable to enable HashLocationStrategy in your Angular application, like so:
RouterModule.forRoot(routes, {useHash: true})
I have a React application with server side rendering. I now have to implement Facebook/Google+ share dialog with og:title and og:image being set dynamically to values returned from the API.
I'm using react-helmet to set my default meta tags, however I have troubles making it work dynamically.
I tried using redux-async-connect to prefetch the result, which resulted in meta tags being rendered (I can see them when I look at the source code), however both Facebook and Google+ ignore them.
Do any of you have experience with making this work?
Essentially, in your public/index.html file you want to replace the metadata with an identifiable string:
<!-- in public/index.html -->
<title>$OG_TITLE</title>
<meta name="description" content="$OG_DESCRIPTION" />
<meta property="og:title" content="$OG_TITLE" />
<meta property="og:description" content="$OG_DESCRIPTION" />
<meta property="og:image" content="$OG_IMAGE" />
And then on the server, you want to replace these strings with the dynamically generated information. Here is an example route with Node and Express:
app.get('/about', function(request, response) {
console.log('About page visited!');
const filePath = path.resolve(__dirname, './build', 'index.html')
fs.readFile(filePath, 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
data = data.replace(/\$OG_TITLE/g, 'About Page');
data = data.replace(/\$OG_DESCRIPTION/g, "About page description");
result = data.replace(/\$OG_IMAGE/g, 'https://i.imgur.com/V7irMl8.png');
response.send(result);
});
});
Taken from this tutorial here: https://www.kapwing.com/blog/how-to-add-dynamic-meta-tags-server-side-with-create-react-app/
You can render the app inside document like this:
render(<App/>, document)
while App containing all the html you need.
I tried so hard to get this work but all my tries failed. I'm trying to learn Angular routing, I started very simple: created 4 files in the same folder: index.html, page1.html, page2.html and page3.html.
this is the index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>routing</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<script src="D:\Developer Library\MyAngular\angular.min.js"></script>
<script src="D:\Developer Library\MyAngular\Scripts\angular-route.js"></script>
<body ng-app="myApp">
one
two
three
<div ng-view></div>
<script>
var app = angular.module('myApp', ['ngRoute']);
app.config(function ($routeProvider) {
$routeProvider
.when('/page1', { templateUrl: 'D:\Developer Library\dom\AngularRouting\page1.html' }).
when('/page2', { template: '<h1>page2.html</h1>' })//the template is working fine unlike templateUrl
.when('/page3', { template: '<h1>page3.html</h1>' });
//page1.html, page2.html, page3.html are just files holding headers with some text.-->
})
</script>
</body>
</html>
I got these errors in the console window:
XMLHttpRequest cannot load
file:///D:/Developer%20LibrarydomAngularRoutingpage1.html. Cross
origin requests are only supported for protocol schemes: http, data,
chrome, chrome-extension, https, chrome-extension-resource.
and:
Error: [$compile:tpload]
http://errors.angularjs.org/1.5.8/$compile/tpload?p0=D%3ADeveloper%20LibrarydomAngularRoutingpage1.html&p1=-1&p2=
at angular.min.js:6
at angular.min.js:156
at angular.min.js:131
at m.$eval (angular.min.js:145)
at m.$digest (angular.min.js:142)
at m.$apply (angular.min.js:146)
at HTMLBodyElement. (angular.min.js:115)
at Sf (angular.min.js:37)
at HTMLBodyElement.d (angular.min.js:37)
I made every possible change to get it work: I changed the href value of the anchor elements to /#/page1, #/page1, /page, page , I also changed the templateUrl value to similar values (my last try was the full path of the file!)
I'm actually confused between the href value and the first parameter of when method and the templateUrl, so I have some questions that I think will help me understand how routing work in angular:
What each of them refers to: are the href and the first argument of when method the same?
Can I assign the href attribute any value, and refer to it in the when argument?
Is templareUrl value related to the location of the current file(index.html),
What does the hash symbol # mean and why it's important?
Angular is loading templates via AJAX, and AJAX can not access local file system.
You must run you app on a server (you can use local server) for templateUrl to work.
I've an app that retrieve server data using ajax. I've tested in localhost, the loader work fine, but when I install my extension and click on the browser action popup, the loader won't show. The little popup delayed for 2 second and shows the result.
popup.html
<div class="cssLoader" ng-show="loader">Fetching...</div>
js
app.controller('MainControl', function($scope, $http){
$scope.loader = true;
$http({
url: "http://www.corsproxy.com/mydomain.net/items.php",
method: "GET",
}).success(function(data) {
$scope.data = data;
$scope.loader = false;
});
});
Without seeing more of your code it is difficult to know for sure. Nonetheless, my suspicion (based upon the fact that your code works outside of the Chrome extension environment but not inside that environment) is that since you're operating in a Chrome Extension environment, you'll need to include the ng-csp directive (see Chrome documentation or Angular documentation).
I developed an Angular app inside a chrome extension and I needed to use ng-csp in order for Angular to load and fully function properly.
Essentially, Chrome extensions (and even more apps) place a number of restrictive security permissions on the browser environment and ng-csp tells Angular to operate in a way that is more consistent with a strict CSP.
I have included an example below that shows loading the entire Angular application properly:
<!DOCTYPE html>
<html ng-app="myApp" ng-csp>
<head>
<meta charset="utf-8">
<title>My Extension</title>
<link href="css/index.css" rel="stylesheet">
<!-- Include in the next line your Angular library code -->
<script type="text/javascript" src="js/angular-lib.js"></script>
<!-- Include in the next line your custom Angular code such as the $http to load the loader -->
<script type="text/javascript" src="js/myapp.js"></script>
</head>
<body>
<!-- Place your HTML code for the 'Fetching' anywhere here in the body -->
</body>
</html>
According to the docs, CSP "is necessary when developing things like Google Chrome Extensions" (more info can be found on the linked page).
Furthermore, besides defining ng-csp on your root element, there is on more crucial point (which affects ngShow/ngHide):
CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically includes some CSS rules (e.g. ngCloak). To make those directives work in CSP mode, include the angular-csp.css manually.
I found this to be necessary only if the angular.js script is defined inside the app's context.
In any case, here is the source code of minimal demo extension that seems to work fine for me:
Structure:
extension-root-dir/
|_____manifest.json
|_____popup.html
|_____popup.js
|_____angular.js
|_____angular-csp.css
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"browser_action": {
"default_title": "Test Extension",
//"default_icon": {
// "19": "img/icon19.png",
// "38": "img/icon38.png"
//},
"default_popup": "popup.html"
}
}
popup.html:
<!DOCTYPE html>
<html>
<head>
<title>Test Extension</title>
<link rel="stylesheet" href="angular-csp.css" />
</head>
<body ng-csp ng-app="myApp" ng-controller="mainCtrl">
<div ng-show="loader">Fetching...</div>
<div ng-hide="loader">{{status}}</div>
<script src="angular.js"></script>
<script src="popup.js"></script>
</body>
</html>
popup.js:
var app = angular.module('myApp', []);
app.controller('mainCtrl', function ($http, $scope) {
$scope.loader = true;
$http({
url: "http://www.corsproxy.com/mydomain.net/items.php",
method: "GET"
}).finally(function () {
$scope.loader = false;
}).then(function (response) {
$scope.data = response.data;
$scope.status = 'Success !';
}, function (response) {
$scope.status = 'ERROR !';
});
});
(BTW, I am using AngularJS v1.2.16.)
I'm struggling with the following:
My gulpfile.js compiles all .less, minifies it and concattenates all CSS into ./dist/all.min.css
Is there a way I can rewrite the HTML file, remove all style tags and only put one style tag into it loading the minified CSS?
The best way to handle this is to use one of the HTML injectors from the get-go. I'm using gulp-inject to some success so far.
Add gulp-inject to your project:
npm i --save-dev gulp-inject
Assuming that you have a folder layout similar to this:
build/
src/
index.html
less/
main.less
js/
app.js
Your HTML should include this where you want the CSS or JS files to be injected, either the head for both, or head for the CSS and just before body for your JS files:
<!-- inject:css -->
<!-- any *.css files among your sources will go here as: <link rel="stylesheet" href="FILE"> -->
<!-- endinject -->
<!-- inject:js -->
<!-- any *.js files among your sources will go here as: <script src="FILE"></script> -->
<!-- endinject -->
Then your gulpfile looks something like this:
gulp.task('build-styles', function() {
// the return is important!
return gulp.src('src/less/main.less')
.pipe(less())
.pipe(gulp.dest('build'));
});
gulp.task('build-js', function() {
// the return is important if you want proper dependencies!
return gulp.src('src/js/**/*.js')
// lint, process, whatever
.pipe(gulp.dest('build'));
});
gulp.task('build-html', function() {
// We src all files under build
return gulp.src('build/**/*.*')
// and inject them into the HTML
.pipe(inject('src/index.html', {
addRootSlash: false, // ensures proper relative paths
ignorePath: '/build/' // ensures proper relative paths
}))
.pipe(gulp.dest('build'));
});
gulp.task('build', ['build-styles', 'build-js'], function(cb) {
gulp.run('build-html', cb);
});
gulp.task('default', ['build'], function() {
gulp.watch('src/**/*.less', function() {
gulp.run('build-styles');
});
gulp.watch(['build/**/*.*','!build/index.html', 'src/index.html'], function() {
gulp.run('build-html');
});
});
This is just a rough idea, and you can do a lot more using gulp-watch for incremental builds, but the key here is that we watch the build directory to choose when to rebuild the HTML file, and watch the src directory for everything else.
NOTE:
Since this is getting a lot of upvotes, there are a couple other plugins that do reference replacement beside gulp-inject. You may want to look at them and see if one of them is a better fit for you, especially if you are not using gulp-rev:
gulp-usemin
gulp-useref
There are also two CDN libraries that do a similar thing, but for CDN resources
gulp-google-cdn
gulp-cdnizer (full disclosure: I wrote this one)
You want to rewrite it during a build? Why not to replace all the CSS links with a single link to all.min.css in your source code? Anyways, you can use gulp-replace plug-in to search and replace a string in your files during a build. Here is yest another sample project to look at:
Web App Boilerplate - HTML5 Boilerplate front-end web application template extended with LESS style sheets and Gulp.js build system.
See also gulp-smoosher. Example:
index.html
<html>
<head>
<!-- smoosh -->
<link rel='stylesheet' href='styles.css'>
<!-- endsmoosh -->
</head>
styles.css
body {
background: red;
}
Gulpfile.js
gulp.task('default', function () {
gulp.src('index.html')
.pipe(smoosher())
.pipe(gulp.dest('dist'));
});
dist/index.html
<html>
<head>
<style>body {
background: red;
}</style>
</head>