It seems that the default behavior of Angular is to show the bindings that exist in html when an exception is thrown and it can't continue. Is there any way to hide them in this case?
I was thinking that ng-cloak might work for this but I'm trying to avoid adding ng-cloak to each element in my app.
Thoughts?
then add ng-cloak in a large div that contains everything you want to hide. You can even cloak the whole <body>.
Another common solution is to add an isReady variable on your $scope. By default, isReady will be false and you can display your {{...}} with a different value. When everything (like your ajax data) is loaded, set the isReady to true.
For example, (assuming you are injecting $scope to the controller instead of using controller as vm)
in your html markup,
<h1>{{isReady?title:'loading'}}</h1>
in the controller
angular.module('myApp').controller('$scope', function($scope){
activate();
function activate(){
// your code to get ajax data...
$scope.title='title to display';
$scope.isReady=true; // place at the end when everything before runs without error.
}
});
You can use ngBind instead;
<div ng-bind="someExpression"></div>
Related
I have a one-page site that I am building out and this is my first time using Angular on a site. Building it on top of Laravel too for the backend but that is beyond the scope of this question.
I need to be able to open a modal on a main page view which will add a new resource (e.g. a new client) or edit a resource. I want to somehow get the form's html inside the modal body when the $uibModal.open()'s controller is called and set the $scope.modalBody equal to the injected items.modalBody (the only way this works is if I use:
$scope.modalBody = $sce.trustAsHtml(items.modalBody);
The only problem now is that anything inside the HTML body, Angular will not use it's magic and do any data-binding. It is still in the raw form of
{{ object.property }} or since I'm using Laravel and avoiding conflict with the Blade template engine:
<% object.property %>
See screenshot:
screenshot
I have been banging my head against the wall on this one...I have tried putting $scope.$apply() in my directive and my controller, neither of which worked. I have a feeling that is the source of my problem though. I have also tried making the html just a <new-client></new-client> directive and using templateUrl: 'views/clients/add.php' which would be ideal, but the template is not being included inside the <new-client></new-client>.
I'm using ui-bootstrap 0.14.3 and Angular 1.4.8.
Could this be a bug? Or am I doing something wrong? Anyone have a better way of getting a form into my modal? Let me know what code you want to see so I don't clutter this post with unnecessary code blocks.
I have come across a similar issue with using jQuery's AJAX to receive template strings and append it to a server.
So when HTML is added via jQuery, bound html string, etc., angular doesn't know it needs to automagically compile this data.
What you need to do is use the $compile service, to $compile your html and then attach the correct $scope to it:
`$compile('jQuerySelectorReturningHtmlOrAnHTMLStringThatNeedsToBeCompiled')($scope);`
There are multiple examples in Angulars Documentation for $compile that can give you an idea of what is happening. I think by what you have described the same thing is happening here in your situation.
The key is to call this $compile service function after the html has been bound to the page.
EDIT:
There are a few other options based on some comments, that will serve as a viable solution to rendering this content on your view. For example a directive that takes a string attribute representing the HTML string of your desired view.
1. Modify your directive template in the compile step:
You have the ability to modify your template before the directive compiles and binds any attributes to it, to that directives scope:
app.directive('myAwesomeCompileStepDirective', [myAwesomeCompileStepDirectivef]);
function myAwesomeCompileStepDirectiveFn() {
return {
restrict: 'EA',
compile: function compileFn(tAttrs, tElement) {
//Here you can access the attrs that are passed into your directive (aka html string)
tElement.html(tAttrs['stringThatYouWantToReplaceElementWith']);
return function linkFn(scope, element, attrs, controller, transcludeFn) {
//if all you want to do is update the template you really don't have to do anything
//here but I leave it defined anyways.
}
}
}
}
You can view a file I wrote for a npm component which uses this method to modify my directive template before it is compiled on the page & you can also view the codepen for the complete component to see it in action.
2. Use $compile service to call $compile in link function using directive attrs.
In the same way as the aforementioned method, you can instead inject the $compile service, and call the function mentioned above. This provides a bit more work, for you but more flexibility to listen to events and perform scope based functions which is not available in the compile function in option 1.
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'm looking to pre-render multiple controllers with static data.
My code structure
Controller in app.js file ($scope.userList = localData.users)
View in separate template file user_list.html (ng-repeat="users in userList")
My goal is to avoid flickering when rendering user_list view in angularJS on slow devices, such as phones.
#Ofiris answer is correct, so I upvoted it. But it only solves part of the flickering, that of a template in its raw format before Angular has a chance to render it.
There are actually 3 states to the template:
When the template is raw and unprocessed
When the template has been processed by angular
When the template has had its data loaded by angular
ng-cloak resolves the transition from 1 to 2, but it cannot resolve the transition from 2 to 3, since it does not know when all of your data is ready.
Let's say you are loading up an object via $resource or $http and it will set a scope object foo to be "Jim". Here is how your 3 states look in the browser.
First state (pre-rendering):
<div>{{foo}}</div>
Second state (post-rendering, while ajax is running in background, maybe some processing):
<div></div>
Third state (all loaded):
<div>Jim</div>
So ng-cloack very much solves the transition from 1 to 2, and hides that ugly {{foo}}, but it won't help you with the flicker from nothing to "Jim".
For that, you would need some sort of flag of your own. I wrote a directive which I wrap all of my templates in (along with ng-cloak), called "loading". Each controller sets $scope.ready = true; when all of the data is ready for state 3. But it is very simple, you just need to wrap as follows:
<div ng-hide="!ready">
<!-- all of your stuff here -->
<div>{{foo}}</div>
</div>
and then your controller can do:
.controller('MyCtrl',function($scope,MyService) {
MyService.get('/data',function(data) {
$scope.foo = data.foo; // or whatever else you do here
// other processing
$scope.ready = true; // this shows everything
});
});
Check out the ngCloak directive.
The ngCloak directive is used to prevent the Angular html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.
Try to place it on the list containing div.
If you would like to resolve your data before the controller is instantiated, you can use routeProvider.resolve method, see this example.
In order to standardize loading warnings, and avoid changes in a page when http ajax requests come back and change $scope item values, I set up a simple directive to wrap an element and add "loading" to it.
<loading><div>My content here</div></loading>
This is converted into:
<div><div ng-hide="!ready"><div>My content here</div></div> <div ng-hide="ready">Loading...</div></div>
Pretty straightforward. Then in my controller, I just need to set $scope.ready = true; when everything ajax-y is loaded.
Here is my simple directive:
.directive('loading',function () {
return {
restrict: 'E',
template: '<div><div ng-transclude ng-hide="!ready"></div><div ng-hide="ready">Loading</div></div>',
transclude:true,
replace:true
};
})
Problem is that it works consistently for one page and not the other, even though their layouts are identical.
When examining with firebug, it appears that the one that doesn't work has class="ng-hide" on the first element, as if $scope.ready was never set to true. Of course, both Firebug breakpoints and console logs show that it was set to true.
My suspicion is a scope issue, or possibly how Angular is applying the value of scope changes, but not sure?
UPDATE:
I added messages for when the value of the element gets changed inside the directive, and when it is getting changed in the controller:
// directive
link: function ($scope) {
$scope.$watch('ready',function (newval,oldval) {
console.log("ready changed from "+oldval+" to "+newval);
});
}
// controller
console.log("changing $scope.ready to true");
$scope.ready = true;
For the one that works, I get the following messages:
setting $scope.ready to true
ready changed from undefined to true
For the one that did not, I get the following messages:
setting $scope.ready to true
The directive is not being notified about the change. Is this a scope issue?
HTML:
I really stripped down the html, still managed to recreate the problem:
<!-- THIS ONE HAS THE PROBLEM -->
<loading>
<h3>{{item.name}} Second Page</h3>
</loading>
<!-- THIS ONE IS JUST FINE -->
<loading>
<h3>{{item.name}}</h3>
</loading>
Leads me to believe it must be something in the controller? The routes are basically identical, except for their loading different template html files and different controller names.
UPDATE:
scopes are identical. I had the console messages spit out $scope.$id from inside each controller and from inside the link function (and inside the watch, which is not getting called anyways for the bad one). In all cases, all 3 $id are the same (004) for the page that works, and all 3 $id are the same (006) for the page that does not.
ANOTHER:
setting the directive to isolate scope with scope:{ready:'=ready'} (i.e. 2-way binding has the same effect. Using text binding # makes the whole directive not run correctly.
Apparently, it depends entirely on the ID of the item. I load this via URL /items/:item. If the Item is one that I pre-seeded in the database, so probably has an ID like "40", it all works, both for item type 1 and for item type 2. If it is an auto-generated hex ID for a new item created on the fly, like "8474b2486ee6", then it stumbles and doesn't load.
Finally... if I hit the Back button in the browser, for a second it actually shows!
When trying to add an ng-view inside an ng-include, nothing happens. e.g. in the following code, when themes/midnight/index.html holds an ng-view, no view is rendered:
<ng-include src="'themes/midnight/index.html'"></ng-include>
However, if I use the code below, the view shows twice:
<ng-include src="'themes/midnight/index.html'"></ng-include>
<div ng-view></div>
What is the problem and how can I resolve it?
This problem occurs due a delayed instantiation of ng-view (passing through ng-include). In such case the $route instantiation is delayed as well, and $route will miss the location change event (and routing will not be performed at all).
To bypass this, invoke the $route update function on application initialization:
yourApp.run(['$route', function($route) {
$route.reload();
}]);
Further more, it is sufficient to only include $route as a dependency. This will work, too:
yourApp.run(['$route', angular.noop]);
Source: the related issue on github.
Also check out ui-router, which is intended to specifically deal with the issue of nested views.