Boolean value is not set in view, but is in controller - angularjs

In my TaskHandler module I have a TaskOverviewController.ts:
$onInit() {
this.observeTaskUpdates();
}
private observeTaskUpdates(): void {
showTaskDetailView$
.subscribe(value => {
this.showTaskDetailView = value
console.log('value', value);
console.log('this.showTaskDetailView', this.showTaskDetailView)
});
}
This TaskOverviewController is linked to the TaskOverview.html:
<div id="grid-container" class="dashboard-container" ng-controller="TaskOverviewController as vm">
ShowTaskDetailView: {{vm.showTaskDetailView}}
<div id="main" ng-if="vm.showTaskDetailView">
show if true
</div>
</div>
On a different module CustomerService I have a ActivityTimelineItemComponent.ts:
public showDetailTaskView() {
showTaskDetailView$.onNext(true)
}
When I trigger the showDetailTaskView()function on the ActivityTimelineItem.html I get routed to another page,
<div >
<i
ng-click="vm.showDetailTaskView()"
ui-sref="dashboard.tasks.overview.details.information({ id: activity.task.id})"
>reply</i
>
</div>
And this is on the console:
TaskOverviewController.ts:31 value true
TaskOverviewController.ts:32 this.showTaskDetailView true
TaskOverviewController.ts:31 value true
TaskOverviewController.ts:32 this.showTaskDetailView true
But the #main element is not visible in the DOM. And the ShowTaskDetailView: {{vm.ShowTaskDetailView}} is also not showing the controllers set value.
I've tried wrapping the .subscribe in the TaskOverviewController in a timeout, but that didn't work either. So why is my controller showing that the showTaskDetailView value is true but my DOM isn't showing the ng-if="vm.ShowTaskDetailView"

Related

Show and Hide a <div> inside an ng-repeat with 'dirPagination' (AngularJS)

In this very site I've seen this question many times, but none of them works for me because of two things: I need the div to toggle when clicking the "options" button, but also to hide when clicking outside of the div; and I need it to work with dirPagination.
I saw this answer, it works fine but with one problem I just can't solve: it shows ALL the hidden divs at the same time, instead of only showing the one I clicked.
Here's my code:
<body ng-controller="MainCtrl">
<!-- pagination -->
<dir-pagination-controls
max-size="7"
direction-links="true"
boundary-links="true">
</dir-pagination-controls>
<ul>
<li dir-paginate="report in reports | itemsPerPage:4">
Options
<h3>{{report.Title}}</h3>
<div class="options" ng-show="dp">
<p>These are some options</p>
</div>
</li>
</ul>
</body>
And the JS to show the options:
//options div
$scope.showOptions = function(event){
if($scope.dp === false || $scope.dp === undefined) {
$scope.dp = true;
event.stopPropagation();
} else {
$scope.dp = false;
}
};
window.onclick = function(){
if($scope.dp){
$scope.dp = false;
$scope.$apply();
}
};
I've made a Plunker in case you wanna se it in action: my Plunker link
Can somebody help me with this issue? :(
Add a new boolean property on your reports array , for example show
var reports = [
{
"ID":1,
"Title":"Report 1",
"Show":false
},
{
"ID":2,
"Title":"Report 2",
"Show":false
}
]
Apply the property in to ng-show and also pass the current report scope object in to showOptions method to write the logic for hide and show.
<li dir-paginate="report in reports | itemsPerPage:4">
Options
<h3>{{report.Title}}</h3>
<div class="options" ng-show="report.Show">
<p>These are some options</p>
</div>
</li>
$scope.showOptions = function(event,report){
report.Show=!report.Show;
/*you can iterate through each reports and change show to false if the clicked report id not equal to report id , Angular JS will automatically update the scope in to the view*/
reports.forEach(function(item, index) {
if (item.ID !== report.ID) {
item.Show = false;
}
});
if($scope.dp === false || $scope.dp === undefined) {
$scope.dp = true;
event.stopPropagation();
} else {
$scope.dp = false;
}
};
https://next.plnkr.co/edit/hnhWMrgR3GMtVcES
You need to use a separate variable for each div you want to show. You could add the dp attribute to the report. There is no need to loop over the reports to hide them. You can just keep track of the currently visible report and hide it when another one is toggled.
Here is the relevant HTML:
<li dir-paginate="report in reports | itemsPerPage:4">
Options
<h3>{{report.Title}}</h3>
<div class="options" ng-show="report.dp">
<p>These are some options</p>
</div>
</li>
and JavaScript
var visibleReport;
$scope.showOptions = function(event, report){
if (report == visibleReport) {
report.dp = !report.dp;
}
else {
if (visibleReport) visibleReport.dp = false;
report.dp = true;
}
visibleReport = report
event.stopPropagation();
};
window.onclick = function(){
if (visibleReport) visibleReport.dp = false;
visibleReport = null;
$scope.$apply();
};
Here is a working plunker https://next.plnkr.co/edit/sWLxBGlF8D22nvYp?preview

How to bind checkboxes to Vuex store?

I have a component that contains some checkboxes. I need to be able to access which checkboxes are checked from other components in my Vue application, but I cannot for the life of me figure out (nor find online) how to properly connect the checkboxes to my Vuex store.
What is the right way to connect checkboxes within a component to a Vuex store, so that they act just as if the checkbox was connected to the components data via v-model?
Here is a starting point for what I'm trying to do (in a very very basic sense)
https://jsfiddle.net/9fpuctnL/
<div id="colour-selection">
<colour-checkboxes></colour-checkboxes>
</div>
<template id="colour-checkboxes-template">
<div class="colours">
<label>
<input type="checkbox" value="green" v-model="colours"> Green
</label>
<label>
<input type="checkbox" value="red" v-model="colours"> Red
</label>
<label>
<input type="checkbox" value="blue" v-model="colours"> Blue
</label>
<label>
<input type="checkbox" value="purple" v-model="colours"> Purple
</label>
<chosen-colours></chosen-colours>
</div>
</template>
<template id="chosen-colours-template">
<div class="selected-colours">
{{ colours }}
</div>
</template>
const store = new Vuex.Store({
state: {
colours: []
}
});
Vue.component('colour-checkboxes', {
template: "#colour-checkboxes-template",
data: function() {
return {
colours: []
}
}
});
Vue.component('chosen-colours', {
template: "#chosen-colours-template",
computed: {
colours() {
return store.state.colours
}
}
});
const KeepTalkingSolver = new Vue({
el: "#colour-selection"
});
The aim is to get the colours that are selected in the colour-checkboxes component to output in the chosen-colours component, going through the Vuex store.
You can use computed property with getter as vuex getter and setter in computed property which will call a mutation for that state property to do this.
You can see an example of this here with two-way Computed Property:
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
I wanted to provide an answer that actually uses checkboxes.
There is one possible solution outlined here:
Vuex dynamic checkboxes binding
And a simpler solution can be achieved something like the following:
<div v-for="tenant in tenants"
v-bind:key="tenant.id"
class="form-group form-check">
<input type="checkbox"
class="form-check-input"
v-bind:id="tenant.id"
v-bind:value="tenant.name"
#change="updateSelectedTenants">
Key here is calling a method using on-change, it will pass an event to the method with all the details needed to make the change.
The #change function:
updateSelectedTenants(e) {
console.log('e', e.target)
console.log('e', e.target.value)
this.$store.dispatch('updateSelectedTenants', e.target)
}
Here I want the value, in this case will be the tenants name, but further inspection of the target also gives the 'id', and whether or not the checkbox is 'checked' or unchecked.
Over in the store, we can manipulate the 'selectedTenants' array:
updateSelectedTenants (context, tenant) {
if(tenant.checked) {
// Tenant checked, so we want to add this tenant to our list of 'selectedTenants'
context.commit('addSelectedTenant', { id: tenant.id, name: tenant.value })
} else {
// otherwise, remove the tenant from our list
context.commit('removeSelectedTenant', tenant.id)
}
}
Here are the actual mutators:
addSelectedTenant (state, tenant) {
this.state.selectedTenants.push(tenant)
},
removeSelectedTenant (state, id) {
this.state.selectedTenants = this.state.selectedTenants.filter(tenant => {
return tenant.id != id
})
The vuejs docs are great, but sometimes they can be a little light on with real world examples. I don't think it's possible to achieve the above using a computed value, with get(), set()... but I'd like to see a solution that can.
OK I have been challenged to show my solution. Here it is on jsfiddle
the html is:
<div id="app">
<label v-for="brother in ['Harpo','Groucho','Beppo']">
<input type='checkbox' v-model='theBrothers' v-bind:value='brother' />
{{ brother }}
</label>
<div>
You have checked: {{ theBrothers }}
</div>
</div>
and the js is:
const store = new Vuex.Store({
state: {
theBrothers: []
},
})
new Vue({
el: "#app",
store: store,
computed: {
theBrothers: {
set(val){this.$store.state.theBrothers = val},
get(){ return this.$store.state.theBrothers }
}
},
})
2021 - easy, readable, & taking advantage of the power of Vue/Vuex...
There are lots of complicated answers for a simple problem. Run the snippet below to see it in action.
Here is a working solution that solves all of the issues described below:
const store = new Vuex.Store({
state: {
names: ['Max'],
},
mutations: {
setNames(state, names) {
state.names = names;
}
}
});
new Vue({
el: '#app',
store,
computed: {
selectedNames: {
get: function() {
return this.$store.state.names;
},
set: function(val) {
console.log(val);
this.$store.commit('setNames', val);
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#3.6.2/dist/vuex.js"></script>
<div id="app">
<div>
<input type="checkbox" v-model="selectedNames" :value="'John'" id="checkbox-1" />
<label for="checkbox-1">Click me to add my value to the state</label>
<br />
<input type="checkbox" v-model="selectedNames" :value="'Max'" id="checkbox-2" />
<label for="checkbox-2">I am preselected since my value already exists in the state <code>names</code> array</label>
<div>State: <strong>{{ names }}</strong></div>
</div>
</div>
Long story short all you need to do is take a piece of state (names below), create a mutation (setNames below) to set it, and then bind the v-model to a computed (selectedNames below) that has a getter and setter, the getter gets the piece of state names, and the setter calls the mutation setNames.
In my opinion this is the cleanest solution to this problem because it follows the natural pattern of Vue/Vuex and how checkboxes are typically implemented.
Other answers in here attempt to mutate the state directly without mutations, while some other answers avoid using a v-model which presents issues with having a preselected value and requires much more code, and finally the accepted answer doesn't even show any HTML template code on how to implement it.
Use #change to update Vuex as needed:
HTML:
<input
v-for='item in items'
#change='update_checkboxes'
v-model='selected_checkboxes'
:value='item.id'
type='checkbox
/>
<label>{{item.name}}</label>
JS:
data: function(){
return {
selected_checkboxes: [] // or set initial state from Vuex with a prop passed in
}
},
methods: {
update_checkboxes: function(){
this.$store.commit('update_checkboxes', this.selected_checkboxes)
}
}
Based on the solution from #Saurabh - it is important to use actions and getters rather than directly accessing vuex state - this will ensure consistency throughout the application.
<p>Mega test: <input type="checkbox" v-model="mega" /></p>
computed: {
mega: {
get () {
return this.$store.getters.mega
},
set (value) {
this.$store.dispatch('updateMega', value)
}
}
}
const store = new Vuex.Store({
state: {
mega: false
},
getters: {
mega (state) {
return state.mega
}
},
mutations: {
updateMega (state, value) {
state.mega = value
}
},
actions: {
updateMega (context, value) {
context.commit('updateMega', value)
}
}
})
You shoud remove colours = [] in data.

Protractor isSelected giving false reading after re-enabling checkboxes

I have a list of checkboxes and at the top a all checkbox. When toggling the all checkbox, all checkboxes will get deselected or selected.
By default/initially, all checkbox is enabled with all checkboxes checked. So, I will deselect the all checkbox and all the checkboxes will uncheck. This passes no with no issues in protractor:
it('all checkbox is deselected', function() {
modelsAllCheckbox.click();
expect(modelsAllCheckbox.isSelected()).to.eventually.be.false;
});
it('all models should be deselected', function() {
ppvPercentages.modelChoices().then(function(modelChoices) {
modelChoices.forEach(function(modelChoice) {
expect(modelChoice.isSelected()).to.eventually.be.false;
});
});
});
this.modelChoices = function(rowNumber) {
return element.all(by.repeater('model in vehicleCheckboxes.models'));
}
Then I re-enable the all checkbox. I visually can see, in the browser, the all checkbox being checked in all the checkboxes successfully being checked/selected. Hoewever, in the test to assert they are all selected fails:
it('all button is re-enabled', function() {
modelsAllCheckbox.click();
expect(modelsAllCheckbox.isSelected()).to.eventually.be.true;
// give time for all models to set
browser.sleep(3000)
});
it('all models are selected', function() {
ppvPercentages.modelChoices().then(function(modelChoices) {
modelChoices.forEach(function(modelChoice) {
expect(modelChoice.isSelected()).to.eventually.be.true;
});
});
})
<div class="overflow-container">
<!-- all checkbox -->
<input type="checkbox"
ng-model="vehicleAllCheckbox.models"
ng-change="toggleAllModels(vehicleAllCheckbox, vehicleCheckboxes.models, vehicleCheckboxes.year)">All
<div ng-repeat="model in vehicleCheckboxes.models | orderBy: 'description' track by model.description">
<!-- the rest of the checkboxes -->
<input type="checkbox"
ng-change="modelCheckboxToggle()"
ng-model="model.checked" >
{{model.description}}
</div>
</div>
I see all the checkboxes are checked in the browser viusally. Why is modelChoice.isSelected() giving false instead of true up re-enabling the all checkbox?
The problem is in the way you are locating the checkboxes. Currently, you are targeting the parent div elements since you are using the by.repeater() locator:
<div ng-repeat="model in vehicleCheckboxes.models | orderBy: 'description' track by model.description">
Instead, point modelChoices to input elements (your checkboxes):
this.modelChoices = function(rowNumber) {
return element.all(by.repeater('model in vehicleCheckboxes.models')).all(by.model('model.checked'));
}
As a side note, I think you can improve the way you are expecting the checkboxes to be selected, by either using .each():
ppvPercentages.modelChoices().each(function (modelChoice) {
expect(modelChoice.isSelected()).to.eventually.be.true;
});
Or, by using .reduce():
var allSelected = ppvPercentages.modelChoices().reduce(function (acc, modelChoice) {
return modelChoice.isSelected().then(function (isSelected) {
return acc && isSelected;
});
}, true);
expect(allSelected).to.eventually.be.true;
Or, there are other ways as well.

How to change an object's property in AngularJS

I have a function that sets a property in an object to false if it's true and true if it's false. Yet every time the function runs the object doesn't get affected.
$scope.menuButtons = [{header: "beaded", isActive: false},
{header: "laced", isActive: false}
]
$scope.activeButton = function(isActive) {
isActive == true ? isActive = false : isActive = true;
};
Here's the HTML
<div ng-repeat="b in menuButtons">
<div ng-click="activeButton(b.isActive)" class="shop-navbar-button">{{b.header}}</div>
</div>
I'm using the isActive value to see if the button is active so I can use a different class. For some reason the isActive value in the $scope doesn't get affected.
Pass in b instead of b.isActive. The boolean is just being passed by value, so re-assigning the value will have no effect on b.isActive.
I think you can change from this:
<div ng-click="activeButton(b.isActive)" class="shop-navbar-button">{{b.header}}</div>
to this:
<div ng-click="b.isActive=!b.isActive" class="shop-navbar-button">{{b.header}}</div>
You will NO need the auxiliar function. It is cleaner and more angular way.

Toggle true/false through radio buttons in AngularJS (inside of ng-repeat)

Hej, I've got an "almost" working fiddle. I have a list of items and I want to change their value if their radiobutton is selected. Here's the code:
CodePen:
http://codepen.io/anon/pen/MyvQoP
Html:
<div ng-app="myapp" ng-controller="myController">
<div ng-repeat="food in foodList">
<span>{{food.name}}</span>
<input type="radio" ng-model="food.selected" name="radiofood" ng-value="true">
</div>
</div>
JS:
angular.module('myapp', []).controller("myController", myController)
function myController($scope) {
$scope.foodList = [
{
name: 'banana',
selected: 'false'
},
{
name: 'orange',
selected: 'false'
},
{
name: 'apple',
selected: 'false'
}
]
}
The problem:
A radiobutton once clicked, changes it's value to true but clicking another one does not change the previous one to false. So if you click each one of them, one by one, all of them will be true. I only want one to have the true value.
Thanks
--- Edit 2016-03-31 ---
I was looking for a solution without writing a custom fuction but it turns out this can't be done. I've marked #Ankit Pundhir answer as the best one but it wasn't exaclty what i was aiming for.
Add method to controller file:
$scope.selectFood = function(selectedFood){
angular.forEach($scope.foodList,function(food){
if(food != selectedFood){
food.selected = false;
}
})
};
and add ng-change="selectFood(food)" to radio button.
The most simple solution is to add ng-change event on the input and then write a function which takes selectedFood as param and do the following:
Iterates through foodList and changes every value to false
Toggle status of selectedFood (if true set false end vice versa)
Something like this:
$scope.toggleParam = function(selectedFood){
loopThroughAndSetToFalse();
findAndSetReverseValue(selectedFood);
}
function loopThroughAndSetToFalse(){
for(var i=0; i<$scope.foodList.length; i++){
$scope.foodList[i].selected = false;
}
}
function findAndSetReverseValue(selectedFood){
for(var i=0; i<$scope.foodList.length; i++){
if($scope.foodList[i].name === selectedFood.name){
$scope.foodList[i].selected = !(selectedFood.selected);
}
}
}
And your html now will look like this:
<div ng-app="myapp" ng-controller="myController">
<div ng-repeat="food in foodList">
<span>{{food.name}}</span>
<input type="radio" ng-model="food.selected" name="radiofood" ng-change="toggleParam(food)" ng-value="true">
</div>
<br><br>
{{foodList[0]}}<br>
{{foodList[1]}}<br>
{{foodList[2]}}
</div>

Resources