md-checkbox does not work with ng-click - angularjs

I want to save position each time when I change checkbox:
<h1 class="md-display-2">Simple TODO ng app</h1>
<h2 class="md-display-3"></h2>
<div ng-include src="'todo/add.html'"></div>
<div>
<div layout="row">
<div flex class="md-title">Scope</div>
<div flex="10" class="md-title">Till date</div>
<div flex="10" class="md-title">Is reached?</div>
<div flex="10" class="md-title">
<span ng-click="todoctrl.show_add()" class="material-icons controls">add</span>
</div>
</div>
<div layout="row" ng-repeat="todo in todoctrl.todos track by $index">
<div flex ng-class="{true:'striked', false:'simple'}[todo.reached]">{{todo.name}}</div>
<div flex="10">
{{todo.tillDate | date:'dd/MM/yyyy'}}
</div>
<div flex="10">
<md-checkbox ng-model="todo.reached" aria-label="Is reached" ng-click="todoctrl.changeState(todo.name)"></md-checkbox>
</div>
<div flex="10">
<span ng-click="todoctrl.deleteScope(todo.name)"
class="material-icons controls">clear</span>
</div>
</div>
</div>
In this case controller is touched (I tried with to debug with console log), but the checkbox value is not changed before page reload. After reload the value is checkbox is presented as expected.
If I remove ng-click="todoctrl.changeState(todo.name)" then checkbox is working good, but no info is sent to controller.
This is my service:
(function() {
'use strict';
angular.module('app').service('ToDoService', ToDoService);
ToDoService.$inject = ['JsonService'];
function ToDoService(JsonService) {
return {
deleteScope : deleteScope,
submitScope : submitScope,
changeState : changeState,
getData : getData
}
function getData() {
var todos = JsonService.getData();
return todos;
}
function deleteScope(arr, scope) {
arr.splice(findElementByScope(arr, scope), 1);
JsonService.setData(arr);
}
function submitScope(arr, scope, tillDate) {
var newTodo = {};
newTodo.name = scope;
newTodo.reached = false;
newTodo.tillDate = tillDate;
arr.push(newTodo);
JsonService.setData(arr);
}
function changeState(arr, scope) {
console.log("Service change state for scope: " + scope);
var todo = {};
var index = findElementByScope(arr, scope);
todo = arr[index];
todo.reached = !todo.reached;
JsonService.setData(arr);
}
function findElementByScope(arr, scope) {
for (var i = arr.length; i--;) {
if (arr[i].name == scope) {
return i;
}
}
return -1;
}
}
})();
And this is the Controller:
(function() {
'use strict';
angular.module('app').controller('ToDoController', ToDoController);
function ToDoController(ToDoService) {
var vm = this;
vm.show_form = false;
vm.todos = ToDoService.getData();
vm.scope = '';
vm.show_add = show_add;
vm.submitScope = submitScope;
vm.deleteScope = deleteScope;
vm.changeState = changeState;
function show_add() {
console.log("Controller show add");
vm.show_form = true;
}
function submitScope() {
ToDoService.submitScope(vm.todos, vm.scope, vm.tillDate);
vm.show_form = false;
vm.scope = '';
}
function deleteScope(scope) {
ToDoService.deleteScope(vm.todos, scope);
}
function changeState(scope) {
ToDoService.changeState(vm.todos, scope);
}
}
})();

Use ng-change instead of ng-click
<md-checkbox ng-model="todo.reached" aria-label="Is reached" ng-change="todoctrl.changeState(todo.name, todo.reached)"></md-checkbox>
ng-change trigger after value change in model

Related

Save the value of a service in a variable

I must fill a combo to be able to select the value that the user wanted I have managed to do this with the data stored in an array, the next step is to consult a service and use the return values
<html>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="text">ObjectType:</label>
<input type="text" ng-model="type" ng-keyup="complete(type)" class="form-control" placeholder="Search"/>
<ul class="list-group">
li class="list-group-item" ng-repeat="data in filterType" ng-click="fillTextbox(data)">{{data}}</li>
</ul>
</div>
</div>
</html>
var app = angular.module("Trace", []);
app.controller('TraceControlles', function ($scope, $http) {
//$scope.typeList = ["integration", "none"];
$scope.typeList = [];
$scope.loadTypes = function () {
$http.get("/api/Trace/GetObjectType")
.then(function (responce) {
$scope.typeList = responce.data;
}, function errorCallback(responce) {
});
}
$scope.complete = function (string) {
var output = [];
angular.forEach($scope.typeList, function (type) {
if (type.toLowerCase().indexOf(string.toLowerCase()) >= 0) {
output.push(type);
}
});
$scope.filterType = output;
}
$scope.fillTextbox = function (string) {
$scope.type = string;
$scope.filterType = null;
}
});
146/5000
When running the web api the service return values are not loaded, the browser does not mark any syntax error or anything like that.

Nested ng-repeat gives error after 10 levels deep: 10 $digest() iterations reached

Getting this error: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
I have created a reddit style nested comment system using AngularJS. But after 10 levels deep i get a very ugly error on my console that looks like this:
This happens after exactly 10 levels deep:
The nested comments are directives that look like this:
commentCont.tpl.html:
<div class="comment-cont">
<div
class="upvote-arrow"
ng-show="!collapsed"
ng-click="vote()"
ng-class="{'active' : node.votedByMe}"
>
<div class="upvote-arrow-top"></div>
<div class="upvote-arrow-bottom"></div>
</div>
<div class="wrapper">
<div class="info">
<span class="collapse" ng-click="collapsed = !collapsed">[ {{collapsed ? '+' : '–'}} ]</span>
<span ng-class="{'collapsed-style' : collapsed}">
<a ui-sref="main.profile({id: node.userId})">{{node.username}}</a>
<span>{{node.upvotes}} point{{node.upvotes != 1 ? 's' : ''}}</span>
<span mg-moment-auto-update="node.createdTime"></span>
<span ng-show="collapsed">({{node.children.length}} child{{node.children.length != 1 ? 'ren' : ''}})</span>
</span>
</div>
<div class="text" ng-bind-html="node.comment | autolink | nl2br" ng-show="!collapsed"></div>
<div class="reply" ng-show="!collapsed">
<span ng-click="formHidden = !formHidden">reply</span>
</div>
</div>
<div class="input-area" ng-show="!collapsed">
<form ng-show="!formHidden" name="form" autocomplete="off" novalidate ng-submit="submitted = true; submit(formData, form)">
<div class="form-group comment-input-feedback-branch has-error" ng-show="form.comment.$invalid && form.comment.$dirty">
<div ng-messages="form.comment.$error" ng-if="form.comment.$dirty">
<div class="input-feedback" ng-message="required">Comment text is required.</div>
<div class="input-feedback" ng-message="maxlength">Comment text cannot exceed 2500 characters.</div>
</div>
</div>
<div
class="form-group"
ng-class="{ 'has-error' : form.comment.$invalid && form.comment.$dirty, 'has-success' : form.comment.$valid }"
>
<textarea
name="comment"
class="textarea comment-cont-textarea"
ng-model="formData.comment"
required
ng-maxlength="2500"
textarea-autoresize
></textarea>
</div>
<div class="form-group">
<mg-button-loading
mgbl-condition="awaitingResponse"
mgbl-text="Save"
mgbl-loading-text="Saving..."
mgbl-class="btn-blue btn-small"
mgbl-disabled="!form.$valid"
></mg-button-loading>
<mg-button-loading
mgbl-text="Cancel"
mgbl-class="btn-white btn-small"
ng-click="formHidden=true;"
></mg-button-loading>
</div>
</form>
</div>
<div ng-show="!collapsed">
<div ng-repeat="node in node.children" ng-include="'commentTree'"></div>
</div>
</div>
commentCont.directive.js:
(function () {
'use strict';
angular
.module('app')
.directive('commentCont', commentCont);
/* #ngInject */
function commentCont ($http, user, $timeout) {
return {
restrict: 'E',
replace: true,
scope: {
node: '='
},
templateUrl: 'app/post/commentCont.tpl.html',
link: function (scope, element, attrs) {
var textarea = element.querySelector('.comment-cont-textarea');
var voteOK = true;
var action = '';
var userInfo = user.get();
scope.formHidden = true; // Do not ng-init="" inside an ng-repeat.
scope.collapsed = false; // Do not ng-init="" inside an ng-repeat.
// Autofocus textarea when reply link is clicked.
scope.$watch('formHidden', function(newValue, oldValue) {
if (newValue !== true) {
$timeout(function() {
textarea[0].focus();
});
}
});
scope.submit = function (formData, form) {
if (form.$valid) {
scope.awaitingResponse = true;
formData.parentId = scope.node.id;
formData.postId = scope.node.postId;
formData.type = 4;
formData.fromUsername = userInfo.username;
formData.toId = scope.node.userId;
formData.fromImage = userInfo.thumbnail36x36.split('/img/')[1];
// console.log(formData);
$http.post('/api/comment', formData)
.then(function (response) {
scope.awaitingResponse = false;
if (response.data.success) {
if (response.data.rateLimit) {
alert(rateLimitMessage);
return false;
}
// id and createdTime is sent with response.data.comment.
var c = response.data.comment;
var newCommentNode = {
id: c.id,
userId: userInfo.id,
username: userInfo.username,
parentId: formData.parentId,
comment: formData.comment,
upvotes: 0,
createdTime: c.createdTime,
votedByMe: false,
children: [],
postId: scope.node.postId
};
// console.log('user', user.get());
// console.log('scope.node', scope.node);
// console.log('response', response);
// console.log('newCommentNode', newCommentNode);
formData.comment = '';
form.comment.$setPristine();
scope.formHidden = true;
scope.node.children.unshift(newCommentNode);
}
});
}
};
scope.vote = function() {
if (voteOK) {
voteOK = false;
if (!scope.node.votedByMe) {
scope.node.votedByMe = true;
action = 'add';
} else {
scope.node.votedByMe = false;
action = 'remove';
}
var data = {
commentId: scope.node.id,
action: action
};
$http.post('/api/comment/vote', data)
.then(function (response) {
// console.log(response.data);
voteOK = true;
if (action === 'add') {
scope.node.upvotes++;
} else {
scope.node.upvotes--;
}
});
}
};
}
};
}
}());
The tree is being called like this:
<script type="text/ng-template" id="commentTree">
<comment-cont
node="node"
></comment-cont>
</script>
<div ng-repeat="node in tree[0].children" ng-include="'commentTree'"></div>
How can I have more than 10 levels of nested ng-repeat without getting an error like this?
The default implementation of $digest() has limit of 10 iterations . If the scope is still dirty after 10 iterations error is thrown from $digest().
The below stated is one way of configuring the limit of digest iterations to 20.
var app = angular.module('plunker', [], function($rootScopeProvider) {
$rootScopeProvider.digestTtl(20); // 20 being the limit of iterations.
});
But you should look in to stabilizing your model rather than configuring the limit of iterations.
I was dealing with a similar issue. end up with the following directive:
(function () {
'use strict';
angular
.module('app')
.directive('deferDom', ['$compile', ($compile) => {
return {
restrict: 'A',
compile: (tElement) => {
// Find, remove, and compile the li.node element.
let $el = tElement.find( "li.node" );
let transclude = $compile($el);
$el.remove();
return function($scope){
// Append li.node to the list.
$scope.$applyAsync(()=>{
tElement.append(transclude($scope));
});
}
},
};
}]);
})();

Angular scope.watch not working in directive

I have a angular directive that watches a list, and then creates a custom select when the list is edited. I have this working on one page, but it refuses to work on another. I dont know why - but it looks like the watch is not catching when the list has changed.
I have reproduced the error here - http://codepen.io/jagdipa/pen/Ramjez - can someone please help?!
angular.module('MyApp',['ngMaterial', 'ngMessages', 'material.svgAssetsCache'])
.controller('AppCtrl', function($scope) {
$scope.clearValue = function() {
$scope.myModel = undefined;
};
$scope.save = function() {
alert('Form was valid!');
};
var Titles =[{"Title":"Mr"},{"Title":"Master"},{"Title":"Miss"},{"Title":"Mrs"}];
$scope.titles = Titles;
});
module MyApp.Directives {
interface TcSelectListScope extends ng.IScope {
sourceList: any[];
sourceListKey: string;
sourceListValue: string;
}
export class TcSelectListController {
static $inject = [];
constructor() {
}
}
export class TcSelectList {
public restrict = "A";
public require = ["ngModel", "^form", '^^mdInputContainer', "select"];
public controller = TcSelectListController;
public controllerAs = 'selectController';
public scope = {
sourceList: "="
}
constructor(private $compile: ng.ICompileService) {
}
public compile(tElement, tAttributes) {
var $compile = this.$compile;
var _cacheService = this.cacheService;
return function postLink(scope, element, attrs, controller) {
var ngModelCtrl = controller[0];
var mdInputContainerCtrl = controller[2];
var selectCtrl = controller[3];
console.log(selectCtrl);
/*if (scope.sourceList == undefined)
{
scope.sourceList = [];
}*/
scope.$watch(scope.sourceList, scope.onModelChanged, true);
//scope.$watchCollection(scope.sourceList, scope.onModelChanged);
scope.onModelChanged = () => {
console.log('tc select list directive 2');
console.log(scope.sourceList);
if (attrs.sourceListKey == undefined) {
throw ("The source-list-key needs to be defined for tc-select-list");
}
if (attrs.sourceListValue == undefined) {
throw ("The source-list-value needs to be defined for tc-select-list");
}
var html = undefined;
html = buildSelect();
html = markSelected(html);
element.append(html);
}
element.on("click", function () {
mdInputContainerCtrl.setHasValue(true);
});
element.bind("blur", function () {
if (ngModelCtrl.$viewValue == undefined) {
mdInputContainerCtrl.setHasValue(false);
}
});
element.bind("change", function () {
mdInputContainerCtrl.setHasValue(true);
});
function buildSelect() {
var html = ``;
angular.forEach(scope.sourceList, (val, index) => {
var itemKey = scope.sourceList[index][attrs.sourceListKey];
var itemValue = scope.sourceList[index][attrs.sourceListValue];
var selected = ``;
html += `<option label="` + itemValue +
`" value="` + itemKey +
`" ` + selected +
` >` + itemValue + ` < /option>`;
});
return html;
}
function markSelected(html) {
if (ngModelCtrl.$modelValue != undefined && ngModelCtrl.$modelValue != null) {
html = html.replace(`value="` + ngModelCtrl.$modelValue + `"`,
`value="` + ngModelCtrl.$modelValue + `" selected`)
}
return html
}
}
}
static factory() {
var directive = ($compile, cacheService) => new TcSelectList($compile, cacheService);
directive.$inject = ["$compile"];
return directive;
}
}
angular.module("MyApp").directive("tcSelectList", TcSelectList.factory());
}
<div ng-controller="AppCtrl" layout="column" layout-align="center center" style="min-height: 300px;" ng-cloak="" class="selectdemoValidations" ng-app="MyApp">
<form name="myForm">
<p>Note that invalid styling only applies if invalid and dirty</p>
<md-input-container class="md-block">
<label>Favorite Number</label>
<md-select name="myModel" ng-model="myModel" required="">
<md-option value="1">One 1</md-option>
<md-option value="2">Two</md-option>
</md-select>
<div class="errors" ng-messages="myForm.myModel.$error" ng-if="myForm.$dirty">
<div ng-message="required">Required</div>
</div>
</md-input-container>
<div layout="row">
<md-button ng-click="clearValue()" ng-disabled="!myModel" style="margin-right: 20px;">Clear</md-button>
<md-button ng-click="save()" ng-disabled="myForm.$invalid" class="md-primary" layout="" layout-align="center end">Save</md-button>
</div>
<md-input-container class="no-errors">
<label>{{translations["Title"]}}</label>
<div class="tc-select">
<select name="title"
tc-select-list
ng-model="myModel.Title"
source-list="titles"
source-list-key="Title"
source-list-value="Title"></select>
</div>
</md-input-container>
<br/>
{{titles}}
<br/>
Title = {{myModel.Title}}
</forms>
</div>
I found the answer. Turns out the following line doesnt work
scope.$watch(scope.sourceList, (newVal) => {
Changing it to the following sorted the problem out
scope.$watch('sourceList', (newVal) => {

Refresh $scope variables from a service

What is the proper method of refreshing data that is retrieved from a service? I am retrieving a test array from my dataService that I'd like to be automatically updated in my view when my save() method is called. I have commented the line that is supposed to update my $scope variable but yet nothing changes. Should I be wrapping it in $apply()?
The data that is placed into the array that dataService.getActivities() returns is from a cookie (which may or not be relevant).
app.controller("activityCtrl", ["$scope", "dataService", function ($scope, dataService) {
$scope.newActivity = "";
$scope.activities = dataService.getActivities();
$scope.save = function (activity) {
try{
if (activity != "") {
dataService.saveActivity(activity);
$scope.newActivity = "";
$scope.activities = dataService.getActivities(); //HERE
}
} catch (e) {
alert(e);
}
}
}
Here is my view, the array lives in a ng-repeat
<div class="col-xs-12" ng-controller="activityCtrl">
<div class="row text-center">
<form novalidate>
<input type="text" placeholder="Activity name" ng-model="newActivity" />
<input type="submit" value="Add activity" ng-click="save(newActivity)" />
</form>
</div>
<div class="row" ng-controller="activityCtrl">
<div class="col-xs-2"> </div>
<div class="col-xs-6 text-left">
<div class="row" ng-repeat="activity in activities">
<div class="btn btn-default" ng-class="{ active : activity.active }" ng-click="activate(activity)">{{ activity.name }}</div>
</div>
</div>
<div class="col-xs-4 text-left">
sdfas
</div>
</div>
</div>
dataService code:
app.service("dataService", function () {
this.getActivities = function () {
if (docCookies.hasItem("activities")) {
var activities = JSON.parse(docCookies.getItem("activities"));
if (activities.constructor != Array) {
activities = [activities];
}
activities.sort(
function (a, b) {
if (a.name > b.name) { return 1; }
if (b.name < a.name) { return -1; }
return 0;
});
return activities;
}
return [];
}
this.saveActivity = function (activity) {
if (!docCookies.hasItem("activities")) {
docCookies.setItem("activities", [JSON.stringify({ "name": activity, "active": true })], Infinity);
} else {
var activities = this.getActivities();
for (var i = 0; i < activities.length; i++) {
if (activities[i].name == activity) {
throw "Activity already exists";
}
}
activities.push({ "name": activity, "active": false });
docCookies.setItem("activities", JSON.stringify(activities), Infinity);
}
};
});
Zac, your answer should work for you. As another option, I like to broadcast an event whenever my service is updated. In the last line of your saveActivity() function in your service, try broadcasting an event on the rootScope. Inject the $rootScope into your service. Add the following to your save method:
$rootScope.$broadcast('activitiesUpdated');
Then in your controller, inject the $rootScope and add an event handler:
$rootScope.$on('activitiesUpdated', function(event, args){
//Update $scope variable here
});
The answer was to look at the cookie value with a call to $watch instead of the method that returns an object. Added this in my activityCtrl
$scope.$watch(function () {
return docCookies.getItem("activities");
}, function (oldVal, newVal) {
$scope.activities = dataService.getActivities();
});

why the ng-class not changed?

Examples of the problem:
http://jsfiddle.net/paloalto/DTXC2/
HTML:
<div ng-app="app">
<div id="wrapper" ng-controller="AppController" ng-class="showChatPanel">
<div id="tabBar" class="ui vertical icon menu inverted" ng-controller="TabBarController">
<a class="item switchChatBtn" data-tab="showChatWraper">Open Chat Panel</a>
</div>
<div id="chatWraper" class="ui segment">Chat Panel Opend!!</div>
</div>
</div>
Javascript:
angular.module('app', ['app.controllers']);
var controllers = angular.module('app.controllers', []);
controllers.controller('AppController', function AppController($scope, $log, $http) {
$scope.showChatPanel = '';
$scope.$on("switchChatPanel", function (event, msg) {
console.log(msg);
$scope.showChatPanel = msg;
console.log($scope.showChatPanel);
// $scope.$broadcast("switchChatPanel-done", msg);
});
$scope.$watch('showChatPanel', function(newVal, oldVal) {
if(newVal){
console.log('yeah! It is a newVal !!!');
} else {
console.log('still oldVal ');
}
});
});
controllers.controller('TabBarController', function TabBarController($scope, $log, $http) {
var tabBarItem =$('#tabBar > .item');
tabBarItem.click(function(){
var tabClass = $(this).data('tab');
console.log(tabClass);
$scope.$emit("switchChatPanel", tabClass);
});
});
CSS:
#chatWraper {
display:none;
}
.showChatWraper #chatWraper{
display:block;
}
=====
I finally solved it using jQuery, but I still wonder why angular not success.
controllers.controller('TabBarController',function TabBarController ($scope,$log,$http) {
var tabBarItem =$('#tabBar > .item');
var chatPanelOpen = false;
tabBarItem.click(function(){
var tabClass = $(this).data('tab');
if(!chatPanelOpen){
$('#wrapper').addClass(tabClass);
chatPanelOpen = true;
} else{
$('#wrapper').removeClass(tabClass);
chatPanelOpen = false;
}
})
})
https://gist.github.com/naoyeye/7695067
========
UPDATE
http://jsfiddle.net/paloalto/DTXC2/17/
You shouldn't be doing DOM manipulation like that in the controller. The correct way to do this is like this
<div ng-controller="TabBarController">
<div ng-click="toggleChatPanel()" ng-class="{tabClass: isChatPanelOpen}">
</div>
controllers.controller('TabBarController', function ($scope) {
$scope.isChatPanelOpen = false;
$scope.toggleChatPanel = function () {
$scope.isChatPanelOpen = !$scope.isChatPanelOpen;
};
});

Resources