Angular.js no scope in ng-include html template - angularjs

I am learning as I go with my first Angular project and have ran into an issue.
Goal: When an link is clicked in a given .html template placed in with ng-include, I want it to change the value of $scope.selectedLocation
The issue: The value of $scope.selectedLocation does not change.
I read that the ng-include creates a child scope, so in order to change the parent scope variable, you could place $parent in front of the value. I have tried this and it does not work.
Main index page:
<body ng-app="photoApp" id="bodyDiv" >
<div ng-controller="PhotoGallery">
<div>
<ng-switch on="selectedLocation" >
<div ng-switch-when="home" >
<div ng-include="'home.html'"></div>
</div>
<div ng-switch-when="loc1">
<div ng-include="'file1.html'"></div>
</div>
<div ng-switch-when="loc2">
<div ng-include="'file2.html'"></div>
</div>
</ng-switch>
</div>
</div>
</body>
home.html code:
<div class="container-fluid">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<a href="#" ng-click="selectedLocation='loc1'">
Location 1
</a>
</div>
<div class="col-lg-6 col-md-6 col-sm-6">
<a href="#" ng-click="selectedLocation='loc2'">
Location 2
</a>
</div>
</div>
</div>
photoApp.js code:
var photoApp= angular.module('photoApp', []);
westonPhotographyApp.controller('PhotoGallery', function($scope)
{
$scope.selectedLocation ="home";
}

The issue is that you're binding to a primitive in an inherited scope. To fix it you should pass an object:
westonPhotographyApp.controller('PhotoGallery', function($scope)
{
$scope.vm = {
selectedLocation: "home"
}
}
Html
<div class="container-fluid">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<a href="#" ng-click="vm.selectedLocation='loc1'">
Location 1
</a>
</div>
<div class="col-lg-6 col-md-6 col-sm-6">
<a href="#" ng-click="vm.selectedLocation='loc2'">
Location 2
</a>
</div>
</div>
ng-include creates a new scope that inherits the parent (controller) scope through the prototype chain. In javascript you can't replace the value of the shadowed inherited property. Passing an object works as you're changing a property on a pointer to the object.
https://github.com/angular/angular.js/wiki/Understanding-Scopes

You seem to have misnamed your app variable. Either name westonPhotographyApp photoApp or vice-versa:
var photoApp = angular.module('photoApp', []);
photoApp.controller('PhotoGallery', function($scope) {
$scope.selectedLocation ="home";
}
Anyways, you could improve on your design:
You could use controllerAs syntax. It names your controller and makes it accessible throughout descendant scopes:
Index:
<div ng-controller="PhotoGallery as pgCtrl">
<div>
<ng-switch on="selectedLocation" >
<div ng-switch-when="home" >
<div ng-include="'home.html'"></div>
...
Home:
<div class="container-fluid">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<a href="#" ng-click="pgCtrl.selectedLocation='loc1'">
Location 1
</a>
</div>
<div class="col-lg-6 col-md-6 col-sm-6">
<a href="#" ng-click="pgCtrl.selectedLocation='loc2'">
Location 2
</a>
</div>
</div>
</div>
With your controller:
photoApp.controller('PhotoGallery', function() {
var vm = this;
vm.selectedLocation ="home";
}

Related

Angular add class to body from inside view

I have an app where, when a thumbnail is clicked a modal will open and play the video connected to the thumbnail. What I need to do is add a class to the body tag when this modal is open so I can stop the body content scrolling behind the modal.
I am using an ng-click to activate an ng-show and ng-if. The problem is all of this is inside the scope of an ng-repeat which in turn is inside the view. I am new to Angular and any help would be appreciated.
<div class="videos" ng-controller="modalCtrl">
<ul>
<li class="clear-fix" ng-repeat="video in filtered = videos | filter:search | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">
<!-- THIS ACTIVATES THE MODAL WHEN CLICKED -->
<div ng-click="showModal = !showModal" class="item-thumbnail float-left pointer" back-img="{{video.thumbnail}}">
</div>
<div class="item-details-container float-left">
<div class="item-header">
<div ng-click="showModal = !showModal" class="item-title pointer">
<h3 class="underline">{{video.title}}</h3>
</div>
<div class="item-author pointer">
<span>by: <a class="underline" ui-sref="{{video.author_link}}">{{video.author}}</a></span>
</div>
</div>
<div class="item-description">
<p>{{video.description.trunc(150)}}</p>
</div>
</div>
<!-- THIS IS THE MODAL BACKGROUND COVER -->
<div ng-show="showModal" class="modal-cover"></div>
<!-- THIS IS THE MODAL -->
<div ng-show="showModal" class="modal-container">
<div ng-if="showModal" class="video-modal clear-fix">
<button class="btn-close" ng-click="$parent.showModal = false">
<span></span>
<span></span>
</button>
<div class="video-container float-left">
<youtube-video video-url="video.url"></youtube-video>
</div>
<div class="video-info-container float-right">
<div class="video-info">
<div class="item-header">
<div class="item-title">
<h3>{{video.title}}</h3>
</div>
<div class="item-author pointer">
<span>by: <a class="underline" ui-sref="{{video.author_link}}">{{video.author}}</a></span>
</div>
</div>
<div class="item-description">
<p>{{video.description}}</p>
</div>
</div>
<div class="voting-container clear-fix">
<div class="votingButton btn-solid pointer" ng-click="upVote(video); showDetails = !showDetails">
<i class="fa fa-thumbs-o-up" aria-hidden="true"></i>
</div>
<div class="bugButton btn-solid pointer">
<i class="fa fa-flag-o" aria-hidden="true"></i>
</div>
<div ng-show="showDetails = showDetails"><span>Thank you for voting.</span></div>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
Here's what you need to do :
You can use ng-class in your body tag like this
<body ng-class="{'is-modal-open': freeze}">
And you can write ng-click on your thumbnail like this
<div class="thumbnail" ng-click="is-modal-open=true"></div>
You could just make a div in your template like this:
<div class="fullscreen-modal">
//content here
</div>
And in your controller you could do this:
var elementResult = element[0].getElementsByClassName('fullscreen-modal');
angular.element(document.body).append(element);
Now your element is a direct child of <body>.
OR
Define the modal div outside of ng-repeat.
Then define a scope function
$scope.openModal() = function(contentOrVideoId)
{
//put logic here
$scope.showModal = true;
$scope.modalContent = contentOrVideoId;
}
And just call this function inside ng-repeat with ng-click="openModal('IdOrContent')"
You are looking for ngClass directive.
So if you would add this to the body element you want to disable scrolling on, it would give you what you are looking for:
<div ng-class="{'is-modal-open': showModal}">
In short, this will add the class is-modal-open when showModal validates to true
Just to clarify (Thanks #Ronnie), you need to make sure the <div> that contains what you call body is inside the scope of your controller (somewhere inside the div that has ng-controller). And you shouldn't put it directly on the <body> tag
[edit]
I don't like using $rootScope, but you could try this:
<body ng-class="{'is-modal-open': showModal}">
// Your div
<div ng-click="showHideModal()" class="item-title pointer">
// your controller
app.controller('modalCtrl', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.showHideModal = function() {
$rootScope.showModal = !$rootScope.showModal;
};
}]);

Angular Transclude issue

How can I call an array by i rather than by # during a repeated transclude? With my code below, the repeat builds my list with the proper number of items in the array, but the data returned for x number of rows is only object[0] as specified in the markup. I am unable to reference i like I could in the repeat.
Am I thinking about this wrong or am I just missing something?
I have a template for an accordion like this
#### accordion.html ####
<ion-content scroll="false">
<ion-list>
<ion-item class="item-stable alertHeader" ng-click="vm.toggleGroup()" ng-class="{active: vm.isGroupActive()}">
<div class="row">
<div class="alertHeaderIcon col-xs-1">
<i class="icon" ng-class="{'icon-watchList':vm.tag == 'Tag1', 'icon-topPerformers':vm.tag == 'Tag2'}"></i>
</div>
<div class="alertHeaderLabel col-xs-9">
<span class="labelName">{{vm.tag}}</span>
</div>
<div class="alertHeaderWarning col-xs-1 pull-right">
<span class="label label-pill label-default">7</span>
</div>
<div class="alertHeaderCaret col-xs-1 pull-right">
<i class="icon" ng-class="vm.isGroupActive() ? 'ion-ios-arrow-down' : 'ion-ios-arrow-forward'"></i>
</div>
</div>
</ion-item>
<ion-item class="item-accordion" ng-repeat="i in vm.data" ng-show="vm.isGroupActive()">
<div ng-transclude></div>
</ion-item>
</ion-list>
</ion-content>
I then place this into my markup as followed:
#### alerts.html ####
<ca-accordion class="accordianTest" tag="'Tag1'" data="vm.itemsA">
<div class="row">
<div class="home-alert-icon col col-10">
<i class="icon" ng-class="{'icon-watchList':vm.itemsA[0].wlName == 'labelA', 'icon-topPerformers':vm.itemsA[0].wlName == 'labelB'}"></i>
</div>
<div class="home-alert-title col col-70">
{{vm.itemsA[0].wlName}}
</div>
<div class="home-alert-retailers">
{{vm.itemsA[0].wlRetailers}}
</div>
<div class="home-alert-metric col col-20">
{{vm.itemsA[0].wlPercent}}
</div>
</div>
</ca-accordion>
What you are missing is that the transcluded content, lives in a sibling scope to which the directive have been called, and not in the scope of your ng-repeat.
so your thats why i isn't reachable here:
<ca-accordion class="accordianTest" tag="'Tag1'" data="vm.itemsA">
<div class="row">
<div class="home-alert-icon col col-10">
<i class="icon" ng-class="{'icon-watchList':vm.itemsA[0].wlName == 'labelA', 'icon-topPerformers':vm.itemsA[0].wlName == 'labelB'}"></i>
</div>
<div class="home-alert-title col col-70">
{{vm.itemsA[0].wlName}}
</div>
<div class="home-alert-retailers">
{{vm.itemsA[0].wlRetailers}}
</div>
<div class="home-alert-metric col col-20">
{{vm.itemsA[0].wlPercent}}
</div>
</div>
</ca-accordion>
and only vm.itemsA is reachable.
So a possible solution for that is either to move the whole ng-repeat outside, to the transcluded content,
Or write your own directive, that will transclude with scope, meaning that the transcluded content will live inside the scope of your ng-repeat.
/**
#desc a copy of the ng-transclude implementation, but makes the transcluded content scope
to be in the same scope of where the transcludeWithScope was used (ngTransclude behaivor
bound it to the parent scope)
*/
function transcludeWithScopeDirective() {
return {
restrict: 'EAC',
bindToController: true,
link($scope, $element, $attrs, controller, $transclude) {
function ngTranscludeCloneAttachFn(clone) {
if (clone.length) {
$element.empty();
$element.append(clone);
}
}
// If there is no slot name defined or the slot name is not optional
// then transclude the slot
var slotName = $attrs.transcludeWithScope || $attrs.ngTranscludeSlot;
$transclude($scope, ngTranscludeCloneAttachFn, null, slotName);
},
};
}
angular
.module('transcludeWithScope', [])
.directive('transcludeWithScope', transcludeWithScopeDirective);
and then update your accordion.html to use the transcludeWithScope instead of ng-transclude:
<ion-item class="item-accordion" ng-repeat="i in vm.data" ng-show="vm.isGroupActive()">
<div transclude-with-scope></div>
</ion-item>
and your usage:
now we can access i, as our transcluded content lives at the scope of our ng-repeat.
<ca-accordion class="accordianTest" tag="'Tag1'" data="vm.itemsA">
<div class="row">
<div class="home-alert-icon col col-10">
<i class="icon" ng-class="{'icon-watchList': i.wlName == 'labelA', 'icon-topPerformers': i.wlName == 'labelB'}"></i>
</div>
<div class="home-alert-title col col-70">
{{i.wlName}}
</div>
<div class="home-alert-retailers">
{{i.wlRetailers}}
</div>
<div class="home-alert-metric col col-20">
{{i.wlPercent}}
</div>
</div>
</ca-accordion>

Need to call ng-switch by url or $location

I am trying to use a url to call a tab element. For example, a url like: localhost:9000/widget&view=map will land on the page with the map tab selected and shown the map tab body.
<div class="search-result-tab" ng-init="selectTab='list'">
<ul class="nav tab-header">
<li ng-class="{active: selectTab=='list'}" ng-click="selectTab='list'; changedTab()">
<a href={{listTabURL}}>List</a>
</li>
<li ng-class="{active: selectTab=='map'}" ng-click="selectTab='map'; changedTab()">
<a href={{mapTabURL}}>Map</a>
</li>
</ul>
<div class="tab-body" ng-switch on="selectTab">
<div class="tab-content" ng-switch-when="list">
<div ng-include="'list.html'"></div>
</div>
<div class="tab-content" ng-switch-when="map">
<div ng-include="'map.html'"></div>
</div>
</div>
</div>
Other urls are:
list: localhost:9000/widget&view=list
map: localhost:9000/widget&view=map
Similar question here. One suggestion is to inject the $location service and switch on $location.path, might be worth a try if you are not going to try ui-router (which you really should look into!)
function Ctrl($scope, $location) {
$scope.pagename = function() { return $location.path(); };
};
<div id="header">
<div ng-switch on="pagename()">
<div ng-switch-when="/home">Welcome!</div>
<div ng-switch-when="/product-list">Our products</div>
<div ng-switch-when="/contact">Contact us</div>
</div>
</div>

How to access parent controller data inside a loop

I have a problem with angularJS.
I have a controller that execute a loop. For every element in the list I display all the informations.
Inside every loop, I added another controller (I guess is not a best practice already).
From the inner controller i want to access data from the outer controller.
Now, I don't mind doing this by assigning metadata on the html code, or by javascript code in the controller.
Here what I have done:
<div ng-controller="PostListCtrl">
<div ng-repeat="e in posts" class="col-lg-3 col-md-4 col-sm-12 col-xs-12">
<div class="galleryElement effect6">
<div class="innerGalleryElement">
<div>
<img class="img-responsive" src="" /><!--{{e.Post.cover}}-->
</div>
<div class="lowerBarHolder">
<div class="float-left avatar">
<div class="shadow">
<img src="{{e.Post.author_avatar}} " />
</div>
</div>
<div class="float-left description">
{{e.Post.title}} <br />
{{e.Post.author}}
</div>
<div ng-controller="PostHeart" class="likeHolder" ng-init="isActive={{e.Post.isActive}}">
<button ng-click="toggleActive($index)"
ng-class="{true: 'loveButtonActive', false: 'loveButton'}[isActive]"
class="">❤</button>
</div>
</div>
</div>
</div>
</div>
This code: ng-init="isActive={{e.Post.isActive}} doesn't work as supposed.
I also tried to assign with data-myData="{{e.Post.isActive}} but at runtime when i tried to access myData from the controller it does access the string {{e.Post.isActive}} instead of accessing the replacement of that placeholder.
Any idea?
Create a directive:
yourModule.directive('postHeart', function() {
return {
restrict: 'E',
scope: {
'e': '=',
'index': '='
},
controller: function($scope) {
var isActive = $scope.e.Post.isActive;
$scope.btnClass = isActive ? 'loveButtonActive' : 'loveButton';
$scope.toggleActive = function(index) {
var postId = $scope.e.Post.id; // access data from the parent controller
// ...
};
},
template: "<button ng-click=\"toggleActive(index)\" ng-class=\"btnClass\">❤</button>"
};
});
Then use it in template:
<div ng-controller="PostListCtrl">
<div ng-repeat="e in posts" class="col-lg-3 col-md-4 col-sm-12 col-xs-12">
<div class="galleryElement effect6">
<div class="innerGalleryElement">
<div>
<img class="img-responsive" src="" /><!--{{e.Post.cover}}-->
</div>
<div class="lowerBarHolder">
<div class="float-left avatar">
<div class="shadow">
<img src="{{e.Post.author_avatar}} " />
</div>
</div>
<div class="float-left description">
{{e.Post.title}} <br />
{{e.Post.author}}
</div>
<post-heart e="e" index="$index" class="likeHolder"></post-heart>
</div>
</div>
</div>
</div>

AngularJS - How to generate random value for each ng-repeat iteration

I am trying to create random span sized divs(.childBox) of twitter bootstrap using AngularJS.
<div ng-controller="HomeCtrl">
<div class="motherBox" ng-repeat="n in news">
<div class="childBox" class="col-md-{{boxSpan}} box">
<a href="#" class="thumbnail">
<img src="{{holderLink}}" height="200px" alt="100x100">
<p class="tBlock"> {{n.title}} </p>
</a>
</div>
</div>
</div>
controller('HomeCtrl', ['$scope', '$http', function($scope,$http) {
$http.get('news/abc.json').success(function(data) {
$scope.news = data;
});
$scope.holderSize = 150;
$scope.holderLink = 'http://placehold.it/'+$scope.holderSize+'x'+$scope.holderSize;
$scope.boxSpan = getRandomSpan();
function getRandomSpan(){
return Math.floor((Math.random()*6)+1);
};
}])
I want to create different integer value for boxSpan for each .childBox div but all .childBox have same boxSpan value. Although everytime i refresh page boxSpan creates random value.
How can i generate different/random value for each ng-repeat iteration?
Just call add getRandomSpan() function to your scope and call it in your template:
$scope.getRandomSpan = function(){
return Math.floor((Math.random()*6)+1);
}
<div ng-controller="HomeCtrl">
<div class="motherBox" ng-repeat="n in news">
<div class="childBox" class="col-md-{{getRandomSpan()}} box">
<a href="#" class="thumbnail">
<img src="{{holderLink}}" height="200px" alt="100x100">
<p class="tBlock"> {{n.title}} </p>
</a>
</div>
</div>
</div>
As an alternative to the accepted answer, since you're likely to reuse this function, you can turn it into a filter for convenience:
angular.module('myApp').filter('randomize', function() {
return function(input, scope) {
if (input!=null && input!=undefined && input > 1) {
return Math.floor((Math.random()*input)+1);
}
}
});
Then, you can define the max and use it anywhere on your app:
<div ng-controller="HomeCtrl">
<div class="motherBox" ng-repeat="n in news">
<div class="childBox" class="col-md-{{6 | randomize}} box">
<a href="#" class="thumbnail">
<img src="{{holderLink}}" height="200px" alt="100x100">
<p class="tBlock"> {{n.title}} </p>
</a>
</div>
</div>
</div>
Improvisation of the accepted answer to prevent digest overflow:
var rand = 1;
$scope.initRand = function(){
rand = Math.floor((Math.random()*6)+1)
}
$scope.getRandomSpan = function(){
return rand;
}
<div ng-controller="HomeCtrl">
<div class="motherBox" ng-repeat="n in news">
<div class="childBox" ng-init="initRand()" class="col-md-{{getRandomSpan()}} box">
<a href="#" class="thumbnail">
<img src="{{holderLink}}" height="200px" alt="100x100">
<p class="tBlock"> {{n.title}} </p>
</a>
</div>
</div>
</div>

Resources