I am using Meteor, Handlebars, and Backbone to create a multipage application. I have a Router set up using backbone which sets a session variable, currentPage. How can I render a different template depending on the value of currentPage?
I was told that I could create a Template helper function that would do this, but I'm not sure how to approach this.
if currentPage is global and pages are stored as strings, then I would expect this to work:
Handlebars.registerHelper('currentPageIs',function(page){
return currentPage == page;
});
// and in the html:
{{#if currentPageIs '/posts'}}
{{> posts}}
{{else}}
{{> homepage}}
{{/if}}
A really simple solution is here: https://gist.github.com/3221138
But I would strongly recommend installing meteor-router. This will require you to first install meteorite.
Also, know that an official routing solution is on the roadmap.
The best choice would to use Iron-Router, but it you don't want that here is a nice pattern to change the template:
//HTML
<body>
{{>mainTemplate}}
</body>
//JS Client Initially
var current = Template.initialTemplate;
var currentDep = new Deps.Dependency;
Template.mainTemplate = function()
{
currentDep.depend();
return current;
};
function setTemplate( newTemplate )
{
current = newTemplate;
currentDep.changed();
};
//Later
setTemplate( Template.someOtherTemplate );
I don't know how to check for the route but if you can, then you can just use the proposed setTemplate function to change the template conditionally.
Related
What is the recommended Angular way of adding custom directives conditionally? No google post or SO article has seemed to really answer my question.
The app allows the user to display and interact with customer information. In one view, the person can click on the order details, which makes an API call to return the data. If the order comes back as a certain type, I want to add an indicator to the already rendered sidebar. It seems like a pretty simple and common use case so I'm wondering what I'm missing.
I'm using Angular 1.4x,
Angular Bootstrap
It is a big app but here is a contrived example of the relevant code:
sidebar.html - before API call:
<div>
Customer Info
Notes
</div>
sidebar.html - what I want to have after API call:
<div>
Customer Info
<a href="#/orders" id="ordertype">
<order-type-directive uib-tooltip="Phone Order" tooltip-trigger="mouseenter" tooltip-placement="top"/>
</a>
Notes
</div>
main-controller.js
angular.module('app.main')
.controller('mainCtrl', function(){
var vm = this;
vm.orderType;
...
vm.getOrderData(memberID)
.then(function(data){
vm.orderType = data.type;
//This is where my question arises
})
})
So I can make a directive:
.directive('orderTypeDirective', function(){
...
})
Or I can just do something like this:
var el = angular.element('#orderType')
.html('<span uib-tooltip="Phone Order" popover-trigger="mouseenter"></span>');
var aNewScope = $scope.$new();
$compile(el)(aNewScope)
But I feel like the above is ugly and hacky. As to how I insert it into the DOM after creating the element, the documentation and all googling assumes I already know how to do that.
For the directive option I've tried to use an ng-if but it doesn't update when the API call comes back and updates vm.orderType.
For the create-element option, I could do something like this inside an if block in mainCtrl:
el.replaceWithEl(el);
But I know there must be a better, more "Angular" way and that I'm missing something glaring.
Have you tried maybe with a ng-if directive ? like:
<div>
Customer Info
<a href="#/orders" id="ordertype">
<order-type-directive uib-tooltip="Phone Order"
tooltip-trigger="mouseenter"
tooltip-placement="top"
data-ng-if="mainCtrl.orderType && mainCtrl.orderType.length>0"/>
</a>
Notes
</div>
Or you can put any other condition in your ng-if directive that maybe match well your case
I am trying to understand Angularjs behaviors.
I am building a web-app, and I want the CurrentUser's info be shared among all the app components. To do that, I have created a CurrentUserController bound to $rootScope. This controller is used by a user directive utilized in the body html element, so that it is globally accessible and it's created just one time.
app.controller('CurrentUserController', function ($rootScope)
{
// initialization
$rootScope.userCtrl = self; //<- MAKE IT GLOBAL
this.islogged=false;
this.name="";
var self = this;
// functions
this.isLogged = function()
{ return self.islogged; };
this.setLoggedIn = function(credentials)
{ self.islogged = true; };
this.setLoggedOut = function()
{ self.islogged = false; };
}
);
app.directive('currentUser', function() {
return {
controller:'CurrentUserController'
};
})
and then my html page
<html>
...
<body current-user>
...
</body>
</html>
However I read that Services should be used to share data between controllers, since they are singleton.
So my question is:
is my approach wrong, or it is equivalent as I utilized services?
Moreover, right now I can utilize the directive ng-switch calling $rootScope.userCtrl functions, like this:
<div id="nav-right-side" class="navbar-right" ng-switch on="userCtrl.isLogged()">
<div ng-switch-when="false">
<login-button></login-button>
</div>
<div ng-switch-when="true">
<loggedin-button></loggedin-button>
</div>
</div>
If I utilize services, would I still be able to do that?
Thank you
The $rootScope is indeed shared across all the app and it is also best to store models into services.
Why bother with services ?
Because of the $digest cycle. Each time a watched value is modified, the digest is triggered. In angular, by default the digest is a loop that goes down all your scope from the $rootScope down to its leafs. On each element, it has to get if the value has been modified or not to update the view accordingly. This is pretty expensive, and it is the cause of why angular can be slow on big applications. Keeping the scope as light as possible is how you can build complex apps in angular. That's why storing things is always better in services, you do not pollute the scope with data you could put somewhere else.
That being said, auth is peculiar because you want to access the same data from the view and services. You can store it in the $rootScope as Asta puts it but I do not think that is consistant with best practices. This is opinionated
What can be done is creating a service that will hold you model and share it through a controller to have access to it from both the view and the other services/models.
Session.js
function Session(){
var
self = this,
_islogged=false,
_name = '';
// functions
this.isLogged = function() {
return self.islogged;
};
this.setLoggedIn = function() {
self.islogged = true;
};
this.setLoggedOut = function() {
self.islogged = false; };
}
// GetUsername, setUsername ... Whatever you need
}
angular
.module('app')
.service('Session', Session);
rootController.js
function rootController(Session){
// share the Session Service with the $scope
// this.session is like $scope.session when using the controllerAS syntax.
this.session = Session;
}
angular
.module('app')
.controller('rootController', rootController);
I would suggest you take a look at these articles:
Techniques for Authentification in AngularJs Applications
Comprehensive 10 000 words tutorial in angular
Diving into controllerAs syntax
Your best to use a Service to share data as you mention. In your approach you've used a Controller in a way that its not really intended.
You can call your controller from your HTML by using ng-controller so something like the following should work. This would be useful for a Login view for example or a logout directive.
<div ng-controller="userCtrl">
<div id="nav-right-side" class="navbar-right" ng-switch on="isLogged()">
<div ng-switch-when="false">
<login-button></login-button>
</div>
<div ng-switch-when="true">
<loggedin-button></loggedin-button>
</div>
</div>
</div>
In order to have your session available globally for use elsewhere you can use a service which you can initialise from your app. The session data can be added to $rootScope which you can then reference from any view or controller.
Service
angular.module('app').service('session', function($rootScope) {
$rootScope.sessionData.loggedIn = true
// extra logic etc..
});
Main App
angular.run(session)
angular.module('app').run(function(session) {});
Then to reference the variable from your view
<div id="nav-right-side" class="navbar-right" ng-switch on="sessionData.isLoggedIn">
Note: its good practice to use an object with scope variables to help avoid issues with inheritance.
I wrote an application that uses angularjs for data-binding.
The application is some sort of visual html builder.
When the user is done, I want to allow him to export the HTML.
Since I did massive usage in angularjs, its attributes are all over the place, and the generated HTML is ugly.
Is there anyway to get clean version of the HTML?
this example will export the ugly html: http://jsfiddle.net/ga25hep2/
<div ng-app="myApp">
<div id="the_html" ng-controller="MyCtrl">
Hello, {{name}}!
</div>
<button onclick='exportMe()'>export</button>
</div>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.name = 'Superhero';
}
function exportMe(){
alert(document.getElementById('the_html').outerHTML)
}
If you can update angular to a version after 1.3 (you really should, there are lots of other benefits, too), then you can disable all of the ng- class spam by disabling debug info in your app:
myApp.config(function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
});
You can see the result in an updated fiddle here.
Note that I changed the structure of things slightly but the functionality is the same.
I don't think it exists a native way to do this. AngularJS is not about such kind of work.
Just write a script that parses your exported code and remove each ng-* attribute
We need to iterate through all the dom nodes and remove the ng related attributes and ng related classes.
function exportMe(){
var targetEl = document.getElementById('the_html');
var nodes = targetEl.querySelectorAll('*');
nodes = Array.prototype.slice.call(nodes); // convert NodeList to array
nodes.unshift(targetEl); // include the root node while iterating.
nodes.forEach(function (node) {
Array.prototype.slice.call(node.attributes).forEach(function (attr) {
var classes = Array.prototype.slice.call(node.classList); // conver classList to array
classes = classes.filter(function (cls) {
return !(cls.indexOf('ng-') === 0 || cls.indexOf('data-ng-') === 0);
});
// remove ng related classes
node.setAttribute('class', classes.join(' '));
// remove ng related attributes
if(attr.name.indexOf('ng-') === 0 || attr.name.indexOf('data-ng-') === 0) {
node.removeAttribute(attr.name);
}
})
});
alert(targetEl.outerHTML)
}
Here is a solution: http://jsfiddle.net/ga25hep2/2/
What I am trying to do is after clicking on a buddy in the buddy list, load a chat dialog template HTML file without disturbing other elements in current DOM just like chatting in facebook.
My problem is that after loading the html template file the scope variables such as {{contact.jid}} are not properly rendered, and the controller for the dialog is not even called.
How can I force a rerender or a call on the controller so that those variables are properly renderred? Or should I not use the jQuery.load function to do this? I can't figure out any other way.
Thank you.
Code of the controllers:
// Controller for the chat dialog
ctrl.controller('ChatCtrl',function($scope){
$scope.test = "Test"
});
// Controller for the buddy list
// $scope.toggleDialog is called when a buddy is clicked
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.toggleDialog = function(to){
$.("#chatarea").load("chatdialog.html")
};
});
The controller function of chat dialog is not called after loading chatdialog.html which has an attribute of ng-controller, so the {{test}} variable is not set.
You need to wrap your content that will be compiled inside a directive.
This directive receives the variables and the HTML that will be compiled.
You can wrap this directive in the element that you like:
http://plnkr.co/edit/RYVCrlDmgVXT3LszQ9vn?p=preview
For example, given the following html and variables:
// In the controller
$scope.controllerHtml = '<span>{{variables.myVariable}}</span>';
$scope.controllerVariables = {myVariable: 'hello world'};
<!--In the HTML-->
<div compile-me html-to-bind="controllerHtml" variables="controllerVariables"></div>
You will get the following:
<div>
<span>hello world</span>
</div>
You are loading the external HTML via jQuery and Angular has no way of knowing how to use it. There are two ways to solve this issue:
use ngInclude to load the template from the server, angular will load it from the server and compile it for you to use.
continue to use jQuery load the HTML from the server and use the $compile service to teach angular how to use it
I would highly suggest using method #1 to load your external template.
I suppose the:
$.("#chatarea").load("chatdialog.html")
is the jQuery .load, or something similar. I would get the template via ngInclude, checking if test is setted or not; html:
<div id="chatarea" ng-if="test">
<div ng-include="'chatdialog.html'"/>
</div>
controller:
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.test = '';
var callbackFunction = function(data) {
$scope.test = data.test;
};
$scope.toggleDialog = function(to){
AjaxRequestToGetBuddyInfoAndMessages(to, callbackFunction);
};
});
Obviously test will be a more complex object, so the ngIf test will be different (and you will need to take into account the fact that:
$scope.test = data.test
if they are objects, they will lose the reference and have an unwanted behaviour).
I am new to Angular getting stuck after making ajax call. How do I render/compile the html content once you inject in DOM so that I can still use the AngularJs functions.
Due to the way my backend is set up I have to get content via ajax ($http). And I am making the app without jQuery. I tried $compile and $apply but didn't work. What am I missing here.
I have the code set up at http://jsfiddle.net/rexonms/RB7FQ/3/ . I want the second div content to have the same properties as the first div.
HTML
<div ng-controller="MyCtrl" class="section">
<input ng-model="contentA">
<div>
And the input is: {{contentA}}
</div>
</div>
<div ng-controller="MyAjax" class="section">
<div id="dumpAjax">
{{ajaxData}}
</div>
<button ng-click=getajax()> Get Ajax</button>
</div>
SCRIPT
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
}
function MyAjax($scope){
var data = '<input ng-model="contentB">{{contentB}}';
$scope.getajax = function(){
$scope.ajaxData = data;
}
}
Thanks in advance.
ng-bind-html-unsafe is not available 1.2 and later verison of angular...
so you should use ng-bind-html which creates a binding that will innerHTML the result of evaluating the expression into the current element in a secure way.
using $scope variable in your string make it unsafe, so you should use $sce.trustAsHtml but this time variables in your string cannot be bind because they will be not compiled...
basically you should compile your string in order to bind your variables. Here comes custom directives you can create a directive which can replace with ng-html-bind...
Writing a custom directive which extends ng-bind-html with some extra functions can be a solution...
here is my PLUNKER
and here is your updated JSFIDDLE with my solution...
Instead of {{ajaxData}}, you should use something like:
<div ng-bind-html-unsafe="ajaxData"></div>
However, you'd still need to set the proper model to bind the contentB and get it working.