Include TemplateController in new projects #273

Closed
mikermcneil opened this Issue Mar 30, 2013 · 13 comments

Comments

8 participants
@mikermcneil
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);
    });
  }
};


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

As a short-term workaround (from @Tidwell):

(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

Member

mikermcneil commented Mar 30, 2013

As a short-term workaround (from @Tidwell):

(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

@yoshuawuyts

This comment has been minimized.

Show comment
Hide comment
@yoshuawuyts

yoshuawuyts Aug 1, 2013

Contributor

Maybe I'm not fully grasping the difference between /views and /templates for jade-lang, but I'm trying to render my views through Angulars ng-view and the sails /views folder. Since I'm not using EJS, would this be the proper way? Also: is the above code (with streams) going to be implemented into the next version of sails? I think that out of the box support for Angular (with proper documentation) is quite important... but well I guess you guys must feel the same :)

Contributor

yoshuawuyts commented Aug 1, 2013

Maybe I'm not fully grasping the difference between /views and /templates for jade-lang, but I'm trying to render my views through Angulars ng-view and the sails /views folder. Since I'm not using EJS, would this be the proper way? Also: is the above code (with streams) going to be implemented into the next version of sails? I think that out of the box support for Angular (with proper documentation) is quite important... but well I guess you guys must feel the same :)

@anissen

This comment has been minimized.

Show comment
Hide comment
@anissen

anissen Aug 27, 2013

I am also attempting to use Angular as front-end for my Sails server and the integration is not exactly pretty. So I would like to echo @yoshuawuyts's request for an elegant out-of-the-box way to integrate with Angular.

anissen commented Aug 27, 2013

I am also attempting to use Angular as front-end for my Sails server and the integration is not exactly pretty. So I would like to echo @yoshuawuyts's request for an elegant out-of-the-box way to integrate with Angular.

@mikermcneil

This comment has been minimized.

Show comment
Hide comment
@mikermcneil

mikermcneil Sep 8, 2013

Member

@anissen I feel your pain-- if you've got a project handy you could shoot my way, I'd love to look at it and get a better idea
@yoshuawuyts you are right again my friend

Template compatibility seems like a priority, even if it's just getting a good boilerplate example up people can use. In my experience, it's best to pick up your views as precompiled templates (aka javascript functions). This is oftentimes done using AMD (usually RequireJS), but also sometimes dropped directly on the page (the JST behavior currently in the default Sails gruntfile)

As for the API-- either direction is a one-to-many problem, unfortunately:

See #835 for one approach (marshaling the API on the backend)
See also #742 (marshaling the API on the frontend)

Member

mikermcneil commented Sep 8, 2013

@anissen I feel your pain-- if you've got a project handy you could shoot my way, I'd love to look at it and get a better idea
@yoshuawuyts you are right again my friend

Template compatibility seems like a priority, even if it's just getting a good boilerplate example up people can use. In my experience, it's best to pick up your views as precompiled templates (aka javascript functions). This is oftentimes done using AMD (usually RequireJS), but also sometimes dropped directly on the page (the JST behavior currently in the default Sails gruntfile)

As for the API-- either direction is a one-to-many problem, unfortunately:

See #835 for one approach (marshaling the API on the backend)
See also #742 (marshaling the API on the frontend)

@yoshuawuyts

This comment has been minimized.

Show comment
Hide comment
@yoshuawuyts

yoshuawuyts Sep 8, 2013

Contributor

@mikermcneil This is my current solution:

config/routes.js

module.exports.routes = {

  '/': {
    view: 'layout'
  }, 

  '/home': {
    view: 'layout'
  }, 

[etc.]


  // Controllers to allow Angular.js to properly fetch files located in specified locations

  '/ui/:file' : { 
    controller : 'ViewController', 
    action : 'ui'
  },

  '/landing/:file' : { 
    controller : 'ViewController', 
    action : 'landing'
  },

api/controllers/viewController.js

/**
 * viewController
 *
 * Makes sure that Angular can properly call view partials. 
 * Routed in 'config/routes.js'
 */

module.exports = {

  //* e.g.
  ui: function (req,res) {
    console.log('Serving view: ', 'ui /', req.param('file'));
    res.render('ui' + '/' + req.param('file'));
  },

  landing: function (req,res) {
    console.log('Serving view: ', 'landing /', req.param('file'));
    res.render('landing' + '/' + req.param('file'));
  },  
};

I use a single layout.jade file that hosts ng-view, fetching from two directories containing my views ( views/landing/ & views/ui/).

Contributor

yoshuawuyts commented Sep 8, 2013

@mikermcneil This is my current solution:

config/routes.js

module.exports.routes = {

  '/': {
    view: 'layout'
  }, 

  '/home': {
    view: 'layout'
  }, 

[etc.]


  // Controllers to allow Angular.js to properly fetch files located in specified locations

  '/ui/:file' : { 
    controller : 'ViewController', 
    action : 'ui'
  },

  '/landing/:file' : { 
    controller : 'ViewController', 
    action : 'landing'
  },

api/controllers/viewController.js

/**
 * viewController
 *
 * Makes sure that Angular can properly call view partials. 
 * Routed in 'config/routes.js'
 */

module.exports = {

  //* e.g.
  ui: function (req,res) {
    console.log('Serving view: ', 'ui /', req.param('file'));
    res.render('ui' + '/' + req.param('file'));
  },

  landing: function (req,res) {
    console.log('Serving view: ', 'landing /', req.param('file'));
    res.render('landing' + '/' + req.param('file'));
  },  
};

I use a single layout.jade file that hosts ng-view, fetching from two directories containing my views ( views/landing/ & views/ui/).

@anissen

This comment has been minimized.

Show comment
Hide comment
@anissen

anissen Sep 16, 2013

@mikermcneil First off, sorry I've taken so long in responding - I was on a business trip. Secondly, thank you for your consideration in this matter.

Unfortunately I cannot share this specific project as it's the property of the company I work at (a maritime company! 👍). We're using Sails for the website/administration/API parts of our software solution.

However, I'll list some of the pain points that I can recall:

  • Route handling! This has been mentioned already but angular's and Sails' route system is not a great match. In the solution by @yoshuawuyts (which we also use) Sails' route system is degraded to serving views through a dummy controller. We serve EJS-files but use them as static view files.
  • Differences between regular http requests and socket requests
    • Sockets was missing IP property (this has been added in 0.9.4)
    • Cannot do requests using BasicAuth over sockets
    • We want the (admin) web interface to have more rights than the public-access API. However, the web interface also relies on the API for data so the separation seems a bit blurred. I haven't looked into using CSRF-tokens yet, so they might prove a possible solution.

DISCLAIMER: This has little to do with Sails as a whole. We're really impressed with Sails in my company and have found it to be extremely helpful :)

anissen commented Sep 16, 2013

@mikermcneil First off, sorry I've taken so long in responding - I was on a business trip. Secondly, thank you for your consideration in this matter.

Unfortunately I cannot share this specific project as it's the property of the company I work at (a maritime company! 👍). We're using Sails for the website/administration/API parts of our software solution.

However, I'll list some of the pain points that I can recall:

  • Route handling! This has been mentioned already but angular's and Sails' route system is not a great match. In the solution by @yoshuawuyts (which we also use) Sails' route system is degraded to serving views through a dummy controller. We serve EJS-files but use them as static view files.
  • Differences between regular http requests and socket requests
    • Sockets was missing IP property (this has been added in 0.9.4)
    • Cannot do requests using BasicAuth over sockets
    • We want the (admin) web interface to have more rights than the public-access API. However, the web interface also relies on the API for data so the separation seems a bit blurred. I haven't looked into using CSRF-tokens yet, so they might prove a possible solution.

DISCLAIMER: This has little to do with Sails as a whole. We're really impressed with Sails in my company and have found it to be extremely helpful :)

@WonderPanda

This comment has been minimized.

Show comment
Hide comment
@WonderPanda

WonderPanda Sep 24, 2013

@yoshuawuyts
@anissen

Hey guys, I too am currently trying to discover best practices and work arounds for using Sails with Angular.js especially in the context of a single page app. Any chance you could elaborate a little bit more on the ViewController solution that you are using? Particularly some reference to Angular code accessing the ViewController would be very useful!

@yoshuawuyts
@anissen

Hey guys, I too am currently trying to discover best practices and work arounds for using Sails with Angular.js especially in the context of a single page app. Any chance you could elaborate a little bit more on the ViewController solution that you are using? Particularly some reference to Angular code accessing the ViewController would be very useful!

@yoshuawuyts

This comment has been minimized.

Show comment
Hide comment
@yoshuawuyts

yoshuawuyts Sep 24, 2013

Contributor

On the front-end I've implemented it as follows:

assets/js/app.js

angular.module('stddr', [], function($routeProvider, $locationProvider) {

    $routeProvider.

    when('/', {
      templateUrl: '/landing/landing', 
      controller: 'ViewCntrl'
    }).

    when('/home', {
      templateUrl: '/ui/home', 
      controller: 'ViewCntrl'
    }).

     when('/course', {
      templateUrl: '/ui/course', 
      controller: 'ViewCntrl'
    }).

      when('/settings', {
      templateUrl: '/ui/settings', 
      controller: 'ViewCntrl'
    }).

    when('/landing', {
      templateUrl: '/landing/landing', 
      controller: 'ViewCntrl'
    }).

    otherwise({
        redirectTo: '/404'
      });

  $locationProvider.html5Mode(true);

  });

function ViewCntrl($scope, $route, $routeParams, $location) {
  $scope.$route = $route;
  $scope.$location = $location;
  $scope.$routeParams = $routeParams;
}

You need full routing on both your front end and your back end to ensure all requests are caught and routed properly. Got to agree with @anissen that using dummy controllers isn't exactly the most elegant solution, but until something better comes along this will have to do.

Contributor

yoshuawuyts commented Sep 24, 2013

On the front-end I've implemented it as follows:

assets/js/app.js

angular.module('stddr', [], function($routeProvider, $locationProvider) {

    $routeProvider.

    when('/', {
      templateUrl: '/landing/landing', 
      controller: 'ViewCntrl'
    }).

    when('/home', {
      templateUrl: '/ui/home', 
      controller: 'ViewCntrl'
    }).

     when('/course', {
      templateUrl: '/ui/course', 
      controller: 'ViewCntrl'
    }).

      when('/settings', {
      templateUrl: '/ui/settings', 
      controller: 'ViewCntrl'
    }).

    when('/landing', {
      templateUrl: '/landing/landing', 
      controller: 'ViewCntrl'
    }).

    otherwise({
        redirectTo: '/404'
      });

  $locationProvider.html5Mode(true);

  });

function ViewCntrl($scope, $route, $routeParams, $location) {
  $scope.$route = $route;
  $scope.$location = $location;
  $scope.$routeParams = $routeParams;
}

You need full routing on both your front end and your back end to ensure all requests are caught and routed properly. Got to agree with @anissen that using dummy controllers isn't exactly the most elegant solution, but until something better comes along this will have to do.

@WonderPanda

This comment has been minimized.

Show comment
Hide comment
@WonderPanda

WonderPanda Sep 24, 2013

@yoshuawuyts Wow thanks for the quick reply! That definitely is starting to make a little bit more sense. I guess the last piece of the puzzle for me is what the implementation of the angular views might be then? I'm quite new to Sails so I'm a little confused still as to how this all fits together. From the tutorials I've followed so far a simple action on the User controller to display a profile page might be like this:

 // render the profile view (eg. /views/show.ejs)
    show: function (req, res, next) {
        User.findOne(req.param('id'), function foundUser (err, user) {
            if (err) return next(err);
            if (!user) return next();
            res.view({
               user: user
            });
        });
    },

And the corresponding view using EJS would be:

<div class="container">
    <h1><%= user.name %></h1>
    <h3><%= user.title %></h3>
    <% if (user.admin) { %>
        <img src="/images/admin.png" /> admin
    <% } else { %>
        <img src="/images/pawn.png" /> pawn
    <% } %>
    <hr>
    <h3>contact: <%= user.email %></h3>

    <a class="btn btn-medium btn-primary" href="/user/edit/<%= user.id %>">Edit</a>
</div>

How does this fit in using Angular? Are you using $resource to pull information from the JSON API or am I missing something in terms of how you make things available to the $scope of whichever controller is ultimately handling the data being injected into the view being rendered from the ViewController?

Thanks again for taking the time to help a newbie out!

@yoshuawuyts Wow thanks for the quick reply! That definitely is starting to make a little bit more sense. I guess the last piece of the puzzle for me is what the implementation of the angular views might be then? I'm quite new to Sails so I'm a little confused still as to how this all fits together. From the tutorials I've followed so far a simple action on the User controller to display a profile page might be like this:

 // render the profile view (eg. /views/show.ejs)
    show: function (req, res, next) {
        User.findOne(req.param('id'), function foundUser (err, user) {
            if (err) return next(err);
            if (!user) return next();
            res.view({
               user: user
            });
        });
    },

And the corresponding view using EJS would be:

<div class="container">
    <h1><%= user.name %></h1>
    <h3><%= user.title %></h3>
    <% if (user.admin) { %>
        <img src="/images/admin.png" /> admin
    <% } else { %>
        <img src="/images/pawn.png" /> pawn
    <% } %>
    <hr>
    <h3>contact: <%= user.email %></h3>

    <a class="btn btn-medium btn-primary" href="/user/edit/<%= user.id %>">Edit</a>
</div>

How does this fit in using Angular? Are you using $resource to pull information from the JSON API or am I missing something in terms of how you make things available to the $scope of whichever controller is ultimately handling the data being injected into the view being rendered from the ViewController?

Thanks again for taking the time to help a newbie out!

@xdissent

This comment has been minimized.

Show comment
Hide comment
@xdissent

xdissent Sep 24, 2013

Member

You could use $resource for your User model as show above if you alias get() to findOne(). Or use spinnaker which is like $resource over sockets.

Member

xdissent commented Sep 24, 2013

You could use $resource for your User model as show above if you alias get() to findOne(). Or use spinnaker which is like $resource over sockets.

@swordsreversed

This comment has been minimized.

Show comment
Hide comment
@swordsreversed

swordsreversed Nov 24, 2013

Is there any progress on this issue? Last update was from two months ago, and I'm looking for a (somewhat) definitive solution to this... : |

Is there any progress on this issue? Last update was from two months ago, and I'm looking for a (somewhat) definitive solution to this... : |

@jakswa

This comment has been minimized.

Show comment
Hide comment
@jakswa

jakswa Jan 8, 2014

Another bump! I dove into sails with some excitement as it seemed to focus on single-page apps, but not being able to cleanly utilize angular has me down in the dumps. Either supporting the standard http-based requests or some $templateCache sorcery (like I've seen in other packages) would be awesome.

edit: oops, this was meant for #205 I think

jakswa commented Jan 8, 2014

Another bump! I dove into sails with some excitement as it seemed to focus on single-page apps, but not being able to cleanly utilize angular has me down in the dumps. Either supporting the standard http-based requests or some $templateCache sorcery (like I've seen in other packages) would be awesome.

edit: oops, this was meant for #205 I think

@CWyrtzen

This comment has been minimized.

Show comment
Hide comment
@CWyrtzen

CWyrtzen May 8, 2014

We're closing this to keep things under control. It has been tracked. Thanks for sharing your ideas and feedback. If it needs to reopen we can certainly do it if there's interest.

CWyrtzen commented May 8, 2014

We're closing this to keep things under control. It has been tracked. Thanks for sharing your ideas and feedback. If it needs to reopen we can certainly do it if there's interest.

@CWyrtzen CWyrtzen closed this May 8, 2014

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