balderdashy / sails

Realtime MVC Framework for Node.js
https://sailsjs.com
MIT License
22.83k stars 1.95k forks source link

Using sails with AngularJS #205

Closed sheniff closed 11 years ago

sheniff commented 11 years ago

Hi, I'd like to use AngularJS for the frontend in my sails app.

The only problem is that I need to access to the templates (same ones that now are under /assets/js/templates) directly using an URL, commonly

/templates/:name

I know that can be done with express, just by adding

'/templates/:name': routes.templates

in the routes section in app.js

But when I try to do something similar in Sails, in routes.js, it says routes is not defined.

Any idea how can I define an url that way? to directly access to some resources?

mikermcneil commented 11 years ago

Definitely interested in providing access to templates that way. @techpines Any ideas on how we might do this with Asset Rack?

In the mean time, if you drop your templates in the views/templates directory, they'll be automatically injected into a hidden div in your layout file and you can grab them via selectors.

sheniff commented 11 years ago

Yeah, that's right. However, AngularJS relies on XHR requests to retrieve partials directly from the server. I'll use Backbone in the mean time as I can use this kind of approach with it or I'll look for the way to get the templates from the main template with angular.

Thanks!

mikermcneil commented 11 years ago

@techpines might make sense to include a TemplateController in new projects with an index action which just spits out the appropriate template from memory and serves it with the appropriate MIME type.

@sheniff can you give us an example request that angular might send for a template? i.e. does the :name look like tabbar or tabbar.ejs?

sheniff commented 11 years ago

Sure. This is an example of route provider in AngularJS, the common place to locate partials calls:

$routeProvider.when('/welcome', {
  templateUrl : 'templates/welcome.html',
  controller  : WelcomeController
});

It usually comes with the file extension as partials are usually expected to be regular HTML files, just with the frontend (angular) logic in them, but plain HTML in the end.

techpines commented 11 years ago

@garth wrote an AngularTemplates asset for AssetRack:

https://github.com/techpines/asset-rack/tree/master/lib#angulartemplatesasset

Would that work for you?

mikermcneil commented 11 years ago

@techpines Would that work inside of Sails presently?

techpines commented 11 years ago

It can be run as express middleware like this:

var rack = require('asset-rack');
app.use(new rack.AngularTemplatesAsset({
    url: '/templates.js'
    dirname: __dirname + '/templates'
}));

So I believe if my sails knowledge is up to date, that he could run it through a policy.

But if you wanted to add it to the assets being managed by sails in assets.js that would be a different story. Just depends how you wanted to do that piece. Maybe a TemplateController would be a good route to go?

sheniff commented 11 years ago

Hmmm... Maybe using asset stack could do the trick. But I'm definitely more into the TemplateController solution, as it seems cleaner to use and understand :)

I'll try both though and I'll let you know how they were!

Thanks! On Mar 6, 2013 5:00 PM, "Brad Carleton" notifications@github.com wrote:

It can be run as express middleware like this:

var rack = require('asset-rack');app.use(new rack.AngularTemplatesAsset({ url: '/templates.js' dirname: __dirname + '/templates'}));

So I believe if my sails knowledge is up to date, that he could run it through a policy.

But if you wanted to add it to the assets being managed by sails in assets.js that would be a different story. Just depends how you wanted to do that piece. Maybe a TemplateController would be a good route to go?

— Reply to this email directly or view it on GitHubhttps://github.com/balderdashy/sails/issues/205#issuecomment-14536816 .

mikermcneil commented 11 years ago

@sheniff Looking forward to it :+1:

Tidwell commented 11 years ago

I'm using this workaround right now (based on angular-express-seed conventions):

angular.module('app', [])
    .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
        $routeProvider.when('/index', {
          template: $('#template-index').html(),
          controller: IndexCtrl
        });
    });

And dumping everything in /templates as such:

<script id="template-index" type="template/html">
{{angular stuff}}
</script>

works fine, but makes the page a bit heavy

mikermcneil commented 11 years ago

@Tidwell would a TemplatesController be preferable here? Happy to investigate that approach. We could look at a convention where templates can be separated by subdirectories and served conditionally to different areas of your app-- something like:

GET /templates (get all top-level templates in /assets/templates) GET /templates/settings (get all templates in /assets/templates/settings) GET /templates/settings/account (get all templates in /assets/templates/settings/account)

I think we'd still want to provide the option of using the <%- assets.templateLibrary() %> partial, but the REST call to grab templates would be another alternative.

Doing it this way means you could grab only the templates you need, and even control access to them if you're in a high-security situation. Of course, templates are usually safe, but I could see some scenarios where it would matter.

mikermcneil commented 11 years ago

Implementation in pseudo-code:

// api/controllers/TemplateController.js

module.exports = {

  index: function (req,res) {
    var listOfAssetSourcePaths = sails.config.assets.sequence;

    var htmlString = "";
    async.each(listOfAssetSourcePaths, function (path,cb) {
      require('fs').readFile(path,function (err, contents) {
        if (err) return cb(err);
        htmlString += contents;
      });
    }, function (err) {
      if (err) return res.send(err,500);

      res.contentType('text/html');
      res.send(htmlString);
    });
  }
};
mikermcneil commented 11 years ago

We could also use streams in the above example to provide better performance, sending the templates directly from disk straight down chunkwise in the response.

mikermcneil commented 11 years ago

Closing in favor of https://github.com/balderdashy/sails/issues/273

But we definitely need some solid AngularJS examples!

levid commented 11 years ago

I have been playing around with SailsJS for a new project lately and put together this simple CRUD demo using Sails and Mongo as the backend layer and Angular Resource as the REST connector to Sails. It also includes an Angular JS front-end with some Socket.io interaction and some other goodies.

I am really impressed with Sails and it's definitely been fun to play with so far. I thought this might also be useful for others since there aren't a ton of examples out there.

Thanks for the awesome framework!

https://github.com/levid/angular-sails-socketio-mongo-demo

daslicht commented 11 years ago

@levid I have noticed that your https://github.com/levid/angular-sails-socketio-mongo-demo example based on Sails 0.8, Is it the same for 0.9 ? Note, I just started with Sails.

Zolmeister commented 11 years ago

No, many things have changed. Check out the Migration Guide

janpantel commented 11 years ago

For everyone who is interested in using Sails with Angular, i've written a little ngModule for sails socket.io API: https://github.com/kyjan/angular-sails

Best regards :)

jewelsjacobs commented 10 years ago

@kyjan - I can't wait to check out your module! Thanks for your contribution. :+1:

ralyodio commented 10 years ago

i would like to use mongoose/angular

jchiellini commented 10 years ago

I grabbed the module @kyjan posted a few months back. When I include the ngModule into my project, I am getting a reference error telling me "io is not defined". Am I missing the socket.io dependency or maybe not installing it correctly in my project? Thanks for the help.

janpantel commented 10 years ago

@jchiellini you have to include the socket.io.js file before. The ngSails-Module is just a wrapper to make the socket messages available to angular's scope.

connor11528 commented 10 years ago

So what's the convention filestructure for a sails-angular application?

albertosouza commented 10 years ago

I prefer to separate the angular files by feature and end up getting something like: assets/angularjs/[feature]/controllers/ assets/angularjs/[feature]/services/ assets/angularjs/[feature]/directives/ assets/angularjs/[feature]/views/

The idea is separate packages in features to allow export as bower_components

jewelsjacobs commented 10 years ago

+1 for @albertosouza's solution. Thanks!

bfricka commented 10 years ago

**Quick note to clarify the way AngularJS handles templates***

I know nothing about Sails, but this came up in a search and I've been working w/ AngularJS for a couple of years so I thought I could at least clarify that end of things a bit.

You don't want to XHR every partial. All component directives have a template and many of them use templateUrl (it's cleaner), along w/ angular-ui-router $states and of course plain 'ol ngView.

How it actually works

Assuming the definition object for whatever requires a template does not have the template property inlined, we inevitably get to something like this in AngularJS:

var template = $http.get(templateUrl, { cache: $templateCache });

The { cache: $templateCache } is critical because angular is always trying to not make this request if it can help it. What this actually does is makes sure all templates return the same promise (whether cached or not... always a good practice), and that if we do have to make the request, it gets cached for duration of the app life cycle.

Here's a boiled down version what happens in $http:

var template;
// See if cache was provided, and if so
if (config.cache) {
  // In this case: $templateCache.get('my-partial.tpl.html');
  cachedResponse = config.cache.get(config.url);
  if (cachedResponse) {
    // Resolve `$http` promise w/ response from `$templateCache` in this case
    resolvePromise(cachedResponse);
  }
}

But the most important thing here is that you don't really want to load (templates) from XHR unless it makes sense. So how do we get these into the cache so they are available when we need them? Angular has two core features for exactly this purpose:

  1. The script directive. This is dead simple:

    {
     compile: function(element, attr) {
       if (attr.type == 'text/ng-template') {
         var templateUrl = attr.id, text = element[0].text;
         $templateCache.put(templateUrl, text);
       }
     }
    }

    It literally just checks for the "text\ng-template" type and adds the contents to $templateCache if found. Otherwise it leaves it alone.

    Thus...

    <script type="text/ng-template" id="my-partial.tpl.html"><foo>Template</foo></script>
  2. The second is to just access $templateCache directly in a config block before the app runs:

    angular.module('cornflakes').config([
     '$templateCache', function($templateCache) {
       $templateCache.put('foo.tpl.html', '<bar>Some HTML I already have somehow.</bar>');
     };
    ])

This is extremely useful, since it allows you to preload all the templates you want to for each module. How? Simple:

  1. Use a build tool to do all the hard work, such as html2js which does specifically this.
  2. Generate them from partials on the backend and inject them at runtime if the templates are stateful (but still something that makes a lot of sense to render server side).

Sooo...

When working w/ small apps, it's probably best to just let html2js or something similar just build all your templates and include the full payload. Your application script is downloaded, cached and you never have to think about it.

With large apps, you'll end up probably doing one or more of:

  1. Developing a strategy for processing / decorating static templates (e.g. generating different versions of templates for i18n)
  2. Modularizing your app and attaching template caches to modules
  3. Decorating views and serving via XHR (the original point of this ticket)
  4. Leveraging local storage w/ cache poisoning to augment the management of templates

Basically, in a big app, you're going to have to deal w/ this in a much more serious way.

So the only question left is medium complexity apps. Your mileage will vary, obviously, but some combination of XHR views decorated on the server side and pre-built static templates is probably going to be sufficient for most scenarios.

* Clearly I was lying about "quick", sorry

Cheers

Edit: I can't wait to check out Sails. It looks amazing :+1: Edit2: Fixed sleep-deprivation-induced mistakes

gauravdhiman commented 10 years ago

+1 for @albertosouza's solution. Its simple and elegant way to serve Angular HTML templates from SailsJS backend.