ng-show expression not updating after variable update - javascript

I have a controller which prints some data in a ul using a ng-repeat. Each li is divided in two parts:
<li>
<div>
<button type="button" class="some classes" ng-show="!field.edit.state" ng-click="field.edit.update(true)">Edit</button>
<button type="button" class="some classes" ng-show="field.edit.state" ng-click="field.edit.update(false)">Save</button>
{{field.title}}
</div>
<div ng-show="field.edit.state">
<input />
</div>
The idea is, when the "Edit" button is clicked the input field and the "Save" button are shown, while the "Edit" button itself is hidden. Clicking on the "Save" button goes on the other way around. Since I need to execute some functions on the start and the end of the editing the update also triggers some callbacks from my controller. Now, the callbacks are correcly triggered, the state is updated, but nothing changes on my page.
The update function code is pretty simple:
function registerEdit(callback){
var state = false;
var update = function(newState){
state = newState;
callback(newState)
$timeout(function(){
$scope.$apply()
})
}
return {
state : state,
update : update
}
}
Each field element is injected its own registerEdit property when the data are fetched from the server and injected in the $scope, so they are unique (I've checked that part, and it works). From what I know about Angular, after changing the $scope all I needed to do was to call the $apply function to have Angular change my view, but even as the state changes (I logged it) the display remains the same.

Related

AngularJS Material - Issues with ng-model and ng-change accessing the same object value

I'm using angular-material and I have an md-switch item whose model is being set based on the value in a JS object. The challenge I'm having is, when the user clicks the switch, I don't want to change the object value directly - instead, I want to execute a function that is in the object that changes the value for me.
In the code (or JSFiddle below) you can see that the toggle state never changes.
This is a trivial example, so it doesn't go into the reason why, but is something like this possible?
Here is sample code
HTML
<div class="inset switchdemoBasicUsage" ng-controller="SwitchDemoCtrl" ng-cloak="" ng-app="MyApp">
<md-switch ng-model="data.state" aria-label="Switch 2" class="md-primary" ng-change="data.change()">
{{ data.state }}
</md-switch>
</div>
JS
angular.module('MyApp',['ngMaterial', 'ngMessages', 'material.svgAssetsCache'])
.controller('SwitchDemoCtrl', function($scope) {
$scope.data = {
state: true,
change: function() {
this.state = !this.state;
}
};
});
Here is the link to the JSFiddle: http://jsfiddle.net/wx8s709z/
The problem is not that it never changes but you changes it two times.
First time it changes when you click and second time you change it by your function. So if you comment your code into call function you'll see that all works.
ng-change is event that indicates you that your value is changed and then you can call another function with it's value, using only $scope.data.state

Undo changes to model state when angular form is canceled

In an Angular 4 application, I have a template driven form with controls bound to values in my component. When the form input changes the object bound to the input changes immediately (two-way binding). When the cancel button is clicked, I want to undo the change to the bound object. This allows the user to change values then change their mind and cancel their changes.
https://plnkr.co/edit/RnnPwtHZY0qTN1H6er0z?p=preview
The plunker above has such a form with a bound field to read the hero.name
<h2>{{hero.name}} details!</h2>
An input bound to a hero object.
<form #myForm="ngForm" (ngSubmit)="save(myForm)">
<div class="form-group">
<label>name: </label>
<input name="heroName" [(ngModel)]="hero.name" placeholder="name" />
</div>
<button (click)="cancel(myForm)">Cancel</button>
<button type="submit">Save</button>
</form>
The cancel button calls the ngForm's resetForm() method.
cancel(myForm){
myForm.resetForm();
}
Repro steps
Change the hero name; Observe the h2 changes immediately proving that the bound object changed as well
Click cancel; Observe the name is cleared and the h2 changes because the hero.name is now null
I expected the cancel button to change the hero.name back to the original value. Is this how resetForm() is supposed to work? Is there a different way?
If you make a reset() method, where you set the default values, then you can call it whenever it's needed, as in ngOninit and reset button click:
ngOnInit(){
this.reset();
}
reset(){
this.hero = new Hero(1,'Plunker');
}
cancel(myForm){
this.reset();
}
DEMO
You can reunite cancel() and reset() by refactoring, but you may want keep it as is in case you add something else in cancel.
From the angular documentation, I guess the solution is to use a reactive form instead of a template driven form.
https://angular.io/guide/reactive-forms reads (emphasis :
In keeping with the reactive paradigm, the component preserves the
immutability of the data model, treating it as a pure source of
original values. Rather than update the data model directly, the
component extracts user changes and forwards them to an external
component or service, which does something with them (such as saving
them) and returns a new data model to the component that reflects the
updated model state.

How to modify a scope variable with ng-click in a button and reflect the change immediately in one of the buttons attributes

Scenario
I have a button (created via a directive) that looks like below (after being rendered via the directive):
<button class="btn btn-sm btn-primary" create-element="row" ng-click="setSettings([{span: 6}, {span: 6}], create);" settings="{}" data-dismiss="modal">One half block<button>
Note: The settings attribute of the button is bound to $scope.settings object.
The code in my controller is as follows:
$scope.settings = {}; // the settings object that I want to bind to the button
// function to be called if I'd want to set some data to the settings variable from the button
$scope.setSettings = function (setting, callback){
$scope.settings = setting;
callback();
};
$scope.create = function () {
$log.log($attrs.settings); // always reflects data set from previous click event
var settings = $attrs.settings;
var type = $attrs.createElement;
var element = DOMService(type, settings);
getTarget().append($compile(element)($scope));
};
Problem
I'd want to produce a two way binding via the ng-click directive so that any change in the variable settings is reflected in one of the buttons own attributes.
The first click on the button does change the $scope.settings object but the change isn't there in the button's settings attribute. So, upon every second click, the data that was set in the previous click is reflected in the attribute.
When the button is clicked, it calls the setSettings() function via which I'm passing an arbitrary settings object, and soon after, the callback is called. The problem is that when the settings object is set to some value with the click on the button, I cannot reflect the change occurring in it immediately in the view.
I know that a two way binding for instance on an input[type=text] is possible via ng-model directive. So I'm able to produce a two way binding as such:
<input class="input-sm form-control" ng-model="settings" placeholder="Title" />
and to reflect the change (in settings object), I can echo the object out in, say, another button in this way:
<button ng-click="create()" settings="settings" data-dismiss="modal">Create</button>
So, how do I achieve the same two-way binding through the ng-click directive. And if, the way I'm wanting to achieve this is wrong, then what is the appropriate one?

View only partially refreshed after $scope.$on()

In this AngularJS controller for a pre-filled form, fetching the data is triggered by an event using $broadcast and $on. However, when the data updates, only some of the corresponding fields get updated in the view.
$scope.currentHouse = {};
$scope.$on('houseInfoLoad', function(event, data) {
$scope.currentHouse = data.houseDetail; //does not refresh the view
console.log($scope.currentHouse); //output is correct
//the next three calls refresh the corresponding fields in the view
$scope.changeGarageRadioValue($scope.currentHouse.hasGarage);
utils.setSelection($scope.houseKeywords, $scope.currentHouse.keyword.id);
utils.setSelection($scope.houseTypes, $scope.currentHouse.type.id);
});
changeGarageRadioValue() essentially does what it says, and utils.setSelection(list, id) adds a selected = true property to the element of the list that has the id. This has the effect of setting the value of a select field (using isteven's multi-select plugin).
The view has a few text fields bound to properties of $scope.currentHouse, as well as these radio buttons and select fields.
The result is that the text fields sometimes do not get updated, however the select and radio buttons do.
Here's what I tried unsuccessfully:
Wrapping everything in a $timeout();
Calling $scope.$apply() after setting $scope.currentHouse (throws an error saying that we are already inside an $apply())
Changing the initial definition of $scope.currentHouse from {} to an object with each of the fields set to null.
Can anyone see what I am missing, or how to force trigger the refresh?
EDIT : with extracts from the view :
A text field:
<label>ADDITIONAL DESCRIPTION</label>
<div>
<input type="text" ng-model="currentHouse.additionalDescription" class="input-mandatory" value=""/>
</div>
A multi-select:
<label>TYPE</label>
<div>
<div directive-id="houseType" multi-select input-model="houseTypes" output-model="currentHouse.type" button-label="text" item-label="text" selection-mode="single" default-label="Select" tick-property="selected" ></div>
</div>
The radio buttons :
<div><label>HAS GARAGE</label></div>
<div>
<div id="radiobuttonNo" ng-click="changeGarageRadioValue(false);">
NO
</div>
<div id="radiobuttonYes" ng-click="changeGarageRadioValue(true);" class="active">
YES
</div>
</div>
EDIT #2 as per Alok Singh's suggestion:
I tried putting data.houseDetail into $scope.currentHouse.house instead of $scope.currentHouse directly, and changing the view accordingly. Still no results.
$scope.currentHouse = data.houseDetail
You cannot get the updated value of currentHouse because its a model.
So change the above code i.e
$scope.currentHouse.house = data.houseDetail

Angular: ng-show stale state

I'm using ng-show to show a div upon clicking a button.
The div I'm ng-show-ing contains a course schedule with dynamically updated courses as the user clicks around and adds courses.
But when this course schedule gets triggered to show, it is in a stale state. There are no courses appearing.
Any insight onto why all the added courses data is not actually showing? Is there something special that has to be done after ng-show to refresh the state of the data?
HTML:
<div id="schedule-div" ui-calendar="uiConfig.calendar" ng-model="eventSources" ng-show="scheduleMode" calendar="weekView"></div>
JS:
// Event handle for "Generate Schedule" button
$scope.getSchedules = function () {
scheduleFactory.getSchedules($rootScope.addedCourses).
success(function (data) {
var scheduleListing = angular.fromJson(data);
// Create closure with current scheduleListing
scheduleInstance = renderSchedule(scheduleListing);
scheduleInstance(0);
// $scope.events is the data used
// This log correctly, logs data
console.log($scope.events);
// Toggle the ng-show
$scope.scheduleMode = true;
}).
error(function() {
$window.alert("Schedules not found.");
});
};
As you can see this is all happening in a callback from the server (I don't think this should matter though). The console.log() shows the data correctly so the data does exist by the time the ng-show is toggled.
When the ng-show logic is completely removed the data is correctly updated in the schedule view. So the problem must lie somewhere in ng-show displaying a stale state.
Check your data source. ng-show doesn't care about the content. It just make the container display:none or display:block based on the condition.
Your datasource is not getting updated.
If you have any example than post.
after code
// Toggle the ng-show
$scope.scheduleMode = true;
add
$scope.apply()
You're using ng-model=eventSources to set your data in the schedule listing. It doesn't appear to be updated, as the variable eventSources isn't being used in your getSchedules() code. That controls what get's updated inside the div.
ng-show only shows or hides the div, doesn't control anything else inside of it.

Categories