i'm using angular js in my website and i'm getting a strange behavior when i update my model.
My model is an json complex object like this:
$scope.DataSource = {prop1:"", prop2:[{a:1, b:b2}, {a:3, b:4}], prop3:"value"}
use a controller to edit object an use custom directives to edit separatelly child objects like in prop2 ( in this case are tabs ).
My problem is that when i update $scope.DataSource the previous generated DOM elements are not removed or updated, i just get a duplicated UI for each object in Prop2.
is there any way to force angular to update or remove previous generated elements? preventing a duplicated tabs(in this case)?
Duplicated DOM elements are likely the result of duplicated data in the scope associated with the view. Without any code, it is not clear if the scope generating the view is tied to a controller or a directive. Try logging the scope that the view is generated from to the console.
Related
I have been struggling a long way in a issue, wherein I need to update a parent obj from directive scope. I need to fetch some data using $http and fit this data against a property in original parent Obj.
However, after doing this, the view gets updated but somehow the model binded to these view become undefined. Since the view are updated with new data, somehow the models are becoming undefined after that.
Only now I came to know that, $http triggers a $digest, so I think that is the cause of my issue.
What can I do to avoid my models becoming undefined and the fetched values to remain intact in original object.
Just to make things clear, before I attach a wrking plnkr. here is what I mean:
I have a obj $scope.Obj. I have binded the input fields in directive template with this object using 2 way binding like
<input ng-model = Obj.something.something2[$index]/>
Now say I made a API call and update my something2 in $scope.Obj as:
$scope.Obj.something.something2 = APIResponse.something3
The values from new object something3 are visible on UI, but in backend after this
$scope.Obj.something.something2[$index]
becomes undefined.
Pls suggest possible reasons for this...
The other models are becoming undefined because you are replacing the object. Instead you should use angular.extend.
angular.extend($scope.Obj.something.something2, APIResponse.something3);
For more information see the AngularJS angular.extend API Reference.
I am creating form, where few fields are dynamic, ng-model is added dynamically.
Ex.:
form.append("<input type='hidden' name='paymillToken' value='" + token + "' data-ng-model = 'formdata.token'/>");
This fields shows undefined while I try to access using $scope.formdata.token
Following is another scenario where I am adding fields via ajax.
angular.forEach(data.data, function(obj, key) {
list+='<div class="items text-center"><img src="assets/uploads/discs/'+obj.image+'" class="img-circle"><br><input type="radio" id="chkDisc'+obj.id+'" name="disc_id" value="'+obj.id+'" required data-ng-model="formdata.disc_id" /></div>';
});
$scope.discslist = $sce.trustAsHtml(list);
This model disk_id is not accessible too.
Okay, to expand on my comment and a bit more on what everyone else here is saying to you, the main issue you're having is inherent in your approach. The way you're trying to manipulate the DOM is very un-AngularJS.
In AngularJS, when you want to change what is displayed to the user (the view), you make changes to your model (your controller scope). That means, you have to set up your view to be able to respond to those changes. We do that with directives and expressions in Angular.
You're probably already using directives to respond to changes in your model whether you realize it or not. ngRepeat, ngModel, ngShow, ngIf, ngInclude, are a handful you're probably familiar with, and even forms and form elements like inputs are actually directives in Angular. When you use these, a change in your model (such as loading data into the controller scope) signals to Angular that it should check whether that change affects any of the directives in your view, and if so, respond to it by updating the view.
In order to do this, Angular needs to know which parts of the model are connected to which parts of the view. It makes these connections when it compiles the html elements that are added to the page. This compile process happens automatically when you load an Angular app. After that, it's up to us to tell Angular when to compile html that is added to the page.
More often than not, we do this without even realizing it. For example, when you use the ngView directive, it will compile the template for each route that it loads, so that all of the directives in your template are properly linked with their associated model.
I know this is a long explanation, but there are two very important points here that are essential to learning AngularJS:
To change the view, you change your model and let the directives (and expressions) on your page respond to those changes.
When you add html elements to the page, if you want AngularJS to be able to use them in your view, they must be compiled first. The compile process should be done via a directive (either a built in one or a custom one).
So, how does all of this apply to your question?
First, I'm guessing that you're breaking both rules by trying to manipulate the DOM via a controller. Even if it is possible to use $compile in a controller, using a controller to change the DOM is bad practice and simply wrong to do (read the part in that link to the doc that specifically states: Do not use controllers to: Manipulate DOM...). A good rule to remember when you're learning AngularJS is that the only time you should ever be using JQuery or JQLite inside Angular is when you are creating a custom directive.
Okay, so how do you solve your question? Use a directive. It looks like you've got a case where you're trying to iterate over an object called data and add some inputs that correspond to the data.data property. This sounds like a job for ngRepeat.
The first thing you need to do is add your data to your controller and make sure it is accessible to the view. The easiest way to do this is by injecting $scope into your controller and setting the data on a scope variable. In its simplest form, that might look something like this:
angular.module('MyApp', [])
.controller('MyController', ['$scope', function($scope){
$http.get('/some/url/that/returns/the/data').
success(function(data) {
$scope.data = data;
});
}]);
Now that we have the data somewhere that we can access from the view, we can use it with the ngRepeat directive in our html, something like this:
<div ng-controller="MyController">
<div class="items text-center" ng-repeat="disc in data.data">
<img ng-src="assets/uploads/discs/{{disc.image}}" class="img-circle"><br>
<input type="radio" id="{{'chkDisc' + disc.id}}" name="{{disc.disc_id}}" value="{{disc.disc.id}}" required data-ng-model="formdata[disc.disc_id]" />
</div>
</div>
This is a common issue. By updating the value and not the model angular has no idea that the value in the field has been updated. As the first commenter said updating in this manner is completely unnecessary when using ng-model.
I have two custom element directives:
<my-directive-parent></my-directive-parent> //only one
and
<my-directive-child></my-directive-child> //variable count
The my-directive-parent directive has its own controller and templateUrl properties defined in its directive definition object. The my-directive-child directive has its own link, scope, templateUrl and require properties defined in its directive definition object. The fourth parameter passed into the link function is the parent my-directive-parent's controller. This is working as expected.
Based on user input, instances of my-directive-child are appended to or removed from the parent my-directive-parent DOM via the my-directive-parent's controller. Since I'm using ngView for the top-level page, changing pages results in Angular automatically cleaning up the various directives and their controllers of the top-level page. Upon returning, the controller for the page re-runs, but the user appended views do not show up (since they were previously wiped out automatically by Angular). I'm using a service which holds the data representing the collection of my-directive-child instances that were previously added.
My issue lies in restoring the my-directive-child directives based on this cached service data. Currently, when the my-directive-parent directive's controller is run, I'm getting the service data array that represents the instances to be added, looping through it, and adding a new my-directive-child instance to the DOM. This is working, but upon restoring each instance I need to pre-populate it with data that was previously entered by the user (of which is recorded in the service). Currently, when adding to the DOM this way each instance is in a "blank" state instead of a desired pre-populated state.
So I have the data needed to recreate the child directive instances, but I'm unable to pre-populate them with the necessary data.
I've tried to place a custom attr on the my-directive-child in an effort to forward the data to the instance's scope, but I learned from Angular's API docs that:
The new scope rule does not apply for the root of the template since the root of the template always gets a new scope
Questions:
Is my approach wrong? What should I be doing?
Is there a way to pass attrs defined on the actual element directive into the directive's scope that represents the inner template HTML?
How do I ensure a custom directive within a custom directive properly pre-populates itself?
Thank you in advance for any ideas, answers, or thoughts that might lead to a solid solution.
Part of the code in my controller deletes a DOM element:
MetrofficeApp.controller('EmployeesCtrl', function($scope) {
...
angular.element(deleteElem).remove();
$scope.$apply();
When I navigate away from this page using angular, and then come back to the same page where the deleted element is - the element is visible again.
What must I do besides $scope.$apply() to make the changes permanent (save DOM changes) between navigating pages?
I feel like you have a fundamental misunderstanding about the DOM. Every time you navigate back to a page, all code is re-invoked and templates are recreated. So, it is correct behavior that the DOM is created again.
My guess is that you have some underlying model that is visualized by the DOM. Rather than deleting components of the DOM, you should be deleting the part of the model that is visualized by the DOM (and pushing that change to the server). This way, the next time you navigate to the DOM, the model is consistent and the deleted item is no longer shown.
And a smaller point, but still important: controllers should not be manipulating the DOM directly. You should be creating directives for that.
You need to set some flag on the scope object which the angular js template can use to determine whether the show the dom element in question.
E.g.
// controller code
scope.shouldShowElement = some.flag;
// angular template
<div ng-show="shouldShowElement">...</div>
Angular templates have access to all the variables on the scope - if, at a later point, you do:
// controller code
scope.shouldShowElement = true;
scope.$apply();
Your template will update to reflect that change
In this example, using ng-switch, I'm able to switch between different views.
Each view is assigned a controller.
I've put a quick sample online here : http://jsfiddle.net/FBHjZ/1/
It looks like the controller is reinstanciated everytime I switch views :
If you enter a val in the input field, go to home and switch back to settings, the value is lost.
How can I prevent this?
Basically, what I want is to keep state from previous views when I switch between views.
There is no way of preventing the existing ngSwitch from re-instantiating controllers and re-creating a new scope. As noted in the documentation this directive is creating a new scope and actually creates / destroys corresponding DOM elements.
If you goal is to preserve state just put it in one of the parent scopes, check this jsFiddle:
http://jsfiddle.net/FBHjZ/2/
An alternative approach is to use a service for the shared model.