Service to indicate some done criteria using Promise - javascript

We have several controllers in an application, for several different tabs/pages.
I want to have some mechanism to indicate that something is finished in one of
them for use in another controller. Promises should be this mechanism,
so I am trying to get a hang of it.
I have played around at http://jsfiddle.net/ExN6Q/ and gotten something that works like I want, but I am not super happy with the result for the services.
If I have the following html:
<div ng-app="myApp">
<div ng-controller=myController1>
Fill in this field: <input type="text">
<input type="submit" value="Done" ng-click="submit()">
</div>
<div ng-controller=myController2 ng-show="done1">
And this field: <input type="text">
<input type="submit" value="Done" ng-click="submit()">
</div>
<div ng-controller=myController3 ng-show="done2">
As well as this field: <input type="text">
<input type="submit" value="Done" ng-click="submit()">
</div>
</div>
and then the following controllers:
my_module.controller('myController1', function ($scope, Done1Service) {
$scope.submit = Done1Service.done;
});
my_module.controller('myController2', function ($scope, Done1Service, Done2Service) {
$scope.done1 = false;
$scope.submit = Done2Service.done;
Done1Service.get_promise().then(function () {
$scope.done1 = true;
});
});
my_module.controller('myController3', function ($scope, Done2Service, Done3Service) {
$scope.done2 = false;
$scope.submit = Done3Service.done;
Done2Service.get_promise().then(function () {
$scope.done2 = true;
});
Done3Service.get_promise().then(function () {
alert("Congratulations, you're done!");
});
});
then I am actually satisfied with the result, the "problem" is the implementation of
the services:
my_module.factory('Done1Service', function ($q) {
var deferred = $q.defer();
var get_promise_fn = function () {
return deferred.promise;
};
var done_fn = function () {
console.log("I'm done!");
return deferred.resolve(true);
};
return {
get_promise: get_promise_fn,
done: done_fn
};
});
my_module.factory('Done2Service', function ($q) {
... // identical except console.log("I'm done again!")
});
my_module.factory('Done3Service', function ($q) {
... // identical except console.log("I'm done at last!");
});
These feel a bit too boilerplatish, and I wonder if I am doing something wrong.
Could I create one common service and make three instances of it? Is this the normal way
to handle this by returning a promise from a dedicated get_promise function (
which I then assume would probably be called something else)?

As it is right now, your services are a one shot deal since promises cannot be reset. I think you could solve this problem by either using a single EventBus service through which every controllers communicates. E.g. The first controller sends a 'step1completed' message on the bus which is intercepted by the second controller, which does it's work and fires a 'step2completed' event, etc.
If you have many identical processes that have to run in parallel, you could allow creating multiple EventBus instances which would serve as independent communication channels for a specific set of objects.
Angular already allow you to emit or broadcast events through scopes, have a look at $emit and $broadcast.

Related

How to fill an AngularJS form loading data via XMLHttpRequest [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I have a form in an AngularJS controller, I would like to automatically fill the form with data collected with a XHTMLRequest.
Form has simply inputs with data-ng-model
page.html
<div id="PersonalAppID" data-ng-controller="PersonalAppCtrl as ctrl">
<form action="/" method="post" novalidate>
<div class="input-group">
<div class="form-group col-md-5">
<label>First name: </label>
<input name="fname" type="text" data-ng-model="ctrl.fname" placeholder="first name">
</div>
</div>
When controller init itself, it creates a request, downloads the data and put it into variables ctrl.variable
page.js
angular.controller('PersonalAppCtrl',function() { var ctrl = this;
ctrl.$onInit= function(){
var req=XMLHttpRequest();
req.onreadystatechange = function(){
if (req.status == 200&req.readyState==4){
var ret = convertJSON(req.responseText);
ctrl.fname = ret.FirstName;
return (true);
}
}
req.open();
req.send();
}
My problem is that input are filled only if user touch an input and then touch outside it.
I'd like to have form filled as soon as the page is loaded.
I also tried to use ng-load and ng-init but the behaviour is pretty the same in this case.
Any ideas?
Update your code like below:
angular.controller('PersonalAppCtrl',function() { var ctrl = this;
ctrl.$onInit= function(){
var req=XMLHttpRequest();
req.onreadystatechange = function(){
if (req.status == 200&req.readyState==4){
var ret = convertJSON(req.responseText);
$scope.$apply(function () {
ctrl.fname = ret.FirstName;
});
return (true);
}
}
req.open();
req.send();
}
You are making an XMLHTTPRequest which is a non angular code. Angular is not aware when the response is received so you need to make that angular aware by calling $scope.$apply()
Example:
function Ctrl($scope) {
$scope.message = "Waiting 2000ms for update";
setTimeout(function () {
$scope.message = "Timeout called!";
// AngularJS unaware of update to $scope
}, 2000);
}
But, if we wrap the code for that turn in $scope.$apply(), the change will be noticed, and the page is updated.
function Ctrl($scope) {
$scope.message = "Waiting 2000ms for update";
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
}
A very detailed explaination on this is given here - http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Its better to use Angular $http for all HTTP requests. So you dont have to bother about using $scope.$apply()

Angulrjs: A controller doesn't send a value via a factory with the "as" statement

I've been teaching myself how to use the as statement of Angularjs's controller, but struggling to make controllers communicate with others, using the as syntax.
<script type="text/javascript">
angular.module('angularApp', [])
.factory('MessageService', function(){
var message = {
addedItem: "initialMessge"
};
return {
returnMessage: message//This is supposed to be the "var message" defined above
};
})
.controller('DiaplayingProductController', function(MessageService){
var instance = this;
this.data = {
message: MessageService.returnMessage.addedItem
};
})
.controller('ProductController', function($scope, $http, MessageService) {
var instance = this;
this.data = {
message: MessageService.message,
//There are other stuff here
};
this.addItem = function(productName) {
$http({
//other tasks
}).then(function addSucces(response) {
instance.data.message.addedItem = productName;
});
};
});
<span ng-controller="DiaplayingProductController as dpc" ng-bind="dpc.data.message"></span>
<div ng-controller="ProductController as pc">
#foreach ($products as $index => $product)
<div class="product">
<button ng-click="pc.addItem({{$product->name}})>
Add it to Cart
</button>
</div>
#endforeach
</div>
I use Laravel, so {{$product->name}} and #foreach are Laravel's expression.
In a nutshell,
There are one <span> and multiple <button>s, based on the result of #foreach (Again, I use Laravel, so this is basically the same thing as php's foreach)
When one of the <button> is pressed, the content of <span> is supposed to be updated.
The event is triggered in ProductController, which is supposed to update message of DiaplayingProductController, via MessageService.
The message is not going to be sent to the span tag.
This question may be silly. However, there are not many information resources out there which deal with this as statements, so I'd like to ask some advice here. Thank you in advance!
What's this #foreach?
There's a coma in your attributes. Shouldn't be there.
The expression in your ng-click has a missing parenthesis. Also, it should be an expression, therefore the {{}} have nothing to do here.
The data object are not shared between the controllers. You should:
use directives and pass the data using attributes ('=').
set the data in the $scope, which is not as good a solution
use a service as an intermediary (each controller can set/get the value
from that service)

Use http cookie value in an Angular template

I have angular working in one of my ASP.NET MVC applications. I am using two html templates with Angular Routing. One is a list of current Favorites that comes from the database and is serialized into json from my Web API and used by angular to list those items from the database.
The second html template is a form that will be used to add new favorites. When the overall page that includes my angular code loads, it has a cookie named currentSearch which is holding the value of whatever the last search parameters executed by the user.
I would like to inject this value into my angular html template (newFavoriteView.html) for the value of a hidden input named and id'd searchString.
I have tried using jQuery, but had problems, plus I would much rather do this inside of angular and somehow pass the value along to my template or do the work inside the view(template). However, I know the latter would be bad form. Below is the code I think is important for one to see in order to understand what I am doing.
Index.cshtml (My ASP.NET VIEW)
#{
ViewBag.Title = "Render Search";
ViewBag.InitModule = "renderIndex";
}
<div class="medium-12 column">
<div data-ng-view=""></div>
</div>
#section ngScripts {
<script src="~/ng-modules/render-index.js"></script>
}
Setting the cookie in the MVC Controller
private void LastSearch()
{
string lastSearch = null;
if (Request.Url != null)
{
var currentSearch = Request.Url.LocalPath + "?" +
Request.QueryString;
if (Request.Cookies["currentSearch"] != null)
{
lastSearch = Request.Cookies["currentSearch"].Value;
ViewBag.LastSearch = lastSearch;
}
if (lastSearch != currentSearch)
{
var current = new HttpCookie("currentSearch", currentSearch){
Expires = DateTime.Now.AddDays(1) };
Response.Cookies.Set(current);
var previous = new HttpCookie("lastSearch", lastSearch) {
Expires = DateTime.Now.AddDays(1) };
Response.Cookies.Set(previous);
}
}
}
render-index.js
angular
.module("renderIndex", ["ngRoute"])
.config(config)
.controller("favoritesController", favoritesController)
.controller("newFavoriteController", newFavoriteController);
function config($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "/ng-templates/favoritesView.html",
controller: "favoritesController",
controllerAs: "vm"
})
.when("/newsearch", {
templateUrl: "/ng-templates/newFavoriteView.html",
controller: "newFavoriteController",
controllerAs: "vm"
})
.otherwise({ redirectTo: "/" });
};
function favoritesController($http) {
var vm = this;
vm.searches = [];
vm.isBusy = true;
$http.get("/api/favorites")
.success(function (result) {
vm.searches = result;
})
.error(function () {
alert('error/failed');
})
.then(function () {
vm.isBusy = false;
});
};
function newFavoriteController($http, $window) {
var vm = this;
vm.newFavorite = {};
vm.save = function () {
$http.post("/api/favorites", vm.newFavorite)
.success(function (result) {
var newFavorite = result.data;
//TODO: merge with existing topics
alert("Thanks for your post");
})
.error(function () {
alert("Your broken, go fix yourself!");
})
.then(function () {
$window.location = "#/";
});
};
};
favoritesView.html
<div class="container">
<h3>New Favorite</h3>
<form name="newFavoriteForm" ng-submit="vm.save()">
<fieldset>
<div class="row">
<div class="medium-12 column">
<input name="searchString" id="searchString" type="hidden"
ng-model="vm.newFavorite.searchString"/>
<label for="title">Name</label><br />
<input name="title" type="text"
ng-model="vm.newFavorite.name"/>
<label for="title">Description</label><br />
<textarea name="body" rows="5" cols="30"
ng-model="vm.newTopic.description"></textarea>
</div>
<div class="medium-12 column">
<input type="submit" class="tiny button radius" value="Save"/> |
Cancel
</div>
</div>
</fieldset>
</form>
</div>
My current attepts have been using jQuery at the end of the page after Angular has loaded and grab the cookie and stuff it in the hidden value. But I was not able to get that to work. I also thought about setting the value as a javascript variable (in my c# page) and then using that variable in angular some how. AM I going about this the right way?
Or should it be handled in the angular controller?...
I'm new to angular and the Angular Scope and a bit of ignorance are getting in the way. If any other info is needed I can make it available, thanks if you can help or guide me in the right direction.
You can do it by reading the cookie value using JavaScript, set it as a property of the $scope object and access it on the template.
//Inside your controllers
function favoritesController($http, $scope) {
//Get the cookie value using Js
var cookie = document.cookie; //the value is returned as a semi-colon separated key-value string, so split the string and get the important value
//Say the cookie string returned is 'currentSearch=AngularJS'
//Split the string and extract the cookie value
cookie = cookie.split("="); //I am assuming there's only one cookie set
//make the cookie available on $scope, can be accessed in templates now
$scope.searchString = cookie[1];
}
EXTRA NOTE
In AngularJS, the scope is the glue between your application's controllers and your view. The controller and the view share this scope object. The scope is like the model of your application. Since both the controller and the view share the same scope object, it can be used to communicate between the two. The scope can contain the data and the functions that will run in the view. Take note that every controller has its own scope. The $scope object must be injected into the controller if you want to access it.
For example:
//inject $http and $scope so you can use them in the controller
function favoritesController($http, $scope) {
Whatever is stored on the scope can be accessed on the view and the value of a scope property can also be set from the view. The scope object is important for Angular's two-way data binding.
Sorry if I'm misunderstanding or over-simplifying, but...assuming JavaScript can read this cookie-value, you could just have your controller read it and assign it to a $scope variable?
If JavaScript can't read the value, then you could have your ASP write the value to a JavaScript inline script tag. This feels yuckier though.
Update to show controller-as example.
Assuming your HTML looked something vaguely like this:
<div ng-controller="MyController as controller">
<!-- other HTML goes here -->
<input name="searchString" id="searchString" type="hidden" ng-model="controller.data.currentSearch"/>
Then your controller may look something like this:
app.controller('MyController', function ($scope, $cookies) {
$scope.data = {
currentSearch: $cookies.currentSearch
};
// Note that the model is nested in a 'data' object to ensure that
// any ngIf (or similar) directives in your HTML pass by reference
// instead of value (so 2-way binding works).
});

How to create own angular service with XHR properly?

I am very new about AngularJS things. Need to do file upload with other datas in form, I found some scripts and angular plugins but I am using my own service calls $xhr. I was able to send file but i got error, bug(not real error-bug, i just named like that) or i can not use AngularJS properly. Here it is:
.
JS
var app = angular.module('ngnNews', []);
app.factory('posts', [function () {...}]); // I reduced the codes
app.factory('$xhr', function () {
var $xhr = { reqit: function (components) { ... //My Xml HTTP Request codes here }}
return $xhr;
});
app.controller('MainCtrl', ['$http','$scope','$xhr','posts',
function ($http, $scope, $xhr, posts) {
$scope.posts = posts.posts;
$scope.files = [];
var newPost = { title: 'post one', upvotes: 20, downvotes: 5 };
$scope.posts.push(newPost);
$scope.addPost = function () {
$xhr.reqit({
form: document.getElementById('postForm'),
callbacks: {
success: function (result) {
if (result.success) {
console.log($scope.posts); //[FIRST OUT]
$scope.posts.push(result.post);
$scope.title = '';
console.log($scope.posts); //[SECOND OUT]
}
}
},
values: { upvotes: 0, downvotes: 0 },
files: $scope.files
});
...
}
}]);
.
HTML
<form action="/Home/FileUp" id="postForm" method="post" enctype="multipart/form-data">
<div class="form-group input-group">
<span class="input-group-addon">Post Title</span>
<input name="title" class="form-control" type="text" data-ng-model="title" />
</div>
<ul>
<li ng-repeat="file in files">{{file.name}}</li>
</ul>
<button class="btn btn-primary" type="button" data-ng-click="addPost()">Add New</button>
</form>
SCREEN
Sample post displayed in list
.
PROBLEMS
When I click first time Add New button everything works well until $scope.posts.push(result.post);. In console, [SECOND OUT] is here:
First object has $$hashKey but second object which sent from server(added by $scope.posts.push(result.post); function) doesn't have. I want to know why is this happening? But it's not only weird thing, when I second time click Add New button, everything completed successfully (No new logs in console, adding new post to list shown screen image above).
MAIN PROPLEM
I pushed returned value from the server but post list(in screen) is not affected when first click.
QUESTIONS
- What is happening? or
- What am I doing wrong? Thanks for any explanation.
You are doing nothing wrong with respect to $$hashkey if that is your concern. When you use ng-repeat with array of objects angular by default attaches a unique key to the items which is with the property $$hashkey. This property is then used as a key to associated DOM elements with the corresponding item in the array by identity. Moving the same object in array would move the DOM element in the same way in the DOM. You can avoid this (addition of additional property on the object by angular) by using track by with ng-repeat by providing a unique key on the object or a mere $index. So with that instead of creating a unique key and attaching it to $$haskey property angular will use the unique identifier you have provided to associate the DOM element with the respective array item.
ng-repeat="post in posts track by $index"
or (id you have a unique id for each of the object in the array, say id then)
ng-repeat="post in posts track by post.id"
And since you say you are using my xml http request code here, i am assuming it is not within the angular context so you would need to manually perform the digest cycle by using $scope.$apply() is on of those ways.
$scope.addPost = function () {
$xhr.reqit({
form: document.getElementById('postForm'),
callbacks: {
success: function (result) {
if (result.success) {
$scope.posts.push(result.post);
$scope.title = '';
$scope.$apply();//<-- here
}
}
},
But ideally you could wrap your xhr implementation with a $q and if you pass $q promise from your api, you wont need to perform a manual $scope.$apply() everywhere. Because $q promise chaining will take care of digest cycle invocation.

Meteor/Iron Router - When/Where to run JS to swap to WYSIWYG

I'm trying to figure out where is the best place to run a jQuery plugin that replaces a textarea (with a reactive value). It needs to be called after the textarea has been assigned the value.
I've tried various places. The most correct place I've tried seems to be in the templates onAfterAction callback, in a Meteor.defer. This works about 95% of the time.
Something like this:
MyController = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('post', this.params._id);
},
onAfterAction: function () {
Meteor.defer(function () {
$('.make-wysiwyg').wysiwyg();
});
}
});
However, occasionally it doesn't. If I start bouncing between posts really quick, occasionally one will apparently run before the textarea has data and fail to display property (it'll be empty, because it needs the value before wysiwyg() is called).
I've eliminated the wysiwyg() function itself as the culprit by replacing that line with:
$('.make-wysiwyg').each(function () {console.log($(this).val())});
And I can clearly see every so often it'll print empty value fields for no apparent reason.
I'm not sure if the template or publish() function could be a culprit, so I'll supply them as well.
Any ideas greatly appreciated.
Template:
<template name="adminPostsEdit">
<h1>Edit Post</h1>
<form id="edit-post" class="{{isNewClass}}" method="post">
<label for="post-title">Title</label>
<input id="post-title" value="{{post.title}}">
<label for="post-slug">Slug</label>
<input id="post-slug" value="{{post.slug}}">
<label for="post-content">Content</label>
<textarea id="post-content" class="make-wysiwyg">{{post.content}}</textarea>
<label for="post-excerpt">Excerpt</label>
<textarea id="post-excerpt" class="make-wysiwyg">{{post.excerpt}}</textarea>
{{#if post.published}}
<button value="save">Save</button>
<button value="unpublish">Unpublish</button>
{{else}}
<button value="save">Save Draft</button>
<button value="publish">Publish</button>
{{/if}}
<button value="cancel">Cancel</button>
</form>
</template>
publish():
Meteor.publish('post', function (id) {
return Posts.find({_id: id});
});
Helpers:
Template.adminPostsEdit.helpers({
post: function () {
return Posts.findOne();
},
isNewClass: function () {
return !this.id ? 'new' : '';
}
});
You should do that in the template's render function.
Template.adminPostsEdit.rendered = function() {
$('.make-wysiwyg').wysiwyg();
})

Categories

Resources