Isolating re-used controller scopes with AngularJS - angularjs

I'm currently working on a project where I use a primary navigation to open tabs which lazy-load templates.
EDIT: to clarify, the number of tabs can vary on a user basis, so I cannot do this hard coded.
My way to try and accomplish this is by using a Tabs-controller with headers and the tab-items themselves:
<section class="tabs" ng-controller="Tabs">
<div class="tabs__header">
<div class="tabs__header__item is-hidden" ng-repeat="tab in tabs" data-item="{{tab.item}}" ng-if="!tab.header">
<a class="tabs__header__item__text" ng-href="#/{{tab.item}}" ng-click="tabClicked($event)" data-item="{{tab.item}}">{{tab.name}}</a>
x
</div>
</div>
<div class="tabs__container">
<div class="tabs__container__tab is-hidden" ng-repeat="tab in tabs" id="tab-{{ tab.item }}" ng-if="!tab.header" ng-controller="Tab" ng-include src="template.url"></div>
</div>
</section>
And the controllers would look like this:
Tabs.js:
function Tabs(scope, root) {
root.$on('/general/page/initdata', function(e, data) {
//set default elements
scope.tabs = data['nav-primary'];
//open dashboard
});
root.$on('/nav/primary/open', function(e, itemName) {
root.$emit('/tabs/activate', itemName);
_setActiveTab(itemName);
});
scope.tabClicked = function(e) {
e.preventDefault();
var name = e.target.getAttribute('data-item');
_setActiveTab(name);
};
function _setActiveTab(itemName) {
var collection = document.querySelectorAll('.tabs__header__item.js-is-active, .tabs__container__tab.js-is-active');
angular.element(collection).removeClass('js-is-active');
collection = document.querySelectorAll('#tab-'+itemName+', .tabs__header__item[data-item="'+itemName+'"]');
angular.element(collection).addClass('js-is-active').removeClass('is-hidden');
}
};
Tabs.$inject = ['$scope', '$rootScope'];
Tab.js
function Tab(scope, root) {
var loaded = false;
root.$on('/tabs/activate', function(e, itemName){
if(scope.tab.item === itemName && !loaded) {
loaded = true;
scope.template = {url: '/frontend/templates/'+itemName+'.html'};
}
});
};
Tab.$inject = ['$scope', '$rootScope'];
Now, the problem is that template.url is modified for each tab whereas I only want to modify it for one tab. It turns out scope is shared between the various Tab-instances. How can I circumvent this?

It turns out the problem wasn't with AngularJS. I had simply made an error with my js-is-active class, causing the wrong tab to be displayed.

Related

Ng-repeat - "Are you sure to delete ?" from a modal

I'm retrieving a list of objects (item) from a Django API.
my_app.factory('list_of_items', function($resource) {
return $resource(
'/api/petdata/') });
Then I display everything in a html page within a ng-repeat:
<div ng-controller="ModalDemoCtrl">
<div ng-repeat="item in items | filter:{display:'1'} | orderBy: 'item_name'">
<div class="box box-widget widget-user">
{{ item.pet_name }}{% endverbatim %}
<button type="button" class="btn btn-box-tool" ng-click="askDelete(item)" href="#"><i class="fa fa-times"></i></button>
</div>
<div>
Everything's fine so far.
Then I want the user to be able to delete one of the item by clicking on the button from the html page.
What means deleting here :
1. Update the API database by changing the property "display:1" to "display:0".
2. Remove the item from the ng-repeat.
I want to make a "Are you sure" modal to confirm the delete process.
This is the askDelete function.
angular.module('djangular-demo').controller('Ctrl_List_Of_Pets', function($scope, $http, $window,$filter,list_of_pets,pet_by_id,$uibModal) {
$scope.items = list_of_items.query()
$scope.askDelete = function (idx,item,size,parentSelector) {
// console.log("PET",$scope.pet_to_be_undisplayed);
var parentElem = parentSelector ?
angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;
var modalInstance = $uibModal.open({
animation: true,
ariaLabelledBy: 'LOL',
ariaDescribedBy: 'modal-body',
templateUrl: "myModalContent.html",
controller: function($scope) {
$scope.ok = function() {
modalInstance.close();
};
$scope.cancel = function() {
modalInstance.dismiss('cancel');
};
},
size: size,
appendTo: parentElem,
resolve: {
}
});
modalInstance.result.then(function() {
reallyDelete(item);
});
};
var reallyDelete = function(item) {
$scope.entry = items_by_id.get({ id: item.id }, function() {
// $scope.entry is fetched from server and is an instance of Entry
$scope.entry.display = 0;
$scope.entry.$update({id: $scope.entry.id},function() {
//updated in the backend
});
});
$scope.items = window._.remove($scope.items, function(elem) {
return elem != item;
});
};
});
What works :
Updating the DB works with a PUT request (code hasn't been provided).
What doesn't work :
Removing the item from the ng-repeat never works. Or it throws me an error like here because it doesn't know window._.remove or it doesn't know $scope.items. It depends from what I try. Or the modal close and there is no update of the ng-repeat list, no refresh and every items remain whereas the PUT request to update worked.
I read every article on scope inheritance and I think I didn't make any mistake here but I'm might be wrong. I've been struggling for too long so I post here !
Would you suggest anything to make it work ?
Thank you for your rime.
First:
$scope.askDelete = function (idx,item,size,parentSelector) receives the item index, the item, size, and parent selector... and you are calling ng-click="askDelete(item)"
I assume you are attempting to pass the item, but in askDelete you are receiving as first parameter the index (maybe you should do ng-click="askDelete($index)"?)
Second:
In reallyDelete why are you removing the items array like this:
$scope.items = window._.remove($scope.items, function(elem) {
return elem != item;
});
?
IMHO, it would be a much cleaner code if we just do:
$scope.items.splice(idx, 1) //<- idx would be the idx of the entry in the items
You may want to take a look at Splice

How to trigger the click event inside ng-repeat of angularjs?

I am doing the ng-repeat for showing the project list in my app. on click on the individual project I need to show the list of surveys inside the project. But when the page get loaded for first time I want to trigger the click event of first project which is default.
Below is the html code for that list project.
projectlist.html
<ul class="sidebar-nav">
<li class="sidebar-brand" >
<a>
Projects
</a>
</li>
<li ng-repeat="obj in allProjects track by $id(obj)">
<a ui-sref="survey.surveyList({id: obj.ProjectID})" ng-click="getProjSurveys(obj.ProjectID)" data-id="{{obj.ProjectID}}">{{obj.ProjectName}}<span class="projectsetting"><img src="./images/settings.png"/></span></a>
</li>
</ul>
ctrl.js
require('../styles/survey/survey.less');
angular.module("adminsuite").controller("surveyController",['getAllSurveyService','$timeout','AuthenticationService', '$scope','$rootScope', '$state', '$stateParams', function(getAllSurveyService,$timeout, AuthenticationService,$scope,$rootScope,$state,$stateParams){
$rootScope.header = "survey";
$scope.hoverIn = function(){
this.hoverEdit = true;
};
$scope.hoverOut = function(){
this.hoverEdit = false;
};
$rootScope.surveySelected = false;
console.log($('.checked').length);
console.log($('.checkbox').length);
if($state.current.name != 'login'){
$rootScope.bgGround = 'bgBlank';
$rootScope.footerbgGround = 'bgFooter';
}
$scope.supported = false;
$scope.wapLink = 'http://apidev.1pt.mobi/i/interview?uid=188A80CF0D61CA60D2F5_495F23E0FD4B9120D1E7&surveyid=20903';
$scope.success = function ($event) {
$scope.supported = true;
console.log($(".copied"));
};
$scope.fail = function (err) {
console.error('Error!', err);
};
getAllSurveyService.surveyList().then(
function( data ) {
$scope.allProjects = data;
$scope.allSurveys = data[0].Surveys;
if($scope.allSurveys.ErrorMessage){
AuthenticationService.ClearCredentials();
}
}
);
$scope.getReportDetails = function(){
return $http.get('http://localhost:8395/api/UserSurvey/GetAllSurveys', {
headers: { 'Content-Type': 'application/json','SessionID':$rootScope.token}
})
};
$scope.getProjSurveys = function(projId){
var i;
for(i in $scope.allProjects){
if(projId == $scope.allProjects[i].ProjectID){
$scope.allSurveys = $scope.allProjects[i].Surveys;
}
}
angular.element(document.querySelectorAll(".surveymodule")).removeClass('toggled');
};
$scope.toggleClass = function($event){
angular.element($event.target).toggleClass("checked");
$rootScope.selectedItems = angular.element(document.querySelectorAll(".checked")).length;
angular.element($event.target).parent().toggleClass("active");
if(angular.element(document.querySelectorAll(".checked")).length < 1){
$rootScope.global.showNewHeader= false;
};
};
}]);
on-click of the projects I am able to show data in the ui-view but for the first time on load of the page unable to trigger click event for the first project. I tried with the above code inside the controller using angular.element and trigger('click'), but it is not working for me. Please help.
This is actually angularjs issue. JQuery's trigger click event not getting triggered in angular's ng-repeat.
Check this : https://github.com/angular/angular.js/issues/3470
The problem is you are using ui-sref along with ng-click. Here the state is changing before the function completes and either reloading the controller (if both states has same controller) or changing the controller. Hence ng-click finction is not running.
remove ui-sref and use $state.go() to change the state inside ng-click
Sample code here:
HTML
<ul class="sidebar-nav">
<li class="sidebar-brand" >
<a>
Projects
</a>
</li>
<li ng-repeat="obj in allProjects track by $id(obj)">
<a ng-click="getProjSurveys(obj.ProjectID)" data-id="{{obj.ProjectID}}">{{obj.ProjectName}}<span class="projectsetting"><img src="./images/settings.png"/></span></a>
</li>
</ul>
JS:
$scope.getProjSurveys(objId){
// your codes here
$state.go('survey.surveyList({'id': objId})')
}
Instead of trying to interact with the DOM you should do something like this:
getAllSurveyService.surveyList().then(
function( data ) {
$scope.allProjects = data;
$scope.allSurveys = data[0].Surveys;
$scope.getProjSurveys($scope.allProjects[0].ProjectID)
if($scope.allSurveys.ErrorMessage){
AuthenticationService.ClearCredentials();
// $state.go('login');
}
});

html data get from $http GET is not showing properly in Angular js..?

I have defined a controller like this :
app.controller("home", function ($scope, $http, $common) {
$http({
method: "GET",
url: '/posts/loadData'
}).then(function (response) {
//console.clear()
if (typeof response.data.posts != 'undefined') {
console.log(response.data.posts);
$scope.posts = $common.arrangePosts(response.data.posts);
}
});
})
and a service to arrange data :
app.service('$common', function ($timeout, $sce, $httpParamSerializerJQLike) {
var that = this;
this.arrangePosts = function (rawPosts) {
var posts = [];
$.each(rawPosts, function (key, value) {
posts.push({
postId: value.postId,
postLink: '/post/' + that.cleanString(value.title) + '/' + value.postId,
title: value.title,
summary: $sce.trustAsHtml(value.summary)
});
});
return posts;
}
});
using values in html like this :
<div class="widget fullwidth post-single">
<h4 class="widget-title">Latest</h4>
<div class="widget-content">
<ul>
<li ng-repeat="post in posts">
<h4 class="list-title">{{post.title}}</h4>
{{post.summary}}
</li>
</ul>
</div>
</div>
Data coming from server in JSON form :
Object { postId="4", title="asdf", summary="<p>asdf</p>"}
but all the html tags are printing on my page as it is (like a text) in summary.
In many SO posts people suggested to use $sce.trustAsHtml but its not working for me. Please suggest anyway to solve my problem.
Any help will be appreciated..!!
have you tried this?
<div ng-bind-html='post.summary'></div>
You could solve this over a directive. Did you know, that you can use JQuery Lite inside AngularJS to manipulate the DOM?
Here a quick example:
angular.module("PostsDirective",[])
.directive("posts", function($sce){
return {
link: function($scope, $element, $attrs){
//the HTML you want to show
var post = "<div>hello world</div>";
var posts = [post,post,post,post];
//iterating through the list (_.each is a function from underscore.js)
_.each(posts, function(element){
//if you want to save the trusted html string in a var you can do this with getTrustedHtml
//see the docs
var safeHtml = $sce.getTrustedHtml($sce.trustAsHtml(element));
//and here you can use JQuery Lite. It appends the html string to the DOM
//$element refers to the directive element in the DOM
$element.append(safeHtml);
});
}
};
});
And the html
<posts></posts>
This also pretty nice for the readability for your HTML code. And you can use it everywhere on your page.
BTW:
As i can see, you get the HTML elements directly from a REST-Service. Why don't you get just the data and insert it into the ng-repeat? If you transfer all the HTML you get a pretty high overhead if you have loads of data.

angularjs: scope value doesn't get updated in view

there are buttons in detail.html file:
<div ng-controller="test.views.detail">
<div data-ng-repeat="item in details" scroll>
<button ng-click="showDetails(item)">The details</button>
in detail.js file
angular.module('test')
.controller('test.views.detail', function($scope) {
$scope.detailsClicked = false;
$scope.showDetails = function(item){
$scope.detailsClicked = true;
}....
in formDetail.html code:
<div ng-controller="test.views.detail">
{{detailsClicked}}
<div ng-if="detailsClicked">...
Initially it shows false for detailsClicked, when I click on button it goes to showDetails function but value of $scope.detailsClicked never get updated! It is straight forward not sure why it doesn't work:(
This is because you're using the same controller at two places and expecting the scope object to be the same which it is not. Everytime you call ng-controller in your markup a new scope object will be created. If you want them to be based off the same data then use a service.
Here is an example
app.controller('test.views.detail', function($scope, detailsClicked) {
$scope.detailsClicked = detailsClicked;
$scope.showDetails = function(item){
$scope.detailsClicked.isClicked = true;
}
});
Create a factory/service which will retain the data, make sure the data is a
app.factory('detailsClicked', function(){
var data = {
isClicked: false
}
return data;
});

Angularjs toggle image onclick

I'm trying to toggle a button image when a user clicks it. I prefer to use angularjs instead of jquery if possible. Right now I have a working version which toggles the image when clicked, the only problem is it changes ALL the images on click. How do I reduce the scope or pass in the src attribute for the img element?
<div ng-repeat="merchant in merchants">
<div class="followrow">
<a ng-click="toggleImage()"><img id="followbutton" ng-src="{{followBtnImgUrl}}" /></a>
</div>
</div>
app.controller('FollowCtrl', function CouponCtrl($scope) {
$scope.followBtnImgUrl = '/img1'
$scope.toggleImage = function () {
if ($scope.followBtnImgUrl === '/img1.jpg') {
$scope.followBtnImgUrl = baseUrl + '/img2.jpg';
} else {
$scope.followBtnImgUrl = 'img1.jpg';
}
}
});
Can I pass in the img src attribute the function like toggleImage(this.img.src) or similar?
<div ng-repeat="merchant in merchants">
<div class="followrow">
<a ng-click="toggleImage(merchant)"><img id="followbutton" ng-src="{{merchant.imgUrl}}" />
</a>
</div>
</div>
.
app.controller('FollowCtrl', function CouponCtrl($scope) {
$scope.followBtnImgUrl = '/sth.jpg'
$scope.merchants = [{imgUrl: "img1.jpg", name:"sdf"},
{imgUrl: "img2.jpg", name: "dfsd"}];
$scope.toggleImage = function(merchant) {
if(merchant.imgUrl === $scope.followBtnImgUrl) {
merchant.imgUrl = merchant.$backupUrl;
} else {
merchant.$backupUrl = merchant.imgUrl;
merchant.imgUrl = $scope.followBtnImgUrl;
}
};
});
What you want is a new scope for each followrow. As your code stands, there's only one scope that all of the followrows are referencing.
A simple answer is to create a new controller that you attach to each followrow:
<div class="followrow" ng-controller="ImageToggleCtrl">...</div>
And then move the image toggling logic to that new controller:
app.controller('ImageToggleCtrl', function($scope) {
$scope.followBtnImgUrl = '/img1';
$scope.toggleImage = function() { /* the toggling logic */ };
});
Now, a new scope will be instantiated for each row, and the images will toggle independently.
I just added two clickable images:
<div ng-app="FormApp" ng-controller="myController" max-width="1150px;" width="1150px;" >
<input ng-show="ShowDown" type="image" style="width:250px; height:40px;" src="~/Content/Images/contactShow.png" ng-click="ShowHide()"/>
<input ng-show="ShowUp" type="image" style="width:250px; height:40px;" src="~/Content/Images/contactHide.png" ng-click="ShowHide()" />
</div>
They toggle eachothers visibility. At page load one is visible, one is not, and both clickable images call the same function:
<script type="text/javascript">
var app = angular.module('FormApp', [])
app.controller('myController', function ($scope) {
$scope.ShowDown = true;
$scope.ShowUp = false;
$scope.ShowHide = function () {
$scope.ShowDown = $scope.ShowDown ? false : true;
$scope.ShowUp = $scope.ShowUp ? false : true;
}
});
</script>

Resources