use ngStorage values in the template of site - angularjs

I've got a site that uses ngStorage to save cart data until checkout, when it is then stored in the database. When using vm.$storage = $localStorage; in my controller, I can access the cart information easily for the part of the page in the section of index.html but I WANT to be able to access and display information from ngStorage in the header of my site, which is nested inside .
I've tried adding ng-controller="SiteController" to my li tag but still don't get anything to display that way.
How can I get the localStorage using ngStorage to display in the template (index.html) of my site?
My index.html file is setup something like this:
<html ng-app="myApp">
<body>
// a bunch of code to show my other navigation
// code to show my shopping cart at a glance
<li class="quick-cart">
<a href="#">
<span class="badge badge-aqua btn-xs badge-corner">
{{vm.$storage.cart.items.length}}
</span>
<i class="fa fa-shopping-cart"></i>
</a>
<div class="quick-cart-box">
<h4>Shop Cart</h4>
<div class="quick-cart-wrapper">
<a href="#"><!-- cart item -->
<h6>Item Name</h6>
<small>$12.34</small>
</a><!-- /cart item -->
<!-- cart no items example -->
<!--<a class="text-center" href="#"><h6>0 ITEMS ON YOUR CART</h6></a>-->
</div>
<!-- quick cart footer -->
<div class="quick-cart-footer clearfix">
VIEW CART
<span class="pull-left"><strong>TOTAL:</strong> $54.39</span>
</div>
<!-- /quick cart footer -->
</div>
</li>
//other code between the header and controller content
<div ng-view></div> //the CONTENT section controlled by each controller
//other code to finish the page
My site-controller.js code is:
/* global angular */ angular.module('myApp')
.controller('SiteController', SiteController);
function SiteController($route, $routeParams, AuthFactory, jwtHelper, $window, $localStorage, $sessionStorage) {
var vm = this;
vm.$storage = $localStorage;
vm.$storage.cart = vm.$storage.cart;
console.log(vm.$storage.cart);
}

Since you are using controllerAs syntax in controller you need to use the as alias in ng-controller
ng-controller="SiteController as vm"
This defines the vm you are already using in the view for:
{{vm.$storage.cart.items.length}}

Related

Angular - Page does not bind variable outside view when specific view url is requested

I tried to solve the problem but could not find the solution for this one. I have 2 views home and cart. Home displays all the products and cart displays all the items added to the cart. Backend for this is NodeJS which just servers index file and processes views.
In the following index file, I have {{fullCart.totalItems}} . When I try localhost:3000 then it binds perfectly by showing 0 and routes to localhost:3000/home but when I try localhost:3000/home url directly in the browser then it does not bind {{fullCart.totalItems}} so nothing is displayed
index.html
<body ng-app="bagitApp" ng-init="quanArr=[1,2,3,4,5,6,7,8,9,10]">
<div class="container-fluid nav-color navbar-fixed-top" ng-controller="headerController">
<div class="container">
<a class="navbar-brand" href="#"><img class="img-responsive" src="images/logo.png"></a>
<nav>
<div class="navbar-header">
<button type="button" class="navbar-toggle nav-btn" data-toggle="collapse" data-target="#navBar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="collapse navbar-collapse" id="navBar">
<ul class="nav navbar-nav">
<li>Home</li>
<li>Cart</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><i class="glyphicon glyphicon-shopping-cart"></i><i class=" teal new badge">{{fullCart.totalItems}}</i></li>
</ul>
</div>
</nav>
</div>
</div>
<div class="container pan-mar" ng-view>
</div>
</body>
main.controller.js
var app=angular.module('bagitApp',['ngRoute']);
app.config(['$routeProvider','$locationProvider',function($routeProvider,$locationProvider){
$routeProvider
.when('/home',{
templateUrl:'partials/Products Panel.html',
controller:'productsController'
})
.when('/cart',{
templateUrl:'partials/cart.html',
controller:'cartController'
})
.otherwise({
redirectTo:'/home'
});
$locationProvider.html5Mode({enabled:true,requireBase:false});
}]);
app.factory('cartService',function(){
return {
cart:[],
totalItems:0
};
});
function headerController($rootScope,$scope,cartService){
$scope.fullCart = cartService;
console.log("headerController")
console.log($scope.fullCart)
}
function productsController($rootScope,$scope,cartService){
$scope.fullCart = cartService;
$scope.products = products;
console.log("productsController")
console.log($scope.fullCart)
$scope.addToCart=function(item,quantity){
$scope.fullCart.cart.push({item,quantity:parseInt(quantity)});
$scope.fullCart.totalItems += parseInt(quantity);
console.log($scope.fullCart);
}
$scope.removeFromCart=function(itemId){
}
}
app.controller('productsController',['$rootScope','$scope','cartService',productsController])
app.controller('headerController',['$rootScope','$scope','cartService',headerController])
Server.js
var express = require('express');
var server = express();
var engines = require('consolidate');
server.set('views',__dirname);
server.engine('html',engines.mustache);
server.set('view engine','html')
server.use(express.static(__dirname));
server.get('/:name',function(req,res){
res.render('index');
});
server.listen(3000,function(){
console.log("Listening on localhost:3000");
})
Your problem is that when you go to
localhost:3000
then on the server your express.static serves index.html as a static asset so it's not render using mustache.
Since mustache uses same syntax {{ }} as angular then when you go to /home route handler for /:name renders index.html using mustache and since you dont pass in any variable then mustache simply deletes this
{{fullCart.totalItems}}
what you should do is
1.create public folder and place all public assets in it and change
server.use(express.static(__dirname));
to
server.use('/public', express.static(__dirname));
-html templates into templates subfolder
-js files like angular.js into js subfolder, etc.
2.create views folder and place your index.html in it.
This way you avoid index.html is servered once as static and once render with mustache. from now on it's always rendered. Unfortunately this will lead to another problem which is mustache will overwrite or delete any {{}}
So one option is to change angular {{}} to something like [[ ]] by adding
appModule.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
});
Then you could use it in your template:
<div>[[message]]</div>
Or you can most probably change {{}} to something else in mustache too but i don't know myself but you can have a look at mustache docs
On the node service you will want to add this.
app.get('*', function(req, res, next){
res.sendFile(PATH_TO_INDEX);
});
This will respond with the index.html on any other route, and then once the angular app bootstraps it will take over on routing and render the correct page.
In your angular app you will want to add this as well for each script/css file.
<base href="/">
Make sure it is above any of your scripts or stylesheets though.

How to prevent services from firing before user is authorized in AngularJS app

I have a service that is called to get the authorized user's location list. This information is used to populate a select list and they can switch locations to view different information.
The code for the select list is in the nav bar in the index.html but I don't want the service to fire on the page until after they user has been successfully authenticated and the app switches to the main view.
I'm using ng-show to hide the navigation bar with an isAuthorized() function for the AuthCtrl. However, the service call from the LocationCtrl to populate the ng-repeat always fires whether the user is logged in or not. I could move the nav component into the main view but then I'd need to have it in all the views that are protected. What is the best way to handle populating service-based menu items in a single page app?
index.html
<!doctype html>
<html lang="en" ng-app="betterbooks">
<head>
scripts...
</head>
<body>
<nav class="navbar-fixed-top" ng-controller="AuthCtrl as auth">
<div class="container" ng-show="auth.isAuthorized()">
<ul class="navbar-right">
<li>Home</li>
<li class="dropdown">
Accounts <span class="caret"></span>
<ul class="dropdown-menu" ng-controller="LocationCtrl">
<li ng-repeat="location in locations">
<a href="#/location/{{ location.id }}/">
{{ location.name }}
</a>
</li>
</ul>
</li>
<li><button ng-click="auth.logout()">Logout</button></li>
</ul>
</div>
</nav>
<div class="view-container" style="height:100%">
<div ui-view class="view-frame"></div>
</div>
</body>
</html>
Use an ng-if instead. The navbar won't be loaded into the dom until auth.isAuthorized() is true, so the LocationCtrl won't render or run until that point.

Angularjs: Call bootstrap modal multiple views.

My goal is to move the modal out of my source view, and move it into its own view, but for some reason, my modal does not show up. I have tried putting the modal into a directive, but its not working. I have moved the modal to the index page, but then the view changes when they modal opens.
Category.html
<section class="row">
<h1>{{ selectedCategory | uppercase}}</h1>
<div class="col-md-3 col-xs-12" ng-repeat="source in sources[selectedCategory]">
<a ng-href="#/explore/{{selectedCategory}}/{{source.name}}">
<section class="col-xs-2">
<img ng-src="assets/img/{{source.imagename}}" height="30" width="30">
</section>
<p class="col-xs-8">{{ source.name }}</p>
</a>
<div ng-if="!objectContains(addedSources,source.name)"><!-- Show this, if addesSources does not contains source.name -->
<section class="col-xs-2">
<!-- This part, is where i want the modal to be called. -->
<button class="tiny" data-toggle="modal" data-target="#myModal" ng-click="setUpModalData(source.name)">Add</button>
</section>
</div>
<div ng-if="objectContains(addedSources,source.name)"> <!-- Show this, if addesSources contains source.name -->
<section class="col-xs-2">
<button class="tiny secondary" ng-click="removeSource(source.name)">remove</button>
</section>
</div>
</div>
</section>
<div class="modal fade" id="myModal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal content -->
</div><!-- modal-content -->
</div><!-- modal-dialog -->
</div><!-- myModal -->
Currently the controller does not open or close the modal, its only job is to provide the information shown in the modal. If you click on add for a particular source, the modal will open with that source name on the top.
I have tried doing what seems to work for other people, but i cant get it to work for me.
I want to be able to call this modal from different views. You can click add on the source List view (list of all sources), and the individual source view(details about one source). There will be an add button on both views, that will both call this modal.
I am using twitter bootstrap for the css.
Here is my Controller for this view.
.controller('CategoryController', ['$scope', '$http', '$routeParams', function($scope, $http, $routeParams){
$http.get('assets/js/Category.json').success(function(data) {
$scope.selectedCategory = $routeParams.simplename; //Get the the name of the url
$scope.sources = data; //set sources list, for view to iterate.
$scope.collectionList = {}; // List of all collections, and source under every collection
$scope.addedSources = {}; // object of sources, and the collection they're in. etc ("The Verge" : tech)
$scope.setUpModalData = function(simplename){
$scope.selectedSourceName = $scope.selectedSourceNameTitle =simplename;
$scope.selectedCollection = $scope.selectedCategory;
/* if the current category does not exist in the collection list,
* we will pre fill the form with the current category.
* Other wise we will set it, and it will not be pre pubulated.
*/
if(!($scope.selectedCategory in $scope.collectionList)){
$scope.collectionName = $scope.selectedCategory;
$scope.selectedCollection = 'createNewCollection';
}
}
$scope.removeSource = function(simplename){
var collectionNameHolder = $scope.addedSources[simplename]; //The collection the source is in.
delete $scope.collectionList[collectionNameHolder][simplename]; //delete the source from both lists.
delete $scope.addedSources[simplename]
}
$scope.arrayContains = function(array, element){
return (array.indexOf(element) > -1);
}
$scope.objectContains = function(object, element){
return (element in object);
}
});
}])
Can you put that in a simplified jsfiddle? I'm not sure about what you're trying to achieve and what's not working.
In any case, if you want to have a single modal for multiple views, you could do that with a service, which job would be to open or close the modal, or with events on rootScope which would tell the modal's controller that the modal should be displayed or hidden.
https://docs.angularjs.org/guide/services#creating-services
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast

My directive stopped working when I started using ng-repeat with a different controller. What am I doing wrong?

So I followed this guide so I could have a nav bar on every page: http://tomaszdziurko.pl/2013/02/twitter-bootstrap-navbar-angularjs-component/
And it was working, until I created a separate controller to populate my bootstrap carousel. The thing is, my ng-repeat works fine, but when it does I can't see my navbar on that page. I can see it just fine on other pages. I believe this is a scoping issue, but I am not sure where.
This is what I have in the main body of this page:
<body>
<reusable-navbar></reusable-navbar>
<!-- Carousel Start -->
<div id="main-carousel" class="carousel slide container" data-ride="carousel">
<!-- Wrapper for slides -->
<div class="carousel-inner">
<!--Must set this by hand-->
<div class="item active">
<img alt="" src="../Revamp/Images/carousel/1.jpg">
</div>
<!--Repeat through the rest-->
<div ng-controller="carouselPhotoController">
<div class="item" ng-repeat="source in source">
<img alt="" ng-src="{{source.source}}">
</div>
</div>
</div>
</div>
And my controller looks like this:
var carouselPhotoController=angular.module("revampApp", []);
carouselPhotoController.controller("carouselPhotoController", function($scope, $http){
$http.get('../Revamp/Images/carousel/photos.json').success(function(photos){
//Carousel photos
$scope.source = photos;
})
});
And the directive is identical to the one in that walk through, just with a different template. So how to I get it so my nav bar will show up AND I can use ng-repeat?
Make sure you are not recreating the app.
This creates a new app:
var carouselPhotoController=angular.module("revampApp", []);
But this only accesses an app already created (note the absence of the second parameter):
var carouselPhotoController=angular.module("revampApp");
Change the above line and it should work.

angular controllers, multiple templates, and passing scope values from controllers to master page

I'm starting a new project and am going to be using angular in a "single page architecture" application. I'm a little new to angular.
So, I purchased a template for my site. It has 2 distinct layouts that I would like to use. 1 for my unauthenticated (marketing) pages and another for most of my authenticated pages.
The difference in each is subtle, but the inside pages require a class on the <body> tags that the outside pages cannot have. I considered using 2 layouts but then that got tricky as I started thinking about how I would lay out my urls.
My thought is to use angular to manage my layout so that I only need one master page like this:
<body ng-class="{menu-right-hidden: isInternalPage }">
<div class="container-fluid">
<div ng-if="isInternalPage" id="menu" class="hidden-print hidden-xs sidebar-blue sidebar-brand-primary">
<!-- sidebar content -->
</div>
<div id="content">
#RenderBody()
</div>
<div class="clearfix"></div>
<div ng-if="isInternalPage" id="footer" class="hidden-print">
<!-- internal footer -->
</div>
<div ng-if="!isInternalPage" id="footer" class="hidden-print">
<!-- external footer -->
</div>
</div>
</body>
My question is this: Is there an easy way to set isInternalPage (and possibly other valies) without having $scope.isInternalPage = true/false; decorating all of my controllers?
You could use ng-init and define a scope variable on $rootScope:
<body ng-init="$root.isInternalPage = true" ng-class="{menu-right-hidden: $root.isInternalPage }">
<div class="container-fluid">
<div ng-if="$root.isInternalPage" id="menu" class="hidden-print hidden-xs sidebar-blue sidebar-brand-primary">
<!-- sidebar content -->
</div>
<div id="content">
#RenderBody()
</div>
<div class="clearfix"></div>
<div ng-if="$root.isInternalPage" id="footer" class="hidden-print">
<!-- internal footer -->
</div>
<div ng-if="!$root.isInternalPage" id="footer" class="hidden-print">
<!-- external footer -->
</div>
</div>
</body>
Alternatively, you could assign your variable on $rootScope inside one of your controllers:
app.controller('ctrl', function($rootScope) {
$rootScope.isInternalPage = true;
});
What you can do in this case is create an AngularJS service: "You can use services to organize and share code across your app"
angular.module('core').service('GlobalVars', [ 'addDependenciesHere',
function(addDependenciesHere) {
this.isInternalPage = someVal ? true : false
}
]);
Then inject this into the constructor for each controller
angular.module('core').controller('HomeController', ['$scope', 'GlobalVars',
function ($scope, GlobalVars) {
$scope.globals = GlobalVars;
}
]);
Then in your view you can access this directly
<body ng-class="{menu-right-hidden: globals.isInternalPage }">

Resources