Using Express to render an .ejs template for AngularJS and use the data inside AngularJS $scope - angularjs

I hope I can explain myself with this first question I post on Stack Overflow.
I am building a small test application with the MEAN stack.
The application receives variable data from Mongoose based on an Express Route I have created.
For example the url is: localhost:3000/cities/test/Paris
Based on the name of the city the response gives me the name of the city and a description. I Know how to get this data inside the .ejs template
But thats not what I want. I want to use this data inside an ngRepeat.
Maybe this is not the right way but maybe you can help me figure this out.
The reason I want to do this is because I don't want a single page application but an Angular template that can be used over and over for each city and only uses the data that gets back from the mongoose find() results and not the whole cities array.
app.js :
var cityRoutes = require('./routes/cities');
app.use('/cities', cityRoutes);
app.set('views', './views'); // specify the views directory
app.set('view engine', 'ejs'); // register the template engine
./routes/cities/cities.js :
var express = require('express');
var citiesList = require('../server/controllers/cities-controller');
var bodyParser = require('body-parser');
var urlencode = bodyParser.urlencoded({ extended: false });
var router = express.Router();
// because this file is a fallback for the route /cities inside app.js
// the route will become localhost:3000/cities/test/:name
// not to be confused by its name in this file.
router.route('/test/:name')
.get(citiesList.viewTest)
module.exports = router;
../server/controllers/cities-controller.js :
var City = require('../models/cities');
module.exports.viewTest = function(request, responce){
City.find({ stad: request.params.name }, function(err, results){
if (err) return console.error(err);
if (!results.length) {
responce.json( "404" );
} else {
responce.render('angular.ejs', { messages:results });
// through this point everything works fine
// the angular.ejs template gets rendered correctly
// Now my problem is how tho get the results from the
// response.render inside the Angular directive
// so I can use the data in a $scope
}
});
};
../models/cities.js
var mongoose = require('mongoose');
module.exports = mongoose.model('City', {
stad: { type: String, required: true },
omschrijving: String
});
AngularJS directive :
// This is where I would like to use the messages result data
// so I can create a $scope that handles data that can be different
// for each url
// so basically I am using this directive as a template
app.directive('bestelFormulier', function () {
return {
restrict: 'E',
templateUrl: '/partials/bestel-formulier.html',
controller: ['$scope', '$http', '$resource', '$cookieStore',
function($scope, $http, $resource, $cookieStore){
// at this point it would be nice that the $scope gets the
// url based results. But I don't now how to do that..
// at this point the var "Cities" gets the REST API with
// all the cities...
var Cities = $resource('/cities');
// get cities from mongodb
Cities.query(function(results){
$scope.cities = results;
//console.log($scope.products);
});
$scope.cities = {};
}],
controllerAs: 'productsCtrl'
}
});
The database is stored like this :
[
{
stad: 'Paris',
omschrijving: 'description Paris',
},
{
stad: 'Amsterdam',
omschrijving: 'description Amsterdam',
}
]
I hope these files included helps explaining my issue.
Thanks in advance for helping me out

I figured out a way to do it...
The following changes to my code fixed my issue.
in app.js
var cityRoutes = require('./routes/cities');
app.use('/', cityRoutes);
// removed the name cities
./routes/cities/cities.js :
router.route('/cities/test/:name')
.get(citiesList.viewTest)
// added this route to use as an API
router.route('/api/cities/test/:name')
.get(citiesList.viewStad)
../server/controllers/cities-controller.js :
// added this callback so that a request to this url
// only responses with the data I need
module.exports.viewStad = function(request, responce){
City.find({ stad: request.params.name }, function(err, results){
if (err) return console.error(err);
if (!results.length) {
responce.json( "404" );
} else {
responce.json( results );
}
});
};
in my AngularJS app I added the $locationDirective and changed the following in my Angular directive to :
var url = $location.url();
var Cities = $resource('/api' + url);
// now when my .ejs template gets loaded the Angular part looks at
// the current url puts /api in front of it and uses it to get the
// correct resource
That is the way how I can use it in my $scope and use al the lovely Angular functionality :-)
Hope I can help other people with this... Eventually it was a simple solution and maybe there are people out there knowing beter ways to do it. For me it works now.

Related

Passing the URL/route parameter from laravel and using in angular

I am new to Laravel 5 and angular.
I am using Laravel routing for traversal and backend operations and angular for just UI operations like fetching data and binding UI grid, etc.
I have following route defined in routes.php file below
routes.php
Route::pattern('clientid', '[0-9]+');
//used for AJAX call from angularjs and populating ui-grid
Route::get('getclients/{clientid?}', 'ClientController#getClients');
//used for displaying Laravel view with ui-grid
Route::get('client/{clientid?}', 'ClientController#showClients');
Please find the angular files:
app.js
var appClients = angular.module('getclients', ['clientsService', 'ui.grid', 'ui.grid.exporter', 'ui.grid.selection']);
clientController.js
appClients.controller('ClientsController', ['$scope', '$http', 'Client', '$interval', '$q', function ($scope, $http, Client, $interval, $q) {
/* Defining UI grid options*/
.
.
/* Calling service to fill the grid*/
Client.get(clientid)
.success(function (data, status, headers, config) {
if (data.length > 0) {
$scope.gridOptions.data = data;
}
});
}
clientsService.js
angular.module('clientsService', [])
.service('Client', function ($http) {
return {
// Get all the photos
get: function (clientid) {
if (clientid !== '') {
return $http.get('/myproject/public/getclients/' + clientid);
}
else {
return $http.get('/myproject/public/getclients/');
}
}
}
});
/*
**Note:**
Have already defined route in routes.php for using the same above:
Route::get('getclients/{clientid?}', 'ClientController#getClients');
*/
EXAMPLE:
Step 1:
Say I am hitting URL: http://<domain>/public/myproject/client/2
The following route would catch it and redirect to view where the ui-grid is present
Route::get('client/{clientid?}', 'ClientController#showClients');
Step 2:
Now, somehow need to figure out how to pass that **2** to angular so that I could pass that parameter while making ajax call and get grid data
I am confused as to how we could use the the url parameter from Laravel in angular?
I reckon that I am missing some concept or doing something wrong here.
Could anyone help me out?
Just a workaround to make it work with angular and without jquery.
From routes.php, the control is transferred to showClients action in ClientsController.php
ClientsController.php (Laravel Controller):
Passed the variable to Laravel view from controller using following statement:
public function showClients($clientid = '') {
return view('masters.clients', compact('clientid'));
}
Clients.php (Laravel View)
Added clientidmodel as ng-model and initialized it with passed clientid from Laravel controller using ng-init
<div ng-app="">
<div ng-controller="ClientsController">
<input type="text" name="txtClientId" ng-model="clientidmodel" style="display: none;" ng-init="clientidmodel = '{!!$clientid!!}'"/>
</div>
</div>
clientController.js
Added the watch to the angular model so that we can capture the initial value passed.
$scope.$watch("clientidmodel", function () {
Client.get($scope.clientidmodel)
.success(function (data, status, headers, config) {
if (data.length > 0) {
$scope.gridOptions.data = data;
}
});
});
Not sure whether this is the efficient way but as of now got the things working with this workaround.
Please let me know in case of any better way to approach the same.
You can achieve this in jquery by
var pathname = window.location.href;
var lastItem = pathname.split("/").pop(-1);
Note : Here you will get the last element
i.e.,
If your url is like yourapp.com/app#/product/15 then the script will return 15. That's the last element after / . You can change this according to your wish.
Then you can pass the value directly inside your Laravel Controller.
$.ajax({
type: "POST",
dataType: 'text',
crossOrigin : true,
data: {param : lastItem},
url: serviceUrl+'/getReceipeDetails',
})
.done(function( data ) {
var result = jQuery.parseJSON(data);
if(result.success==1)
{
$(".yourresult").html('Controller return success');
}
else
{
$(".yourresult").html('Controller return failure');
}
})
.fail( function(xhr, textStatus, errorThrown) {
console.log(errorThrown);
});

Using MongoDB with Restangular

I'm new with MEAN and now I have a problem which I can't solve. On server side for providing a REST API with express I'm using library node-restful. So there I have this schema Sport.js:
var mongoose = require('mongoose');
// create a schema
var SportSchema = new mongoose.Schema({
name: {
type: String,
required: true
}
});
// export the model schema
module.exports = SportSchema;
and controller SportController.js
var restful = require('node-restful');
module.exports = function (app, route) {
// setup the controller for REST
var rest = restful.model(
'sport',
app.models.sport
).methods(['get', 'put', 'post', 'delete']);
// register the endpoint with the application
rest.register(app, route);
// return middleware
return function (req, res, next) {
next();
};
};
On the client side I'm using library restangular as AngularJS service to handle Rest API Restful Resources. Here is controller main.js which use it:
angular.module('clientApp')
.controller('MainCtrl', function ($scope, Sport) {
$scope.sports = Sport.getList().$object;
console.log($scope);
});
In Firebug I see that object $scope so that works fine and also I can use object $scope.sports in my template. But what I want here is using mongoosejs' commands, for example
angular.module('clientApp')
.controller('MainCtrl', function ($scope, Sport) {
// find each sport with a name matching 'Run'
Sport.findOne({ 'name': 'Run' }, function (err, sport) {
if (err) return handleError(err);
console.log(sport.name); // <-- does not work
});
$scope.sports = Sport.getList().$object;
//console.log($scope);
});
Is it possible to do it? I'm really new in MEAN so I apologize if I'm doing something totally wrong.
So you have a functional MongoDB and you now just want to list a sport that contains 'Run'?
Put a ng-model in the html element that is supposed to do something with the output. Say you want to list all the sports containing run in a list:
<any-html ng-model="findonly.name" value="Run">
Or perhaps as a modifiable box:
<input ng-model="searchSport.name" placeholder="Search for sports by name">
Which you can then use as an Angular filter
<table> <th>...</th>
<tr ng-repeat="Sport in sports" | filter:searchSport.name:strict"> <td> {{Sport.name}}
</td></tr></table>
or in case of the first example use
filter:findonly.name:strict"

meanjs route to server controller

i am trying to add a new method to my controller "matches.server.controller" names listTeams.
I have added a new route to the matches.server.route.js file like this
'use strict';
/**
* Module dependencies.
*/
var users = require('../../app/controllers/users.server.controller'),
matches = require('../../app/controllers/matches.server.controller');
module.exports = function(app) {
// Match Routes
app.route('/matches/listTeams') <-- new route added here !!
.get(matches.listteams);
app.route('/matches')
.get(matches.list)
.post(users.requiresLogin, matches.create);
app.route('/matches/:matchId')
.get(matches.read)
.put(users.requiresLogin, matches.hasAuthorization, matches.update)
.delete(users.requiresLogin, matches.hasAuthorization, matches.delete);
// Finish by binding the matches middleware
app.param('matchId', matches.matchByID);
};
this is the method in my server controller:
exports.listteams = function(req, res) {
res.json(1);
};
In matches.client.controller i call the method like this:
$scope.listteams = function(){
$scope.teams = Matches.get('matches/listTeams').success(function(data){
var d = data;
}).error(function(data){
var d = data;
});
however when i debug i always come in the list method of matches and not in listTeams method
What am i doing wrong?
Maybe it's because you are duplicating the path name
All parameters that goes after '/matches/' is handling by
app.route('/matches/:matchId')
.get(matches.read)
.put(users.requiresLogin, matches.hasAuthorization, matches.update)
.delete(users.requiresLogin, matches.hasAuthorization, matches.delete);
and perceiving like argument of'/:matchId'
In your case: find me team with id "listTeams"
Try to rename your path from matches to smth else like
module.exports = function(app) {
// Match Routes
app.route('/smthelse_not_matches/listTeams') <-- new route added here !!
.get(matches.listteams);

Proper place for data-saving logic in AngularJS

App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.

Tracking Site Activity (Google Analytics) using Backbone

I am looking the best way to track the Site Activity in Google Analytics for a web app made with Backbone and Requires.
Looking At Google's Page, I found this drop-in plugin - Backbone.Analytics.
My questions are:
1) using Backbone.Analytics, should I change backbone.analytics.js in order to add _gaq.push(['_setAccount', 'UA-XXXXX-X']);?
2) Are there other possible solutions/plugins?
I prefer "do it yourself" style :) It's really simple:
var Router = Backbone.Router.extend({
initialize: function()
{
//track every route change as a page view in google analytics
this.bind('route', this.trackPageview);
},
trackPageview: function ()
{
var url = Backbone.history.getFragment();
//prepend slash
if (!/^\//.test(url) && url != "")
{
url = "/" + url;
}
_gaq.push(['_trackPageview', url]);
}
}
And you add google analytics script to your page as usual.
You shouldn't have to change anything. Just add your Google Analytics code snippet, like normal, and include Backbone.Analytics as you would any other Javascript library.
Just figured i'd share how i'm doing it. This might not work for larger apps but I like manually telling GA when to track page views or other events. I tried binding to "all" or "route" but couldn't quite get it to record all the actions that I need automajically.
App.Router = BB.Router.extend({
//...
track: function(){
var url = Backbone.history.getFragment();
// Add a slash if neccesary
if (!/^\//.test(url)) url = '/' + url;
// Record page view
ga('send', {
'hitType': 'pageview',
'page': url
});
}
});
So i just call App.Router.Main.track(); after I navigate or do anything i want to track.
Do note that I use the new Analytics.js tracking snippet which is currently in public beta but has an API so intuitive that it eliminates the need for a plugin to abstract any complexity what so ever. For example: I keep track of how many people scroll to end of an infinite scroll view like this:
onEnd: function(){
ga('send', 'event', 'scrollEvents', 'Scrolled to end');
}
Good luck.
I wrote a small post on this, hope it helps someone:
http://sizeableidea.com/adding-google-analytics-to-your-backbone-js-app/
var appRouter = Backbone.Router.extend({
initialize: function() {
this.bind('route', this.pageView);
},
routes: {
'dashboard': 'dashboardPageHandler'
},
dashboardPageHandler: function() {
// add your page-level logic here...
},
pageView : function(){
var url = Backbone.history.getFragment();
if (!/^\//.test(url) && url != ""){
url = "/" + url;
}
if(! _.isUndefined(_gaq)){
_gaq.push(['_trackPageview', url]);
}
}
});
var router = new appRouter();
Backbone.history.start();
Regarding other possible solutions/plugins, I've used https://github.com/aterris/backbone.analytics in a few projects and it works quite well as well. It also has options for a few more things like event tracking which can be handy at some point in your analytics integration.
If you use the new universal analytics.js, you can do that like this:
var Router = Backbone.Router.extend({
routes: {
"*path": "page",
},
initialize: function(){
// Track every route and call trackPage
this.bind('route', this.trackPage);
},
trackPage: function(){
var url = Backbone.history.getFragment();
// Add a slash if neccesary
if (!/^\//.test(url)) url = '/' + url;
// Analytics.js code to track pageview
ga('send', {
'hitType': 'pageview',
'page': url
});
},
// If you have a method that render pages in your application and
// call navigate to change the url, you can call trackPage after
// this.navigate()
pageview: function(path){
this.navigate(path);
pageView = new PageView;
pageView.render();
// It's better call trackPage after render because by default
// analytics.js passes the meta tag title to Google Analytics
this.trackPage();
}
}
All answers seem to be almost good, but out-of-date (Sept. 2015). Following this Google devs guide: https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications
Here's my version of the solution (I've added the suggested call to ga('set'...) ):
MyRouter = Backbone.Router.extend
...
initialize: () ->
# Track every route and call trackPage
#bind 'route', #trackPage
trackPage: () ->
url = Backbone.history.getFragment()
# Add a slash if neccesary
if not /^\//.test(url) then url = '/' + url
# Analytics.js code to track pageview
global.ga('set', {page: url})
global.ga('send', 'pageview')
...
Just posting an update to this question as it seems to be one I get a lot from backbone.js developers I know or work with who seem to fall at the last hurdle.
The Javascript:
App.trackPage = function() {
var url;
if (typeof ga !== "undefined" && ga !== null) {
url = Backbone.history.getFragment();
return ga('send', 'pageview', '/' + url);
}
};
Backbone.history.on("route", function() {
return App.trackPage();
});
The Tracking Snippet:
<head>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||
function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();
a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;
a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script',
'//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXXXX-X', 'auto');
</script>
</head>
The Tracking Snippet should be available on any page you wish to track activity. This could be your index.html where all your content is injected, but some sites may have multiple static pages or a mix. You can include the ga('send') function if you wish, but it will only fire on a page load.
I wrote a blog post that goes in to more detail, explaining rather than showing, the full process which you can find here: http://v9solutions.co.uk/tech/2016/02/15/how-to-add-google-analytics-to-backbone.js.html

Resources