I'm making a web app with Angular however I'm having trouble with the load button. So the user presses the load button, selects a save to load, and a new set of properties is loaded into forms that each constitute a step-item in a stepper. The forms array that loads all of the forms is 'reset' with this function:
private resetPropForms(): void {
this.propForms = [];
}
This is the function that receives as an argument a properties event and then sets the forms array:
onPropertiesEmitted(properties: Property[]): void {
this.resetPropForms();
this.resetDividedProperties();
this.resetUndividedProperties();
this.setDividedProperties( properties );
this.setForms();
this.setUndividedProperties( properties );
}
And this is what my template looks like:
<se-stepper
*ngIf="undividedProperties"
[linearMode]="false"
[(activeStepIndex)]="activeStepIndex"
>
<se-step-item
*ngFor="let form of propForms"
>
<app-properties-form
[propertiesForm]="form"
(change)="onPropertiesChanged($event)"
>
</app-properties-form>
</se-step-item>
</se-stepper>
Lastly, the view is updated but only after I go to the next step and come back. And if I save the properties then the correct value is sent to the backend even though the value displayed in the view is incorrect/not updated. Any ideas why this is happening. I tried using trackBy with a unique identifier for each form ( using a random number ) but that didn't work. I tried using ChangesRef and detectChanges() and that didn't work. When I leave out the *ngFor and just display the first form in the array it updates properly so that leads me to believe that this problem has something to do with the *ngFor.
edit:I'm pretty sure setTimeout() placed in onPropertiesEmitted() worked but I forgot where I put it to make it work and it seemed like a not-so-great solution to the problem
I Think the problem is that the array is the same and change detect won't detect changes, try this
private resetPropForms(): void {
//this.propForms = [];
//array.slice() returns new array
this.propForms = this.propForms.slice(0,0);
}
You could also reset the form before assigning new properties ( form.reset() )
Related
So basically I am playing with Svelte trying to spin up a quick app, details of the app aren't important but basically it hosts a bunch of embedded sites. see example here & for replicability:
https://svelte.dev/repl/6f3484554ef8489b9a5960487a0a1f95?version=3.47.0
My problem is that when I add a new url & title to the sites list, the {#each} block that creates the embedded views doesn't update to reflect the new state of the list, even though the list is clearly updating in the console output. Is it something to do with scope or is it a Svelte issue of not triggering reactivity on prop reassignments from components?
Update: some sites don't allow embedding so use https://wikipedia.org as a safe one for testing.
if you replace a hard-coded url in the sites list with wiki address it should work fine. i basically want a new window to pop up as the {#each} block creates a new SiteView component
There are several things wrong with your code, the first being that you do not propagate the changes made to the sites array back to the main application, you should use bind: to keep the two arrays in sync.
<InputBar bind:sites {site} />
The second is that you are modifying an object when adding a new site and then adding that object to the array, this will always be the same object so if you change it the previously added sites will also change. You can solve this by spreading the new object into the array instead:
function add() { sites = sites.concat({...site}); console.log(sites)}
// or alternatively
function add() { sites = [...sites, {...site}]; console.log(sites); }
That said, the application is not very "Svelte" like as it mixes responsibilities and exposes data to components that don't need that data. For example, why would the input bar need to know about the current sites ? It would be a lot better to have the input bar be just that, an input bar. When the user clicks 'add' it raises an event that says 'something has been added' and resets the fields. Then the parent is responsible to add it to the array. This will make for a more flexible solution. If you do that you will see there is also no reason to have a 'site' variable on the top level (or even have that object at all, you can just have two fields)
<script>
import { createEventDispatcher } from 'svelte'
let url = ''
let title = ''
const dispatch = createEventDispatcher()
function add() {
dispatch('add', { url, title })
url = ''
title = ''
}
</script>
<div class="rounded">
<p>Enter a site to stream:</p>
<input type="text" placeholder="www.example.com" bind:value={url}>
<br>
<input type="text" placeholder="example" bind:value={title}>
<button on:click={add}>add</button>
</div>
<InputBar on:add={(ev) => sites = [...sites, ev.detail]} />
On a final note, to add things to the head of the html use <svelte:head> instead.
If you want to change a value from another component you need to bind the property, otherwise the relationship is one-way only (from parent component to child).
<InputBar bind:sites {site}/>
This is my first question in Stack Overflow. I'll try to be specific but I don't know how to keep this short, so this is going to be a long post. Sorry about that. I promise I searched and tried a lot of stuff before asking, but I'm kind of lost now.
I'm developing a simple app in Angular 6 to keep track of software requisites and the tests associated to those requisites.
I have a component, called RequisiteList, whose HTML part consists in a mat-table with an Array of my own Requisite model class as [dataSource]. This array is received as an #Input parameter, and it also has an #Output parameter which is an EventEmitter that notifies and passes to the parent component every time a Requisite on the list is clicked.
I make use of RequisiteList inside of ReqListMain, which is a component consisting on the list and a hierarchical tree for filtering. This component is working fine, showing, and filtering requisites as intended. This component also captures the #Output event of the list and passes it as an #Output to its parent.
Finally (for what it's related to this question), I have a TestView component that has both an instance of RequisiteList to show the requisites currently associated to current test, and an instance of ReqListMain to add new requisites to current test (like a "browser"). This TestView has an instance of the model class Pectest corresponding to the test that is being currently visualized, which has an array of Requisite.
The idea in this last component was that whenever a requisite of the "browser" list was clicked, it was added to the current test's list. In order to do that, in the callback method associated to the #Output event of the browser list, I tried to add the Requisite received as a parameter:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
console.log('Current test: ');
console.log(this.currentTest);
}
In the HTML part of TestView, I inserted the RequisiteList component like this:
<app-requisitelist [requisites]="currentTest.requisites" ngModel name="reqlistview"></app-requisitelist>
(The ngModel property is part of the things I've been trying, I'm not sure it's necessary).
The result is:
The clicked requisite is not shown in the list.
In the console output I can see the content of currentTest object, and I verify that clicked requisites are in fact added to the requisites array of that object, so the event fires and the object is passed upwards by the children components.
I'm not sure if my problem is that data binding is made by value (I don't think so, as I bind an Array, which is an object AFAIK), or the table is not detecting data changes (I've tried to force data change detection with ChangeDetector), or anything else.
You pass a array to the app-requisitelist component. This component waits this array changes to update the content. When you do this.currentTest.requisites.push(requisite), the array this.currentTest.requisites doesn't change, I mean, if you do
const tmp = this.currentTest.requisites;
this.currentTest.requisites.push(requisite)
if (tmp === this.currentTest.requisites) {
console.log('The arrays are the same');
}
You will get the log printed. So, I suggest do something like that:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
this.currentTest.requisites = this.currentTest.requisites.map(item => item);
console.log('Current test: ');
console.log(this.currentTest);
}
The inserted line forces this.currentTest.requisites to be a new array with the same content.
I'm trying to detect when ever an object changes. The object is connected to a large form. Whenever a user changes the input I would like it to have save/cancel buttons popup at the bottom of the page.
My idea was to just make a copy of the object and do *ngIf="object !== object_copy" and if they hit cancel set the data equal to the copied object. I don't know if this the proper way to do it since I will be using it twice as many variables for a small task, but I've only used angular for a short time. I can't get this method to work however because when ever I make a type copy the object losses it's type.
Can someone help me with this or figure out a better way to do this?
If you are using a Form, then you could take advantage of Angular's form control, which will tell you anytime a form and any of its values have been altered in any way. Then, you could do something as simple as:
form.dirty
or even specific fields. There are tons of things you can do with reactive and template forms from Angular.
https://angular.io/guide/forms
You have to subscribe an event to handle the change event:
constructor(private formBuilder: FormBuilder) {
this.myForm = formBuilder.group({
name: 'Jose Anibal Rodriguez',
age: 23
})
this.myForm.valueChanges.subscribe(data => {
console.log('Form changes', data);
})
}
It should works.
ReactiveForm supports the dirty property. You can use 'myForm.dirty' to check the dirty status of the form.
Otherwise, you can set the initial value of the form to an object property using the getRawValue() method
this.initailFormValue = this.myForm.getRawValue();
Then just subscribe the form changes using
myForm.valueChanges.subscribe((value) => {
this.updatedFormValue = this.myForm.getRawValue();
},
(err) => {
//
}
);
Now you have the initial and current form values. You can compare and do the remaining.
I have an AngularJS application that manages badges. In the application is a form to set the badge # and the name of the person it is assigned to, etc. This gets stored in $scope.badge.
When the user submits the form, I want to add the new badge to a list of badges, which is displayed below the form.
Partial code looks like this:
var badge = angular.copy($scope.badge); // make a copy so we don't keep adding the same object
$scope.badgeList.push(badge);
The first time I run this code, it adds the badge as expected.
Any subsequent time I run this code, the next badge REPLACES the previous badge in the badgeList. In other words, if I add 5 badges, the badgeList still only has 1 object in it because it just keeps getting replaced.
I'm thinking that this may be happening because the same object keeps getting added? Maybe I'm wrong? I am using angular.copy to try and avoid that happening, but it doesn't seem to be working.
Any thoughts on this?
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
no need to use angular.copy since you are ultimately storing all the badges in an array
angular.copy is used when you want to make a clone of object and not update the existing object and the clone's change are not reflected in main object.
If you just want to maintain a list of badges you can execute this block of code
like this
function addBadges(){
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
}
If you are refreshing the controller then obviously the variable will be reset and for such a case you need to make use of angular services.
Create a service and inside the service you need to define getter and setter method that will help in data persistence
and your bages array if saved in service will persist till the application is in foreground.
You could do something like this.
function addBadges(){
//initialize if undefined or null
if(!$scope.badgeList){
$scope.badgeList = [];
}
//Check if badge does not exists in the list
if ($scope.badgeList.indexOf($scope.badge) === -1) {
//Add to badge list
$scope.badgeList.push($scope.badge);
}
}
On my SharePoint there is a website where a single list-item is loaded based on a user's selection with JavaScript and CSOM. This list item has a total of ~60 properties defined in it's list definition.
In HTML input fields the user can modify most of the properties after jQuery filled in the loaded properties to their corresponding input fields. When the "save" button is pressed, the properties are collected from the inputs via jQuery and put into a simple JS-object (itemProps):
var itemprops = {
'foo': $('#foo-input').val(),
'bar': $('#bar-input').val()
}
Then, the following function gets called:
function updateListItem(itemProps, onItemAdded, onItemError) {
var list = web.get_lists().getByTitle('ListTitle');
var listItem = list.getItemById(id);
for (var propName in itemProps) {
if (itemProps.hasOwnProperty(propName)) {
listItem.set_item(propName, itemProps[propName]);
}
}
listItem.update();
context.executeQueryAsync(
function() {
onItemAdded(listItem);
},
onItemError
);
}
Debugging shows me, that the data in itemProps are valid. But sometimes (I can't reproduce that effect deterministically) some properties get lost and when I look at the list item in the list on the SharePoint some of the properties are empty, as if itemProps had null or "" associated to that property. When I first tried to debug this I simply created an item and saved it (correctly, with all properties) and then loaded and saved it again without modification but some properties got lost.
Other properties get updated correctly and sometimes this doesn't happen at all.
Is there any way to make sure this effect does't occur or at least to detect it and retry updating the data, before the user's inputs get lost?
I notice you're not invoking context.load(listItem) before calling context.executeQueryAsync() which might cause issues with the listItem object's values being stale or dehydrated.
The code in your question looks like it should still be setting the specified values correctly on the list item, although there may be some code later on (such as in the onItemAdded function) that's running into false assumptions or subtle data differences from the inadequately loaded list item.