Using sails with AngularJS #205

Closed
sheniff opened this Issue Mar 6, 2013 · 27 comments

Comments

@sheniff

sheniff commented Mar 6, 2013

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

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 6, 2013

Member

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.

Member

mikermcneil commented Mar 6, 2013

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

This comment has been minimized.

Show comment
Hide comment
@sheniff

sheniff Mar 6, 2013

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!

sheniff commented Mar 6, 2013

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

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 6, 2013

Member

@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?

Member

mikermcneil commented Mar 6, 2013

@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

This comment has been minimized.

Show comment
Hide comment
@sheniff

sheniff Mar 6, 2013

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.

sheniff commented Mar 6, 2013

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

This comment has been minimized.

Show comment
Hide comment
@techpines

techpines Mar 7, 2013

Member

@garth wrote an AngularTemplates asset for AssetRack:

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

Would that work for you?

Member

techpines commented Mar 7, 2013

@garth wrote an AngularTemplates asset for AssetRack:

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

Would that work for you?

@mikermcneil

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 7, 2013

Member

@techpines Would that work inside of Sails presently?

Member

mikermcneil commented Mar 7, 2013

@techpines Would that work inside of Sails presently?

@techpines

This comment has been minimized.

Show comment
Hide comment
@techpines

techpines Mar 7, 2013

Member

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?

Member

techpines commented Mar 7, 2013

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

This comment has been minimized.

Show comment
Hide comment
@sheniff

sheniff Mar 7, 2013

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
.

sheniff commented Mar 7, 2013

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

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 7, 2013

Member

@sheniff Looking forward to it 👍

Member

mikermcneil commented Mar 7, 2013

@sheniff Looking forward to it 👍

@Tidwell

This comment has been minimized.

Show comment
Hide comment
@Tidwell

Tidwell Mar 8, 2013

Contributor

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

Contributor

Tidwell commented Mar 8, 2013

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

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 15, 2013

Member

@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.

Member

mikermcneil commented Mar 15, 2013

@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

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 30, 2013

Member

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);
    });
  }
};
Member

mikermcneil commented Mar 30, 2013

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

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 30, 2013

Member

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.

Member

mikermcneil commented Mar 30, 2013

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

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Mar 30, 2013

Member

Closing in favor of #273

But we definitely need some solid AngularJS examples!

Member

mikermcneil commented Mar 30, 2013

Closing in favor of #273

But we definitely need some solid AngularJS examples!

@levid

This comment has been minimized.

Show comment
Hide comment
@levid

levid May 8, 2013

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

levid commented May 8, 2013

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

This comment has been minimized.

Show comment
Hide comment
@daslicht

daslicht Jul 25, 2013

@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.

@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

This comment has been minimized.

Show comment
Hide comment
@Zolmeister

Zolmeister Jul 25, 2013

Member

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

Member

Zolmeister commented Jul 25, 2013

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

@janpantel

This comment has been minimized.

Show comment
Hide comment
@janpantel

janpantel Oct 9, 2013

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 :)

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

This comment has been minimized.

Show comment
Hide comment
@jewelsjacobs

jewelsjacobs Oct 31, 2013

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

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

@chovy

This comment has been minimized.

Show comment
Hide comment
@chovy

chovy Oct 31, 2013

i would like to use mongoose/angular

chovy commented Oct 31, 2013

i would like to use mongoose/angular

@jchiellini

This comment has been minimized.

Show comment
Hide comment
@jchiellini

jchiellini Dec 23, 2013

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.

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

This comment has been minimized.

Show comment
Hide comment
@janpantel

janpantel Dec 23, 2013

@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.

@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

This comment has been minimized.

Show comment
Hide comment
@connor11528

connor11528 Jan 10, 2014

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

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

@albertosouza

This comment has been minimized.

Show comment
Hide comment
@albertosouza

albertosouza Jan 11, 2014

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

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

This comment has been minimized.

Show comment
Hide comment
@jewelsjacobs

jewelsjacobs Jan 22, 2014

+1 for @albertosouza's solution. Thanks!

+1 for @albertosouza's solution. Thanks!

@bfricka

This comment has been minimized.

Show comment
Hide comment
@bfricka

bfricka Mar 5, 2014

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 👍
Edit2: Fixed sleep-deprivation-induced mistakes

bfricka commented Mar 5, 2014

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 👍
Edit2: Fixed sleep-deprivation-induced mistakes

@gauravdhiman

This comment has been minimized.

Show comment
Hide comment
@gauravdhiman

gauravdhiman Aug 22, 2014

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment