Mat-Checkbox binding model can it be optional or null? - javascript

Hi all I am new to angular, which I created a dynamic list of checkboxes. Because there are times which I will get the selected value from API which I need to bind to the checkboxes.
Thus I will have the code like below
<div *ngFor="let b of result?.category">
<mat-checkbox (change)="onChangeCheckbox($event)" [value]="b.RefAccess?.accessId"
[checked]="b.RefAccess?.selected" [(ngModel)]="b.BoRefAccess?.selected"
[ngModelOptions]="{standalone: true}">
{{b.BoRefAccess?.accessDesc}}
</mat-checkbox>
</div>
Error "Empty expressions are not allowed ng , Parser Error: The '?.' operator cannot be used in the assignment at column 25 in [b.BoRefAccess?.selected=$event] in..."
My main problem now is the ngModel binding can not be optional but I need it to be optional because there are times API will give me null list item. So I not sure how to handle it.
I also came across another method which keep track on the onChange event but it will only consist checkboxes that being onChanged, those are selected is not included.

The elvis operator can't be used for assignment. You should declare it in a more explicit way.
Instead of
[(ngModel)]="b.BoRefAccess?.selected"
Use
[ngModel]="b.BoRefAccess?.selected" (ngModelChange)="b.BoRefAccess.selected=$event"

So, I'm thinking maybe you could create another variable to bind with ngModel. In the function onChangeCheckbox you created, you could update the value of b.RefAccess.selected if it's different from null.

Related

How v-model make a property of object reactive?

Link to my project:
https://codesandbox.io/s/v-model-3j96f
As of my link above, the is a file named HelloWorld.vue inside the "components" folder:
inputvalue.bbbb is a reactive data which is defined in data option, but
It's weird that inputvalue.cccc will become reactive after input with the v-model, but inputvalue.cccc will not reactive with #input.
In this question (Vue.js bind object properties), the first situation should not be possible.
Using v-model will automatically use $set to set the values on nested properties. This ensures this it works with array indices, as well as working for object properties that don't exist, as per your example.
If you're unfamiliar with $set it is documented here:
https://v2.vuejs.org/v2/api/#vm-set
The code for this part of v-model in Vue is here:
https://github.com/vuejs/vue/blob/399b53661b167e678e1c740ce788ff6699096734/src/compiler/directives/model.js#L44
In your example there are two inputs that use cccc. As you noticed, if you edit the input that uses v-model then everything works fine. However, if you use the :value/#input input first then it doesn't work, even if you subsequently use the v-model input. The behaviour is, somewhat oddly, determined by which of those two inputs you edit first.
The reason for that can be seen in the code for $set:
https://github.com/vuejs/vue/blob/399b53661b167e678e1c740ce788ff6699096734/src/core/observer/index.js#L212
The problem is that $set will only add a reactive property if the property doesn't already exist. So if you use the :value/#input input first it will create a non-reactive cccc property and once that is created it can't be made reactive, even if you use $set. It would have to be removed using delete before it could be re-added reactively.

Can I affect properties of an Angular component based on change of other properties in a non-template-driven fashion?

I'm developing a form with Angular 6 and using some standart components, including ng-select (I'd say that's a requirement). The form is supposed to have rather complicated logic (some field values affect other values or suggestions that ng-select should show). I have a rather general question (hoping that there's an approach which I haven't just found yet), but to stay more specific, let's consider the following example:
I have 2 dictionaries (let's call them categories and items), each item being a "child" of a certain category
I have to let user select a category and an item from each dictionary, for that I have 2 fields like
<ng-select
name="category"
[items]="formSuggestions.categories$ | async"
bindLabel="name"
[(ngModel)]="formFields.category"
></ng-select>
<ng-select
name="item"
[items]="formSuggestions.items$ | async"
bindLabel="name"
[(ngModel)]="formFields.item"
></ng-select>
(in fact, they are wrapped into custom components which I omit for simplicity)
(here formSuggestions.items$ and formSuggestions.categories$ are observables that are filled with suggestions on server response; each item is actually an object having id, name and parentId)
what I need is: when a category is selected, suggestions for items are limited to those which are children of that category; when an item is selected, the category is set automatically
My question is: is there a way in Angular to "subscribe" to changes of one property in model (formFields.item) and apply it to others (formFields.category, formSuggestions.categories$) or the only way to deal with this is to set Outputs like (change) of each field?
The problem with that approach is the actual form is more complicated, for instance:
there's another interface that should be shown in a modal window, where user can choose category (and same for item), so there's multiple points which change the props
item selection should affect another ng-select's suggestions (for another field) and pre-fill some crud interface with default stuff for that item
by the way, I have to show only 10 suggestions each time (suggestion dictionaries are quite long) and there's no "limit" option in ng-select, so I have to affect suggestion list based on field value
...
so I really wonder if I can go less template-driven. Any suggestions, at least for the 2 selections case?
(change) is listening to the classical input change event (not Angular specific). See also MDN-Link
For all [(ngModel)] bound elements, you could also use (ngModelChange) to listen to changes. Thats more Angular style. And it gets even more interesting when you create your own "input" components with the ControlValueAccessor.
The problem in your example is, that you use the subscribed suggestionCategories directly. You could (theoreticly) do a "map" in the observable stream and filter out the unwanted values. But this would only work for each emited event of the observable.
So in your case i fear you have to subscribe to the source, store the result in a component local variable. You also copy the data in a second variable that you use to show the values on the UI.
And whenever the user selects a category, you take the original stored data, filter it and assign the filtered result to your second-variable.
HTML
<ng-select
name="category"
[items]="formSuggestions.categories$ | async"
bindLabel="name"
[(ngModel)]="formFields.category"
(ngModelChange)="filterCategorySugestions($selectedValue)"
></ng-select>
In Typescript you would then use the filterCategorySugestions Method to filter the data and write it into your second variable (mentioned above).
by the way, when filtering, you could afterwards apply a mylist.splice(10) (standard Array method) to limit your results to the first 10. But perhaps you should ensure the order first. :-)
I hope it helps a bit.
warm regards
Jan

AngularJS: Binding once to an attribute

I am trying to bind once an object key/field to the value of an attribute (data-oldField) so that if its value changes via user input (angular x-editable tables) I can grab the html element via data-newField and get the value of data-oldField so that I can rename the field/key in the object. I tried using the native :: expression to bind once, but the value of data-oldField changes when a change to the field name is submitted so that the values of date-oldField and data-newField are equal afterwards which is precisely what I do not want.
I also tried using the angular-once library and adding the directives once once-attr-field='field' as per the api, but I got the same result.
<tr ng-repeat='(field, value) in user.data'>
<td>
<span editable-text='field' e-name='name' e-form='rowform' data-newField='{{ field }}' data-oldField='{{ ::field }}' e-required>
{{ field }}
</span>
</td>
...
</tr>
Edit:
Plunker
I was unable to get the values of the data-oldfield and data-newfield attributes to show on the view, but if you observe the values of the attributes using your brower's dev tools and press the "Rename Field" button, you can see the that the value of data-oldfield changes even though I'm using one time binding. Maybe I'm misunderstanding how the $watchers work for this kind of binding?
From the angular docs it appears that using :: will be updating the object to a new value but will not be updating to view:
An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).
I'm not quite sure I understand the question but one approach would be to
defined a function in your controller that would be called on ng-change that could perform your logic.
Another would be to $watch the model but watching can be expensive
UPDATE
I'm a bit confused what exactly you're trying to accomplish but here is a plunkr that has an ng-change where you can reference the new value and the old value of $scope.user using a copy of the object. You can also use the renameField function however you'd like

How to pass the single parameter to custom attribute directive?

I'm trying to make validation directive for elements generated by ng-repeat (validate common logic). And now my problem is: how to group this elements by this ng-repeat cycle?
I decided to set some unique name on my validation directive to differ elements of different ng-repeats.
So how cat I pass this name?
For example ng-model takes object, and I need just string.
To pass single value in attribute directive (like ng-model="myModel") we can use that fact that directive can read values of any neighbor attribute of the host element INCLUDE IT'S OWN. So if we do something like <div my-directive="someValue">...</div> we can read "someValue" in directive linking function via attributes array. Documentation example.

Editing a delimited string using multiple input fields in AngularJS

I am trying to do the following quite unsuccessfully so far.
I have an string that is semicolon separated. Say a list of emails, so
'email1#example.com;email2#example.com;email3#example.com'
What I am trying to accomplish is split this string (using split(';')) into an array of strings or array of objects (to aid binding). Each of the items I would like to bind to different input elements. After editing I want to read the concatenated value again to send to my backend.
Problem is that when editing one of the split inputs, the original item value is not update (which makes sense as I am guessing the individual items are copies of parts of the original), but I am wondering if there is a way to do something like that.
Note that I want this to go both ways, so watching the individual inputs and updating the original one manually, would just fire an infinite loop of updates.
I have tried a few different ways, including creating an items property get/set using Object.defineProperty to read and right to the string (set was never fired).
take a look at this plnker
You can construct a temporary array on each field update in order to do the string replacement of the old segment with the new value. In order to tackle the lost focus problem you will have to use the ngReapeat's track by $index. The internal array will not be recreated unless you add the separator to your original string.
Here is the complete solution on Plunker
Your main issue is your ng-model attribute on your repeated input element. I would start with making use of ng-repeat's $index variable to properly bind in ng-model. In your original Plunker 'name' is NOT a scope property you can bind to, so this should be changed to ng-model="names[$index]"
Here is a Plunker to reflect this. I made quite a few changes for clarity and to have a working example.
NOTE: You will find that when editing fields directly bound to a repeater, every change will fire a $digest and your repeated <input> elements will refresh. So the next issue to solve is regaining focus to the element you are editing after this happens. There are many solutions to this, however, this should be answered in a different question.
Although binding to a string primitive is discouraged, you could try ng-list.
<form name="graddiv" ng-controller="Ctrl">
List: <input name="namesInput" ng-list ng-model="vm.names"/>
<ul>
<input ng-repeat="name in vm.names track by $index" ng-model="name" ng-change="updateMe($index, name)"/>
</ul>
You'll need both track by $index and an ng-change handler because of the primitive string binding.
function Ctrl($scope) {
$scope.vm = {}; // objref so we can retain names ref binding
$scope.vm.names = ['Christian', 'Jason Miller', 'Judy Dobry', 'Bijal Shah', 'Duyun Chen', 'Marvin Plettner', 'Sio Cheang', 'Patrick McMahon', 'Chuen Wing Chan'];
$scope.updateMe = function($index, value){
// ng quirk - unfortunately we need to create a new array instance to get the formatters to run
// see http://stackoverflow.com/questions/15590140/ng-list-input-not-updating-when-adding-items-to-array
$scope.vm.names[$index] = value; // unfortunately, this will regenerate the input
$scope.vm.names = angular.copy($scope.vm.names); // create a new array instance to run the ng-list formatters
};
}
Here's your updated plunkr

Categories

Resources