hiding the scope in angularJS - angularjs

I have a problem in which I need to hide results until the user has started typing into the search bar. I am using a DotNetNuke plugin, and therefore did not build this module myself. The scopes I believe I need to use are for '.angrid-search' which has a method that returns the search terms, which it will then use in order to decide if the 'angrid-grid' will be displayed. This is the code I have tried thus far, as well as many different similar variations.
if (angular.element($('.angrid-search')).searchTerms === undefined){
angular.element($('.angrid-grid')).hide();
}
angular.element($('.angrid-search')) comes back with undefined, and returns the search terms once something is typed in. It seems to me that the problem is in the second line, in which I try to hide the element.
I am extremely new to Angular (this is pretty much my first real problem), so explaining in layman's terms would be greatly appreciated, especially since I need to learn just as importantly as I need to solve this problem.
Here is the basic DOM
<div class="angrid">
<div class="angrid-search">
</div>
<div class="angrid-grid-view">
<div class="angrid-grid">
</div>
</div>
</div>
There is a bunch of stuff inbetween, but these are the relavent scopes and I did not want to cpypst the inspector window. My main question is: Is the .hide() method supposed to work in this type of sitation?

You could try something like.
<div class="angrid">
<div class="angrid-search">
</div>
<div class="angrid-grid-view">
<div class="angrid-grid" ng-hide="hidegrid">
</div>
</div>
and in js
if (angular.element($('.angrid-search')).searchTerms === undefined){
angular.element($('.angrid-grid')).hidegrid = true;
}

Yes. It's supposed to work. Here's a demo:
angular.module('demo', [])
.controller('demoCtrl', ['$scope',
function($scope) {
$scope.testFn = function() {
if (angular.element($('.angrid-search')).searchTerms === undefined) {
angular.element($('.angrid-grid')).hide();
}
};
}
]);
<script data-require="jquery#1.11.3" data-semver="1.11.3" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script data-require="angular.js#1.5.5" data-semver="1.5.5" src="https://code.angularjs.org/1.5.5/angular.js"></script>
<div ng-app="demo" ng-controller="demoCtrl">
<div class="angrid">
<div class="angrid-search"></div>
<div class="angrid-grid-view">
<div class="angrid-grid">Grid!</div>
</div>
</div>
<button ng-click="testFn()">the code will work...</button>
</div>
By the way, you mention:
angular.element($('.angrid-search')) comes back with undefined
If it does, then obviously hiding things will not work. You should probably ask another question with enough information to reproduce that problem scenario from scratch, so we can help without resorting to guessing.

Related

Page render speed in AngularJS using ng-include

I've got an issue where I populate a page in pieces. There are an arbitrary number of categories with an arbitrary number of items. The code is generally something like the below (warning, transposed).
$scope.getItems = function(key) {
$http.get('get-items?key=' + key)
.then(function(res) {
for (let item of res.data) {
$scope.categories[item.category].items.push(item);
}
});
}
let populateCategories = function() {
for (let key in $scope.categories) {
$scope.getItems(key);
}
}
$scope.getCategories = function(next) {
$http.get('get-categories')
.then(function(res) {
$scope.categories = res.data;
next();
});
$scope.getCategories(populateCategories);
}
The idea is to first get what categories will be on the page, and render them, empty (but w/ a busy icon). After that, hit and endpoint one time per category and populate w/ the results. The busy icon is shown via ng-show & a boolean pointing to the size of the items. 1 or more items = no busy icon, an the items should show.
The loading of the categories more or less works. Populating them though, is not so free flowing. Watching the console output, it takes ages for the browser to render. The busy icon goes away somewhat quickly, but I don't see the items until a bunch of them are ready.
Worth noting, (I think) I saw this problem appear when I moved the html that displays each item from a single file, an template and used ng-include, as I'm using it on two different places. Surely that would not be a cause would it?
EDIT: Adding the html - simplified
item-template.html
<div class="row">
<div class="col-xs-2 col">
<img src="{{item.img}}">
</div>
<div class="col-xs-10 col">
<div>{{item.details}}</div>
</div>
</div>
list.html
<body>
<div class ="container-fluid">
<div class="row">
<div ng-repeat="(key, value) in categories">
<div>{{key}}</div>
<div ng-show="value.busy"">
<img ng-src="{{busy_image}}">
</div>
<div ng-repeat="item in value.items track by $index">
<!-- This in fact seems to be the culprit -->
<div ng-include="item-template.html">
</div>
</div>
</div>
</div>
</body>
So, playing around, if I simply paste the contents of template.html into list.html, the response is much, much better. Looking at this issue, the solution seems to be to use a cache service. I'm happy to use something like that but I'm still curious as to why. The template I'm using isn't small (166 lines) but I can't imagine it being that heavy either on a modern computer.
Several things from the top of head:
amount of items to be shown in the HTML. Large lists with x properties = alot of Angular watchers.
if there are alot of items, maybe check for an alternative to ng-repeat
instead of ng-include item-template.html, create a Component
ng-repeat with track by
use bind once
in this case you can replace ng-show with ng-if

cannot get angular-marked to work against $scope variable

I have a controller that basically assigns text to a $scope variable like this;
$scope['Model'] = ["markdown text 1", "markdown text 2"];
And then I try to use marked on it within a view, like this;
<div ng-repeat="n in Model">
<div marked="n"></div>
</div>
or
<div marked>
<div ng-repeat="n in Model">
{{n}}
</div>
</div>
I just get {{n}} as the output, verbatim. Marked never runs, never does anything to it. I'm completely baffled. I know the text is fine.
I have tried all of the examples and nothing seems to work. It does work if I put in static, hard-coded text between <marked> directives - but nothing dynamic.
The only way I've been able to make anything work is to forcefully use the marked(n) function within the controller - which is far less than ideal and certainly not what I'm wanting to do.
After a lot of trying, I think that the way angular-ui-router is involved may play a part. Here is the HTML structure;
index.html
<div class="content-body">
<ui-view />
</div>
content.html
<ui-view />
entry.html
<div ng-repeat="m in model">
<div marked="m"></div>
</div>
I think I've got it.
<div ng-repeat="m in model">
<div marked="m"></div>
</div>
Works as per this fiddle: https://jsfiddle.net/jorgthuijls/q244srfh/
See, ng-repeat creates its own scope. So, you can bind the m variable to the marked directive.
I got it to work with angular-ui-router too: https://jsfiddle.net/jorgthuijls/ck8by0ze/

AngularJS Scope not updating in view after async call

I am having trouble updating my scope on the front-end while making a request to an API. On the backend I can see that the value of my $scope variable is changing but this is not being reflected in the views.
Here is my controller.
Controllers.controller('searchCtrl',
function($scope, $http, $timeout) {
$scope.$watch('search', function() {
fetch();
});
$scope.search = "Sherlock Holmes";
function fetch(){
var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
$timeout(function(){
$http.get(query)
.then(function(response){
$scope.beers = response.data;
console.log($scope.beers);
});
});
}
});
Here is a snippet of my html
<div ng-if="!beers">
Loading results...
</div>
<p>Beers: {{beers}}</p>
<div ng-if="beers.status==='success'">
<div class='row'>
<div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
<h2>{{beer.name}}</h2>
<p>{{beer.style.description}}</p>
<hr>
</div>
</div>
</div>
<div ng-if="beers.status==='failure'">
<p>No results found.</p>
</div>
I've tried several solutions including using $scope.$apply(); but this just creates the common error
Error: $digest already in progress
The following post suggested to use $timeout or $asyncDefault
AngularJS : Prevent error $digest already in progress when calling $scope.$apply()
The code I have above uses $timeout and I have no errors but still the view is not updating.
Help appreciated
I you are using AngularJS 1.3+, you can try $scope.$applyAsync() right after $scope.beers = response.data; statement.
This is what Angular documentation says about $applyAsync()
Schedule the invocation of $apply to occur at a later time. The actual time difference varies across browsers, but is typically around ~10 milliseconds. Source
Update
As others have pointed out, you should not (usually) need to trigger the digest cycle manually. Most of the times it just points to a bad design (or at least not an AngularJS-friendly design) of your application.
Currently in the OP the fetch method is triggered on $watch. If instead that method was to be triggered by ngChange, the digest cycle should be triggered automatically.
Here is an example what such a code might look like:
HTML
// please note the "controller as" syntax would be preferred, but that is out of the scope of this question/answer
<input ng-model="search" ng-change="fetchBeers()">
JavaScript
function SearchController($scope, $http) {
$scope.search = "Sherlock Holmes";
$scope.fetchBeers = function () {
const query = `http://api.com/v2/search?q=${$scope.search}&key=[API KEY]&format=json`;
$http.get(query).then(response => $scope.beers = response.data);
};
}
As the comments suggest, you shouldn't need to use $timeout to trigger a digest cycle. As long as the UX that elicits the change is within the confines of an angular construct (e.g. controller function, service, etc.) then it should manifest within the digest cycle.
Based on what I can infer from your post, you are probably using a search input to hit an API with results. I'd recommend changing the logic up such that you are triggering your search on an explicit event rather than the $watcher.
<input ng-model="search" ng-change="fetch()">
Remove the $watch logic and the $timeout wrapper.
function fetch(){
var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
$http.get(query)
.then(function(response){
$scope.beers = response.data;
console.log($scope.beers);
//it's a good habit to return your data in the promise APIs
return $scope.beers;
});
}
The reasons I make this recommendation is:
You have finer control of how the ng-change callback is triggered using ng-model-options. This means you can put a delay on it, you can trigger for various UX events, etc.
You've maintained a clearer sequence of how fetch is called.
You have possibly avoided performance and $digest issues.
Hey guys I solved the issue but I'm not sure exactly why this changed anything. Rearranging my code on JS Fiddle I just put all my partials into the index.html file like so and the requests and scope variables updated smoothly. Is was there perhaps a controller conflict with my html above?
<body ng-app="beerify" ng-controller='searchCtrl'>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container"><!-- nav bar code -->
</div>
</nav>
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<div class="container">
<h1>Title</h1>
<form ng-submit="fetch()">
<div class="input-group">
<input type="text" ng-model="search"
class="form-control" placeholder="Search the name of a beer" name="srch-term" id="srch-term">
<div class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
</div>
</div>
</form>
</div>
</div>
<div class="container">
<div ng-if="!beers">
Loading results...
</div>
<div ng-if="beers.status==='success'">
<div class='row'>
<div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
<!-- ng-if will make sure there is some information being displayed
for each beer -->
<h2>{{beer.name}}</h2>
<h3>{{beer.style.name}}</h3>
<p>AbvMin: {{beer.abv}}</p>
<p>AbvMax: {{beer.ibu}}</p>
<p>{{beer.style.description}}</p>
<hr>
</div>
</div>
</div>
<div ng-if="beers.status==='failure'">
<p>No results found.</p>
</div>
</body>

ng-model with dynamic form inputs

I'm having trouble dynamically capturing form inputs in angular. I have a form that takes several inputs for a resource. Each resource has many sections, and each section has many links. I've been able to get the functionality to work for a user to dynamically add/remove sections and links, but when it comes to actually capturing that with ng-model I can't seem to get it.
Based on this stackoverflow post, I thought I could do something like the first answer, ng-model="newResourceForm.sections[section.title]", but that doesn't seem to be working for me (it says that it is undefined)
Here is a link to a plunkr that I made for it:
Looks like the problem in your code is that you are binding your variables to
newResourceForm.sections
but you are creating new sections inside an array named sections without a title property.
Using ng-model="newResourceForm.sections[section.title]" works but section.title is undefined. The result is your newResourceForm.sections object contains just one section named undefined no matter how many objects you have in your sections array.
The way you're adding/editing sections is a little off. It's hard to say without looking at your controller code, but I think this is part of the issue:
<div ng-repeat="section in sections" class="form-group mt">
and it should look more like
<div ng-repeat="section in resource.sections" class="form-group mt">
I made a really minimal working version of what I think you were going for, feel free to try it out! (you just have to change the location of angular.min.js)
<html>
<head>
<script src="static/angular.min.js"></script>
<script>
angular.module('app', [])
.controller('resourseCtrl', resourseCtrl);
function resourseCtrl() {
this.resource = {'sections': []};
console.log('controller started');
this.addSection = function() {
this.resource.sections.push({});
};
this.removeSection = function() {
this.resource.sections.splice(this.resource.sections.length - 1, 1);
};
}
</script>
</head>
<body ng-app="app">
<div ng-controller="resourseCtrl as resourseCtrl">
<button ng-click="resourseCtrl.addSection()">add section</button>
<button ng-click="resourseCtrl.removeSection()">remove section</button>
<div ng-repeat="section in resourseCtrl.resource.sections">
<p>name:<input text ng-model="section.name"></input></p>
<p>title:<input text ng-model="section.title"></input></p>
<p>description:<input text ng-model="section.description"></input></p>
</div>
{{ resourseCtrl.resource }}
</div>
</body>
</html>

angularjs efficiency declaring controller inside or outside

Having a bit of a more complicated issue, but one factor that I really want to know is this..
Does declaring a controller inside (second method) a partial which is not instantiated (yet) increase the efficiency of the app compared to declaring the controller in my index file (first method)
I would think it would be more efficient by having the ng-controller's inside the partials as if they would be outside, they would all have to be instantiated at once at the start as well as keeping more memory in usage at all times.
in terms of memory allocation, I tested both methods and both seem to give me about the same amount of memory used.
First method:
in "index.html:"
<div ng-controller="mainController as mainCtrl">
<div ng-switch="mainCtrl.currentPage">
<div ng-controller='browsePageController as browseCtrl'>
<div ng-switch-when="browsePage">
<browse-page></browse-page>
</div>
</div>
<div ng-controller='otherController as otherCtrl'>
<div ng-switch-when="homePage">
<other-page></other-page>
</div>
</div>
</div>
</div>
Second method:
<div ng-controller="mainController as mainCtrl">
<div ng-switch="mainCtrl.currentState">
<div ng-switch-when="browsePage">
<browse-page></browse-page>
</div>
<div ng-switch-when="otherPage">
<other-page></other-page>
</div>
</div>
</div>
in "homePage.html":
<div ng-controller='homePageController as homeCtrl'>
......
</div>
Another sidequestion is..
Every time a page changes (say from browsePage to otherPage), the controller in the otherPage gets initialized (if using the second method). This actually messes with my data somewhat. Any design pattern suggestions that would help me out better ? thanks
got rid of the switches with views - used ui-router and stateProvider which proved to be quite useful in managing many issues of an angularjs app

Resources