Let's say, I have two divs like this:
<div ng-show="!items.length>
<p>No items found</p>
</div>
<div ng-show="items.length>
<li ng-repeat="item in items">
...
...
</div>
Now $scope.items is populated using an AJAX call. Now whenever I load this page, it initially shows me that No items found. But after a second or so (when the AJAX call gets completed), it displays me the other <div> and hides the No items found. I was wondering if there is a better alternative technique for solving this problem ?
UPDATE: Controller looks like this (simplified for the question):
myApp.controller('Controller', function($scope, $http) {
$http.get(fetchURL).
success(function(data, status, headers, config) {
$scope.items =data
});
});
dont create $scope.items before the ajax is complete, so create $scope.items only after ajax request is complete and data is ready.
for ex:
$http.get(fetchURL).success(function(data) {
$scope.items = data; // create $scope.items here, and don't create before this.
});
then
<div ng-show="items && (items.length ==0)">
<p>No items found</p>
</div>
<div ng-if="items">
<div ng-show="!items.length">No items found.</div>
...
</div>
This will only render the content after the items are actually created.
You can use the resolve parameter of your routeProvider#when. See in the docs that this parameter is:
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated
So, in your case, you can do something like this:
$routeProvider.when('/url', {
templateUrl: 'partials/yourhtml.html',
controller: 'yourController',
resolve: {
'items' : ['$http', function($http) {
return $http.get("items");
}]
}
});
And in controller you inject the 'items' as a parameter:
myApp.controller('Controller', function($scope, items) {
$scope.items = items.data;
}]);
Related
I am working on creating reusable directive which will be showing composite hierarchical data .
On first page load, Categories like "Server" / "Software"/ "Motherboard" (items array bound to ng-repeat) would be displayed . If user clicks on "Server" then it would show available servers like "Ser1"/"Ser2"/"Ser3".
html :
<div ng-app="myApp" ng-controller="myCtrl" ng-init="init()">
<ul>
<li ng-repeat="item in items">
<div my-dir paramitem="item"></div>
</li>
</ul>
</div>
Now first time Items are loading, but clicking on any item is not refreshing ng-repeat. I have checked ng-click, "subItemClick" in below controller, method and it is being fired. However the items collection is not getting refreshed.
http://plnkr.co/edit/rZk9cbEJU90oupVgcSQt
Controller:
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.init = function() {
$scope.items = [{iname: 'server',subItems: ['ser1', 'ser2','ser3']}
];
};
$scope.subItemClick = function(sb) {
if (sb.subItems.length > 0) {
var zdupitems = [];
for (var i = 0; i < sb.subItems.length; i++) {
zdupitems.push({
iname: sb.subItems[i],
subItems: []
});
}
$scope.items = zdupitems;
}
};
}])
.directive('myDir', function() {
return {
controller: 'myCtrl',
template: "<div><a href=# ng-click='subItemClick(paramitem)'>{{paramitem.iname}}</a></div>",
scope: {
paramitem: '='
}
}
});
I am expecting items like ser1/ser2 to be bound to ng-repeat on clicking "Server" but it is not happening .
Any help?
I think that onClick is screwing up the method's definition of $scope. In that context, the $scope that renders the ngRepeat is actually $scope.$parent (do not use $scope.$parent), and you're creating a new items array on the wrong $scope.
I realize the jsfiddle is probably a dumbed down example of what you're dealing with, but it's the wrong approach either way. If you need to use a global value, you should be getting it from an injected Service so that if one component resets a value that new value is reflected everywhere. Or you could just not put that onClick element in a separate Directive. What's the value in that?
I have two views, the first one (Search) has a button which when clicked will add an item to $scope.results1 and then take the user to the other view (Results) where the ng-repeat is.
When I click the button and the results page comes up, only "1" is displayed. However, if I call the test function straight away in the controller, I get taken to the Results page and both "1" and "2" are displayed. In both cases, the console log shows that the array results1 contains 2 items.
From what I've read, the solution would be to implement either a factory or a service but I'm fairly new to Ionic/angular so not quite sure how to begin such an implementation, any pointers would be appreciated!
Button in Search view :
<button class="button-full" id="find" ng-click="test();">Find</button>
SearchController:
$scope.results1=[];
$scope.results1.push(1);
$scope.test = function(){
$scope.results1.push(2);
console.log("pushed 2");
console.log($scope.results1);
$state.go("tab.results");
};
Results view:
<ion-content ng-controller="SearchController">
<body>
<div id="results">
<div class="list" id="search-items">
<div ng-repeat="item in results1">
{{item}}
</div>
</div>
</div>
</body>
</ion-content>
You could implememnt a service for holding Results like this
var mainApplicationModule = angular.module("yourAppName");
mainApplicationModule.service('ResultService', function(){
var results = [];
this.add = function(data){ // to add data to results
results.push(data);
}
this.getResults = function(){ // to get all results
return(results);
}
})
Inject ResultService into your SearchController like this,
mainApplicationModule.controller('SearchController',['$scope','ResultService','$location', function($scope,ResultService,$location) {
ResultService.add(1) // Adds 1 to 'results' array in ResultService
$scope.test = function() {
ResultService.add(2); // Adds 2 to Results array in ResultService
$location.path("/results") // replace with path to your results view
}
$scope.results1 = ResultService.getResults(); // will have [1,2]
}
you can pass data while changing state,
config the state like this:
.state('tab.results', {
url: '/yoururl',
templateUrl: 'yourtemplate',
controller: 'yourcontroller',
params: {
"results": ""
}
});
then using :
$state.go("tab.results",{"results": $scope.results1});
in the second controller inject $stateParams and get value:
$scope.results = $stateParams.results;
This is a newbie question about Angular 1.4.
My Main Question
I have the following search view:
'use strict';
var module = angular.module('myapp.search', ['ngRoute', 'ngResource']);
module.config(function ($routeProvider) {
$routeProvider.when('/search', {
templateUrl: 'search/search.html',
controller: 'SearchCtrl'
});
});
module.factory('Search', function ($resource) {
return $resource('http://www.myapp.com/api/search', {}, {
query: {
method: 'GET',
params: {},
isArray: false
}
});
});
module.controller('SearchCtrl', function ($scope, Search) {
$scope.results = [];
$scope.query = '';
$scope.doSearch = function (query) {
Search.query({q: query}).$promise.then(function (result) {
$scope.results = result.results;
});
};
});
And the partial HTML (search.html):
#search.html
<input placeholder="search" ng-model="query" />
<button ng-click="doSearch(query)" />
<h1>Results</h1>
<ol>
<li ng-repeat="result in results">{{ result }}</li>
</ol>
...
This works perfectly, the problem is when I want to put the search input in a toolbar (defined in index.html):
#index.html
<div ng-controller="SearchCtrl">
<div class="toolbar">
<input placeholder="search" ng-model="query" />
<button ng-click="doSearch(query)">
</div>
...
In this case, by pushing the search button the query is executed in the back-end but the results are not updated in $scope. what's wrong? I've tried calling $scope.$apply() with no luck. I'm lost with this.
Bonus Question
Given that the search functionality is in the toolbar (always visible) users can execute queries in any place of the website. How can I redirect the response to http://www.myapp.com/search?
Thanks in advance.
You have two distinct instances of SearchCtrl, each with its own distinct scope: one in the navbar (dur to ng-controller="SearchCtrl"), and one in the main view (due to controller: 'SearchCtrl').
So the search in the navbar modifies the scope of the navbar controller, and the scope of the main view doesn't know anything about it.
If you want to got to the search main view when something is searched in the navbar, simply use
$location.url('/search?query=' + theEncodedQueryTypedByTheUser);
And in the search route's resolve function, or in its controller, get the route param named query, send the HTTP request, and update the scope with the results.
I bind data of two different scopes to the same common service data. When I update the data through controller 1, the data in controller 2 is not refreshed in template.
Example showing the issue :
<body ng-app="demoShareBindApp">
<div ng-controller="FirstCtrl as first">
Set share data to : <a href ng-click="setShareDataTo('Me')">"Me"</a>
- <a href ng-click="setShareDataTo('Myself')">"Myself"</a>
- <a href ng-click="setShareDataTo('I')">"and I"</a>
</div>
<div ng-controller="SecondCtrl as second">
Text entered : {{sShareData}}
<br>
<br><a href ng-click="revealShareData()">Reveal data</a>
</div>
<script src="bower_components/angular/angular.js"></script>
<script>
angular
.module('demoShareBindApp', [])
.service('myService', function () {
return {
shareData: null
}
})
.controller('FirstCtrl', ['$scope', 'myService', function ($scope, myService) {
$scope.setShareDataTo = function(content) {
myService.shareData = content;
};
}])
.controller('SecondCtrl', ['$scope', 'myService', function ($scope, myService) {
$scope.sShareData = myService.shareData;
$scope.revealShareData = function() {
console.log(myService.shareData);
}
}]);
</script>
</body>
The Text entered : {{sShareData}} is never updated whereas clicking "Reveal data" shows the right share data in console.
I can't find any clue in other SO post on this particular subject. I guess it could be a matter of "$watch/$digest" but I can't figure out what is really going on here.
Any detailed explanation welcome !
You are sharing data through service. When data in changed under first controller, you set this updated data to service but second controller does not know that shared data referenced by service has changed, so we need to notify the second controller that data has changed or we can shared data through events
I created a fiddle, check it
https://jsbin.com/midebu/edit?html,js,output
First approach. Using service
$scope.setShareDataTo = function(content) {
myService.shareData = content;
$rootScope.$broadcast('datacChanged');
};
In second cntroller
$rootScope.$on('dataChanged', function(){
// get updated data
$scope.sShareData = myService.shareData;
})
Other way is that, we do not need to use service , we can simply pass that shared data using events
$scope.setShareDataTo = function(content) {
$rootScope.$broadcast('datacChanged', content);
};
And in Second controller
$rootScope.$on('dataChanged', function(event, data){
// get updated data
$scope.shareData = data;
})
I have AJAX response inside that is deleted object:
request.success(function (data) {
delete $scope.notifications.system[key];
$scope.$apply();
});
I have HTML code with block, that would be appear by condition:
<span ng-show="notifications.system.length == 0" ng-cloak>
DELETED
</span>
So, I tried to use $scope.$apply(); in response at once after removing object. But I have got error:
Error: [$rootScope:inprog] http://errors.angularjs.org/1.3.13/$rootScope/inprog?p0=%24digest
How I can reload template when notifications.system.length is equal zero?
When you use delete on arrays it doesn't change the length of the array instead it replaces the element in the array with undefined. So your ng-show never changes because the length of the array isn't changing. Use splice instead and the array will shorten and your $scope should update at you expect.
$scope.notifications.system.splice($scope.notifications.system.indexOf(key), 1);
you shouldn't need $scope.$apply() for something like this.
If you choose to use the $scope.$apply() you should wrap everything in a $timeout and call it like this.
request.success(function(resp){
$timeout(function(){
$scope.$apply(function(){
//do stuff here to the scope.
});
});
});
Passing in a function reference to $apply will cause it to execute that function then $digest. Seems a bit strange I know, but the reason for this is that AngularJS typically calls $digest in response to user interaction, not necessarily to events like $http.success.
You could also do the managing of your errors differently.
Instead of adding directly to an object you could add the error objects to an array.
Deleting can then be done with the following code:
$scope.removeError = function (errorName) {
angular.forEach($scope.notifications, function (error, index) {
if (error.hasOwnProperty(errorName)) $scope.notifications.pop(index);
});
};
Have a look at the demo below and here at jsfiddle.
angular.module('myApp', [])
.controller('mainController', MainController);
function MainController($http, $scope, $timeout) {
$scope.notifications = [{
errorImg: 'failed to load image',
}//,
/*{ // other errors
//errorJS: 'failed to load js'
}*/];
$scope.removeError = function (errorName) {
angular.forEach($scope.notifications, function (error, index) {
//console.log(index, error.hasOwnProperty(errorName), $scope.notifications);
if (error.hasOwnProperty(errorName)) $scope.notifications.pop(index);
//console.log(index, error.hasOwnProperty(errorName), $scope.notifications);
});
};
$scope.clear = function () {
$http.jsonp('http://www.mocky.io/v2/556f7ba53db19a8f05f1e555?callback=JSON_CALLBACK')
.success(function (response) {
//dummy request
//console.log(response, $scope.notifications);
//delete $scope.notifications['errorImg'];
$scope.removeError('errorImg');
}) //.bind(this))
}
}
MainController.$inject = ['$http', '$scope', '$timeout'];
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller='mainController as main'> <pre>{{notifications |json}}</pre>
<!--<button ng-click="main.show()">Show data</button>
<ul>
<li ng-repeat="info in main.data">{{info.test}}</li>
</ul>-->
<button ng-click="clear()">clear error</button>
<ul>
<li ng-repeat="i in dummy">{{i}}</li>
</ul>
<div ng-show="notifications.length == 0">deleted</div>
</div>