I'm trying to build a dynamic site using Angular. I'm trying to simulate delays in loading HTML by using setTimeout with $q.defer. It works if I don't have the timeout, but as soon as I add the timeout the data isn't loaded. It does get populated if I click between different views, so I know it's executing. But Angular doesn't seem to be aware that it's finally available.
I've got an HTML file, with the following:
<div ng-controller="MyCtrl">
<div id='my-content' ng-view></div>
<div id='footer'>
footer here
View 1 View 2
</div>
</div>
Here's view1.html:
<div ng-include="'teasers.html'"></div>
Here's teasers.html:
<div class='column content' ng-repeat="teaser in data.teasers">
<div class='button type-overlay' ng-class="{{ 'button-' + ($index + 1) }}">
<div class='teaser-text'>
<img class='button-background' ng-src='{{ teaser.img_src }}'>
<span class='teaser-text'>{{ teaser.name }}</span>
</div>
</div>
</div>
Here's my app.js:
var app = angular.module('app', [], function($routeProvider, $locationProvider) {
$locationProvider.hashPrefix("!");
$routeProvider.when('/', {
templateUrl: 'view1.html'
});
$routeProvider.when('/view2', {
templateUrl: 'view2.html'
});
});
app.factory('data', function($http, $q) {
return {
fetchTeasers: function () {
var deferred = $q.defer();
setTimeout(function() {
deferred.resolve([
{
"name": "Teaser 1",
"img_src": "SRC"
},
{
"name": "Teaser 2",
"img_src": "SRC"
},
{
"name": "Teaser 3",
"img_src": "SRC"
},
{
"name": "Teaser 4",
"img_src": "SRC"
}
]);
}, 5000);
return deferred.promise;
}
}
});
And here's the controller.js:
function MyCtrl($scope, data) {
$scope.data = {};
$scope.data.teasers = data.fetchTeasers();
}
What do I need to do to get deferred working in Angular?
Instead of window.setTimeout use angular timeout method: $timeout. Be sure to load it via DI.
$timeout(function(){
// all your magic goes here
});
By the way, you could use setTimeout, but then you would need do call $scope.$apply() manually. In fact this is what angular does internally.
$scope.$apply(function(){ // all your magic goes here })
But again, as I said, just use $timeout.
PS. The reason view gets updated after user interaction is the fact that it invokes dirty checking, unfortunately a little bit too late in this case. Using $apply or $timeout will ensure that angular gets notified about the changes when needed.
PPS. Although I've posted an answer to this particular problem, I should mention that you in this case you could use $resource, which is a angular service supporting RESTful APIs with promises. It uses an implementation of deferreds based on $q as well. There's no need to reinvent the wheel, unless you have any good reasons for it.
Related
I have a simple html file that make make search on Algolia and returns result. I can console the result but can not access $scope.users from view. How can I grab that $scope.users in view.
here is my app.js file
var myApp = angular.module('myApp', []);
myApp.controller('usersController', function($scope) {
$scope.users = [];
var client = algoliasearch('my_app_id', 'my_app_key');
var index = client.initIndex('getstarted_actors');
index.search('john', function searchDone(err, content) {
$scope.users.push(content.hits);
console.log(content.hits);
});
});
Here is my html view file
<div class="results" ng-controller="usersController">
<div ng-repeat="user in users">
<h3>{{ user.name }}</h3>
</div>
</div>
note: ng-app="myApp" attribute given in html tag.
It's most likely because your index.search call isn't triggering an Angular $digest cycle - manually trigger one with either $apply or a $timeout
index.search('john', function searchDone(err, content) {
$scope.users.push(content.hits);
$scope.$apply();
});
The $apply() could throw $digest already in progress errors - another way with a $timeout
myApp.controller('usersController', function($scope, $timeout) {
index.search('john', function searchDone(err, content) {
$timeout(function() {
$scope.users.push(content.hits);
});
});
});
try calling $scope.$apply() to update your bindings
index.search('john', function searchDone(err, content) {
$scope.users.push(content.hits);
console.log(content.hits);
$scope.$apply();
});
algoliasearch doesn't look like it's an injected service and therefore not native to the angular framework. Try calling $scope.$apply().
Hi I want to post item to server, and with each successful addition, automatically add it to DOM with ng-repeat
<div class="" ng-repeat="book in books" >
<div id="eachBook">{{book.title}}</div>
</div>
to POST the data and also to upload an image file, I use Jquery ajax, and $state.go(".") to reload the current page:
var fd = new FormData();
fd.append("file", bookImage);
$http({
method: 'POST',
url: "/someurl,
data: fd,
headers: {
'Content-Type': undefined
}
}).success(function(Image){
var book_obj = {
bookTitle: bookTitle,
bookImage: Image._id
};
$http.post("url to owner book", book_obj)
.success(function(data){
$scope.bookImage = data.bookImage;
$timeout(function(){
alert("success", "successfully added your book");
$state.transitionTo('book', {}, { reload: true });
},2000);
})
})
The problem is with first addition, the DOM is still empty, and even though I use $state to reload the page, it still not working for the first addition. In the end I need to refresh the page manually by clicking refresh.
after the first addition, it works fine. With each book added, it automatically added to DOM..
Any idea how to automatically start the first one without manually rendering the page? using $timeout to delay the refresh has no effect.
Is it not just a simple post to list on success?
var app = angular.module('myApp', []);
app.controller('bookCtrl', function($scope, $http, $timeout) {
$scope.init = function(){
$scope.title = 'initial book?'
postBook();
};
$scope.books = [];
$scope.post = function() {
postBook();
};
function postBook(){
if (!$scope.title) return;
// timeout to simulate server post
$timeout(function() {
$scope.books.push({title:$scope.title});
$scope.title = null;
}, 1000);
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="bookCtrl" ng-init="init()">
<div class="" ng-repeat="book in books">
<div class="eachBook">{{book.title}}</div>
</div>
<input type="text" ng-model="title" /><button ng-click="post()">save</button>
</div>
EDIT: Not sure why your DOM isn't ready but how about ng-init to accomplish an initial book post?
I'm writing an app that uses the same table with the same data in multiple places. I created a custom directive that allows me to reuse this table. Unfortunately, if I edit the table in one instance, the other instance does not refresh. How do I link these two so that any edits I make to one show up in the other?
It sounds like you've mostly figured it out, the hard part is getting your data into a shape where the videos and photos can be shared by the slide show. I recommend doing this in a shared data access object returned by a separate factory in Angular, rather than directly in a scope. I've got a sample in Plunkr if it helps.
The sample has a directives that binds to shared data, retrieved from a factory as an object injected into two separate scopes. In your case, you would have to add methods to retrieve data from the server, and shape it for display.
testApp.factory("News", [function () {
var news = {
"stories": [
{"date": new Date("2015-03-01"), "title": "Stuff happened"},
{"date": new Date("2015-02-28"), "title": "Bad weather coming"},
{"date": new Date("2015-02-27"), "title": "Dog bites man"}
],
"addStory": function (title) {
var story = {
"date": new Date(),
"title": title
};
news.stories.push(story);
}
};
return news;
}]);
Both controllers reference the same factory for the data:
testApp.controller("FirstController",
["$scope", "News", function ($scope, news) {
$scope.news = news;
}]);
testApp.controller("SecondController",
["$scope", "News", function ($scope, news) {
$scope.news = news;
}]);
Views then pass the data into to the news list directive, which both shares the data and keeps the directive relatively dumb.
<div ng-controller="FirstController">
<news-list news="news" title="'First List'"></news-list>
</div>
<div ng-controller="SecondController">
<news-list news="news" title="'Second List'"></news-list>
</div>
The news-list directive is just dumb formatting in this example:
testApp.directive("newsList",
function() {
var directive = {
"restrict": "E",
"replace": false,
"templateUrl": "news-list.html",
"scope": {
"news": "=news",
"title": "=title"
}
};
return directive;
});
View template:
<div class="news-list">
<p>{{title}}</p>
<ul>
<li ng-repeat="story in news.stories | orderBy:'date':true">{{story.date | date:'short'}}: {{story.title}}</li>
</ul>
<form>
<input type="text" id="newTitle" ng-model="newTitle" />
<button ng-click="news.addStory(newTitle)">Add</button>
</form>
</div>
I have a code snippet in my content which is a model fetched from http. I am using syntax highlighter to prettify the code. So I need to call a javascript function as soon as the DOM is updated for that particular model.
Here is a sample code to make it clear. I am using alert to demonstrate it. In my project I would use a third party plugin which will find matching dom elements and remodel them.
Here,
I want the alert to occur after the list is displayed
jsfiddle :
http://jsfiddle.net/7xZde/2/
My controller has something like this.
$scope.items = Model.notes();
alert('test');
alert comes even before the items list is shown, I want it after the list is displayed.
Any hint to help me achieve this.
We need to use $timeout ,
$scope.items = Model.notes();
$timeout(function () {
alert('test');
})
Yeah it was silly , $timeout seemed to be a misnomer to me. I am 2 days old to angularjs . Sorry for wasting your time.
Lucky for you, I wanted to do the exact same thing. Mutation observers are the path forward, but if you need backwards compatibility with older browsers, you'll need a bit more code than this.
Working plunker for Firefox, Chrome, and Safari.
Javascript:
var app = angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.name = 'World';
})
.directive('watchChanges', function ($parse, $timeout) {
return function (scope, element, attrs) {
var setter = $parse(attrs.watchChanges).assign;
// create an observer instance
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$timeout(function () {
var text = angular.element('<div></div>').text(element.html()).text();
setter(scope, text);
});
});
});
// configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
observer.observe(element[0], config);
};
});
HTML:
<body ng-controller="MainCtrl">
<div watch-changes="text">
<p>Hello {{ name }}</p>
<label>Name</label>
<input type="text" ng-model="name" />
</div>
<pre>{{text}}</pre>
</body>
I'm using AngularJS and can't find way to resolve this Issue:
there is part from my controller:
$scope.$on('showMoreNotifications', function (event) {
$.ajax({
url: '/notifications',
data: {
notificationCount: 30
},
success: function (e) {
$scope.notifications = e.Messages;
}
});
});
and here is html which using this controller:
<div class="widget" id="widget-notifications" ng-controller="NotificationsCtrl">
<span class="title" ng-click="$parent.$broadcast('showMoreNotifications')">#*showMoreNotifications()*#
Notifikace
</span>
<div class="messages">
<div ng-repeat="item in notifications" class="message-item type-{{item.Entity}}" data-id="{{item.AuditLogId}}">
<span class="type"></span>
<div class="description">
<span class="date">{{item.Date}}</span> / {{item.Message}}
</div>
</div>
</div>
</div>
If I click on span class title on top, controller right call to server and receives JSON data. Unfortunately dont refresh html which is associated with it. When I click second time, html refresh data from first request.
Your template is not updating since your are making xhr calls using jQuery. Those calls are considered "outside of AngularJS" world so AngularJS is not aware of them and doesn't know that it should start it automatic refresh cycle.
You would be much better using excellent $http service from AngularJS to make xhr calls. You would write something like:
$http('/notifications', {params : {
notificationCount: 30
}}).success(function (e) {
$scope.notifications = e.Messages;
});
There was a similar question where the answer helps migrating from jQuery's $.ajax to AngularJS $http: https://stackoverflow.com/a/12131912/1418796
Next, something not directly related, but you really don't have to broadcast events to react on the click event. It would be enough to write:
<span class="title" ng-click="myClickHandler()">
#*showMoreNotifications()*#
Notifikace
</span>
and then in your controller:
$scope.myClickHandler = function(){
//call $http here
}
Now I resolved my issue... It needs apply on scope
like this:
$.ajax({
url: Escudo.UrlHelper.baseUrl + 'Widgets/Notifications/GetLastNotifications',
data: {
notificationCount: 30
},
success: function (e) {
$scope.$apply(function () {
$scope.notifications = e.Messages;
});
}
});