Refreshing Angular view after changing a value - javascript

Because the page I am working on is a legacy page with lots of dead/living/zombie code I am unable to paste it whole here. So I am trying to post a digest of my issue with sample code.
I have a page in which data comes from Angular. It is a bunch of products. Each product has an attribute named showProduct that determines it should be shown or not. The showProduct attribute is set to 1 when it is first fetched from the backend. While rendering the html, in each product div I have
ng-show={{product.showProduct}}. The ng-show works correctly the first time when its loaded, all products are shown. If from the backend I set this to 0 for any product, it is hidden.
Once the products are loaded, if the user clicks a button, I need to hide some of those products. This button click handler is in jQuery.
So I do the following:
prod = angular.element($('#product-section')).scope().ProductList;
prod is now an array of products with their attributes. Now I iterate through this array, check for the attribute in question (which I know based on what button was clicked) and based on its value for each product, I set a showProduct attribute to 0.
However this does not update the view, to hide that product. If I console.log the angular.element($('#product-section')).scope().ProductList, I can see that its showProduct has correctly been update from 1 to 0.
I am assuming that there is something I need to do in order to make the productList be "re-parsed" and refreshed in the page. However I am not sure how to do this.
I just needed some conceptual tips on what I might be missing, because I understand that providing "specific" code tip for my situation is difficult.
In a nutshell, once I have updated the angular value externally, how do I tell Angular to reparse the code and refresh the view? Something like how it happens automatically for models when updating data in a textbox...
I tried doing angular.element($('#product-section')).scope().$apply(); but that did not work.
Any pointers are greatly appreciated.

Try this:
$scope.$apply(function() {
angular.element($('#product-section')).scope().ProductList;
});
By doing this, you're telling AngularJS to watch what your enclosed code is doing and update the view accordingly.
Just some consideration: wouldn't you be able to just update the model directly instead of invoking an external code? This approach is always recommended.

Angular uses two way data binding, which works, when you use an Angular-Controller (or Directive, etc), in which you define your model. In your HTTML you use something like this (very crude example):
<div ng-controller="myProductListController>"
<div ng-repeat="(index, product) in productList">
<div>{{ product.name }}</div>
</div>
</div>
productListis in your myProductListController and is available in the scope (your model) of your ng-controller directive.
Now, each time the productList changes in your controller, your view will update automatically.
Scopes (models) are bound to some Angular-Controller (or Directive, ...). So you need to have that, in order to use two-way-binding here.
Hope that helps

Related

VueJs Understanding v-model and v-on:click

I'm currently trying to learn VueJS coming from a jQuery background and I've run into something that I either don't understand or trying to do wrongly. I'm using VueJS and Laravel. I add VueJS to my blade templates.
I have table set up with a few rows of data. In those rows I have a text input field. Each row also contains two icons that allow me to move the row up or down.
When I click on the icon I want to send a axios request telling my database to update the order field. When it returns a success message I want to reload the table with the new order.
After clicking the v-onclick icon and reloading the table into my div with an axios get the v-onclick stops working. Also the v-model binding that I've set up also stops working. When I update a value in a text input it no longer updates the value in the data property.
I've added a jsFiddle to give an idea of what I'm trying to accomplish.
I use axios to get a route that returns the products table view.
```reload : function(){
url = '/products;
axios.get(url).then(response => {
document.getElementById('products').innerHTML = response.data;
});
},```
https://jsfiddle.net/k8Lj4asb/1/
This is not the proper way to update a table in VueJS. By manipulating the DOM directly, you perform changes to the site that Vue doesn't track (and hence doesn't know about), which breaks reactivity.
The proper way to do this would be to store your list in a Vuex-Store and perform the update in there. I recommend reading up on the topic using the excellent documentation provided here: https://vuex.vuejs.org/
I would pass the object of products to javascript, Laravel has a package for this https://github.com/laracasts/PHP-Vars-To-Js-Transformer by Jeffrey Way.
Edit: oh cancel that, it looked from your broken fiddle you were trying to do something different.
In your axios response handler just update a data property for products with the response data. Then loop that property in VUE, no PHP side loop required.
<td v-for="product in products">
<input v-model="product.id" type="text" :name="product.name">
</td>

Strategy for dynamically generating directives. Logic in compile, link, or controller?

I'm looking to create a sidebar that will be generated based on data received from the back-end, which will be an array of items. Basically, each piece of data I receive will be its own component, but all together, they will form the side bar. The data I receive will not always be the same depending on the page, but the possible items will be known. So it's not like it's random data. I'll have a directive for each item.
I was thinking that I'd create a container directive (the sidebar) and create a directive for each smaller component. On the page load, the sidebar container would have some logic that knows how to bind the back-end item to the associated directive. It would just iterate through the items, find it's proper directive, then compile and append it to the sidebar element.
Does that sound like the correct approach? For that logic that involves mapping the back-end item to a directive, where should that go? Should that go in the compile, link, or controller function? I would have thought to use the controller function and utilize $element and $compile, but wanted to check on the input of others.
Thanks in advance!

Text box not updating displayed value from directive

Apologies for the long post, but I am attempting to provide as much information as I can.
I have inherited a rather complex app that utilizes Angular. Because of an NDR, I cannot post code samples, and as of yet, have been unable to recreate my issue in a smaller, less complicated stand alone plnkr. I’m sure if I could, I could then figure out what I need to do to fix this issue. I know your hands are tied because I cannot post code, nor can I recreate the problem in a plnkr, but I’m going to do my best to give as much information as possible to hopefully get some general suggestions on what to look at next.
First, the code/architecture:
I have a form.html. Inside that form.html, we are using ng-repeat to iterate through a list of fields pulled from the database, and displaying each field. The fields at the form level are a custom type.
<field data=“attrs.field[fieldId]” on-update”updateField” field=“field"></field>
updateField(fieldId, data) is defined in form.directive.js, and is used to update the data back to the database when it changes. This part works reliably.
form.html also contains a button that is used to clear the contents of the field.
<button class="btn btn-sm btn-danger" tooltip="Clear Field" ng-click="updateMetadataField(field._id, null)" ng-show="field.editable">
The field uses templates to generate the proper input, based upon the type. We use selects, texts, textedits, and many other custom fields. We use predictive text in many of these fields as well.
Here is one example:
<input name={{field._id}} type="text" class="form-control" ng-model="data" ng-blur="onUpdate(field._id, data)" ng-if="field.editable" typeahead="suggestion as suggestion for suggestion in field.suggestions | filter:$viewValue | limitTo:8" />
There is a field.directive.js that has its own methods used to manipulate the custom input fields that have been written. That contains:
scope: {
data: '=',
field: '=',
onUpdate: '='
},
The problem:
When initially loading the form, if there is data in a text field, and the user hits the Clear Field button, the database gets updated and the string is removed from the text box.
When a user first types text into a field, then leave the field, ng-blur calls updateField, and the data gets written to the database. This works fine. When a user clicks on the Clear Field button for a text or textedit field, the value in the database gets set back to null, but the string displayed in the text box doesn’t get cleared. However, if we reload the form, the text field shows empty.
At first I thought this had to do with an isolated scope inside of the fields stemming from the use of ng-repeat. However, a simple output statement of the attrs.field at the top of the form.html showed that every time the text box is updated, the parent scope is updating as well. So this doesn’t appear to be the issue.
I’ve decided that perhaps the issue is that the $modelValue of the input is getting updated, but the $viewValue is not (or at the very least we need to call $render for some reason). I’ve since been attempting to inject ngModel into the form.directive.js in order to access these variables and methods to see if I’m correct, but I’m having a heck of a time doing so.
1st attempt:
I tried injecting ngModel into the form.directive.js’s directive’s argument list. When I load the form, I get the following error:
Error: [$injector:unpr] Unknown provider: ngModelProvider <- ngModel <- fieldsDirective
http://errors.angularjs.org/1.4.7/$injector/unpr?p0=ngModelProvider%20%3C-%20ngModel%20%3C-%20fieldsDirective
I’m rather new to Angular, and I found the information at the link to be rather confusing. Some help understanding what they’re talking about there would be awesome!
2nd attempt:
Injecting ngModel in as an argument to the function in the link field in form.directive.js, along with a require: ’ngModel' statement in the return {} section. When I load the form, I get the following error:
Error: [$compile:ctreq] Controller 'ngModel', required by directive ‘fields', can't be found!
http://errors.angularjs.org/1.4.7/$compile/ctreq?p0=ngModel&p1=fields
When I follow this link it says that it is looking for the required directive controller on the current DOM element or its ancestor (when using require: ‘^ngModel’). However, these are being used by templates in the field used in the form. Does this qualify as being used in the current DOM element this way?
3rd attempt:
Assuming that the template is the reason I cannot include ngModel, I found this: Updating ng-model within a directive that uses a template. I changed the data: ‘=‘ scope mapping to data: '=ngModel’. Unfortunately this hasn’t changed any behavior at all.
I’m stuck. Can anyone provide me some other avenues to explore, or perhaps shed light as to why my including ngModel is failing?
Thanks in advance!
This is definitely tricky to diagnose without being able to see what you're actually doing. I would skip the ngModel injection as dangerous over-complication. I'd guess it's actually something simple at its core.
Now, my shot in the dark: Are you actually updating the $scope.data variable you're trying to work with? It looks to me like you're passing the callback updateField from the parent, which is defined in the parent. That would mean that when you reference $scope.anyVariable in the function, it's referring to the parent's $scope element (you'd be working in the parent controller's closure, not the directive's). You could have updateField take the child scope as a parameter, have the directive pass its $scope through that, and you'd know that you're actually working with the correct $scope object.
When that actual variable that's bound to ngModel is properly modified, the display should change in turn.
So we finally solved the issue.
The Clear Field button was contained inside of the Form.html, but was acting upon the input inside of the field template inside of field.html. Great for not having to repeat code, but unfortunately this just wasn't updating the value inside of the input for us when we used the Clear Field button to set it to null.
To solve, we moved the Clear Field button inside of each template, and changed around the callbacks so that updates made to ng-model in the input go to field's directive then bubble up to form's directive as required.
This fixed (or at least worked around) our issue.
Thanks!

How do I have a form submit values to an ng-repeat list item that's on the same page?

I asked this as part of another question yesterday, but I think it is best to break this into its own question.
I have a registration form which asks the user for name, address, email, etc. In return, smartystreets API returns suggested addresses correcting a possibly mistyped address. What I have (all on one page) is 1) the registration form and 2) a hidden list-item which I want to populate with the returned addresses (via ng-repeat), which will eventually be unhidden as a popup via Jquery UI.
So, the main issue is how to have the list-item populated via ng-repeat since the registration page is loaded before any data can populate the list item.
I have used ng-repeat to populate li's when the data existed already, but I'm confused about what to do in this situation when the data will be passed post-page load.
Just use your array in ng-repeat like you would do it with any other. I prefer to define it in the controller before just that I know that there is something but angular can also handle that for you.
<li ng-repeat="address in addresses">
As aet allready mentioned in the comments, angular watches for changes in your ng-repeat item so it gets updated whenever you change it. For example when you run an ajax request:
$http({method: 'GET', url: '/api/smartystreets/whatever'}).success(function(addresses) {
$scope.addresses = addresses;
});
It doesn't actually matter if the data is allready there, the async action is started on page load or later on with ng-change or anything else.
This is one of the strengts of angular, the two way databinding. The only point where you need to do some more work is when you use any async event which is not "covered" by angular and therefore requires you to run the digest cycle yourself using $apply.

ember-data and how to trigger commit when all bindings and observers have updated?

I have a Item model that has many Sections. Each Item is stored in mongodb and the sections are embedded documents, so when I get an item the json contains the sections.
When I change a property in a section, a number of other sections can also change as they are bound to that property.
How do I save this change to the server after all bindings and observers have been completed ?
I don't really want my users to have to click a save button but I also don't want to save after each change as I will be saving after the change and also after the bound sections changes as well!
Hope this makes sense.
If anyone can show me an example of how to do this it would be great!
Since you are using ember-data, you can create a transaction for that. Once you have made all the changes on the trasaction, then, just call transaction.commit() to bulk update your changes.

Categories

Resources