Custom directive link executes before response.data gets its data - angularjs

I have created custom directive as below,
.html:
<input type="text" placeholder="Search" ng-model="selected" typeahead="Customer.CompanyName for Customer in typeaheadSrc | filter:$viewValue" />
.directive:
module.directive("typeahead", [
function () {
return {
restrict: "E",
templateUrl: typeahead.html,
link: function (scope, element, attrs) {
scope.typeaheadSrc = scope.$eval(attrs.datasource);
}
};
}
]);
Above code is just a custom directive where I can use it anywhere. Only thing it does is take datasource (here I use it as attrs.datasource) to display in typeahead.
Below is the way I use it in html I want,
<div class="col-md-10">
<typeahead datasource={{GetAllCustomers}}></typeahead>
</div>
And it pass the data from controller to html like this,
$scope.GetAllCustomers = [
{
"CompanyName ": "Customer1"
},
{
"CompanyName ": " Customer2"
},
{
"CompanyName ": " Customer3"
},
{
"CompanyName ": " Customer4"
}
];
Above code works fine where GetAllCustomers is passed as datasource of custom directive attrs.datasource field above.
Problem is when I use webapi to get data instead of static data in GetAllCustomers. I use factory and service to get data as below,
module.controller("customerController", [“$scope”, "customerFactory",
function ($scope, customerFactory) {
$scope.GetAllCustomers = customerFactory.GetAllCustomers().then(function (result) {
return result;
},
function (error) {
console.log("GetAllCustomers failed");
});
]);
module.factory("customerFactory", [
"customerService",
"$q",
function (customerService, $q) {
var customerObj = {};
var deferred = $q.defer();
customerObj.GetAllCustomers = function () {
return customerService.GetAllCustomers()
.then(function (response) {
deferred.resolve(response.data);
return deferred.promise;
}, function (response) {
deferred.reject(response);
return deferred.promise;
});
};
return customerObj;
}]);
module.service("customerService", [
"$http",”$scope”
function ($http,$scope) {
$scope.GetAllCustomers = function () {
var tempUrl = TEMPLATES_PATH.customer_api;
var request = {
method: GET,
url: tempUrl
};
return $http(request, { withCredentials: true });
};
}
]);
In above code also GetAllCustomers will get value from webapi, but the problem is response will be loaded only after custom directive is executed. Hence attrs.datasource value will be null.
Now the flow is,
Factory request
Typeahead custom directive
Factory response
Custom directive should be executed after factory response, but it will execute after request is done and before response is given back.
Flow should be like below,
Factory request
Factory response
Typeahead custom directive
Please tell me how to do this for above code. I am not able to create fiddle for webapi. Please tell me how to do this. I tried using promise, but its not working for me. I want to know where to write promise if it is needed.

Change like this, i think it will work
first at the directive
<typeahead datasource={{GetAllCustomersValue}} ng-if="GetAllCustomersValue"></typeahead>
second at the factory part
$scope.GetAllCustomers = customerFactory.GetAllCustomers().then(function (result) {
$scope.GetAllCustomersValue=result;
return $scope.GetAllCustomersValue;
},
function (error) {
console.log("GetAllCustomers failed");
});
]);

Related

Typeahead is not working when user types very fast

I am using typeahead where on typing its showing suggestions in a search box, while suggestions are getting from the server.
Its working fine except when user types really fast. For example if we type storm it's showing records. When type same word with speed, it's not showing its suggestions while I am getting data in response. I have checked by printing JSON just above the box so when I write storm fastly its showing JSON but not showing below suggestions.
Here is the html
<input type="text" ng-model="header.search"
typeahead-on-select="searchClicked($item)"
uib-typeahead="state as state.data.name for state in suggestions | filter:$viewValue | limitTo:8"
typeahead-min-length="0" placeholder="Søg..." search-products>
search-products is the directive use to broadcast search values. Here is the directive code.
APP.directive('searchProducts', searchProducts);
function searchProducts($state) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$watch(attrs.ngModel, function(searchVal) {
scope.$broadcast('searchTheProducts', searchVal);
});
}
};
}
Here is the service call where we are getting data.
$scope.$on('searchTheProducts', function(event, query) {
if (query) {
headerService.getSearchSuggestions(query).then(
function(response) {
$scope.suggestions = response;
},
function(err) {
console.log('Error: ' + err);
}
);
}
});
here is the service logic
function getSearchSuggestions(query) {
pendingRequests.cancelAll();
var url = ApiBaseUrl + "/search/suggestions?query=" + query;
var deferred = $q.defer();
pendingRequests.add({
url: url,
canceller: deferred
});
pending = true;
$http({
method: "GET",
url: url,
timeout: deferred.promise
}).then(
function(successData) {
deferred.resolve(successData.data.data);
},
function(err) {
deferred.reject(err);
}
);
return deferred.promise;
}
I don't think you need a custom directive to fire the $http call to fetch the results whenever your input value changes.Instead you can do as below
<input type="text" ng-model="header.search"
typeahead-on-select="searchClicked($item)"
uib-typeahead="state as state.data.name for state in suggestions($viewValue) | filter:$viewValue | limitTo:8"
typeahead-min-length="0" placeholder="Søg..." >
In your JS controller you can write the function $scope.suggestions() to fetch the new results on query type.
var app = angular.module('plunker', ['ui.bootstrap']);
app.factory('dataProviderService', ['$http', function($http) {
var factory = {};
factory.getCities = function(input) {
//I've used google api to fetch cities ..we can place our service call here..
return $http.get('//maps.googleapis.com/maps/api/geocode/json', {
params: {
address: input,
sensor: false
}
}).then(function(response) {//on success the results can be further processed as required..
return response.data.results.map(function(item) {
return item.formatted_address;
});
});
};
return factory;
}]);
app.controller('TypeaheadCtrl', ['$scope', '$log','$http', 'dataProviderService', function($scope, $log,$http, dataProviderService) {
$scope.suggestions= function(viewValue) {
//you can call your own service call via a factory
return dataProviderService.getCities(viewValue);
};
}]);
Here is the DEMO of the working sample for the above mentioned way,hope this helps you.In this approach no mater how fast you type you will always fetch fresh results instantly.
I'm not quite sure why you have:
angular.forEach(response, function(val, key) {
$scope.suggestions= response;
});
If response is 100 items, then for some reason you're updating $scope.suggestions each time and this may be causing the problem. Simply remove the angular.forEach and just have $scope.suggestions = response instead.
You could use the typeahead-wait-ms attribute, so that you are not querying on every ketstroke:
<input typeahead-wait-ms="50">

Inject different data to same controller

I have a controller that will have identical functionality to another by manage different data. I am new to angularJS so I'm not exactly sure how to proceed. I've read about services and factories but have only seen examples of injecting the same data across different controllers instead of different data to the same controller. Any help to point me in the right direction is appreciated.
angular.module("myApp")
.controller("AirlineController", function () {
this.Airlines = getAirlines(); //some service call that will be ajax eventually
});
angular.module("myApp")
.controller("CitiesController", function () {
this.Cities = getCities();//some service call that will be ajax eventually
});
angular.module("myApp")
.controller("GenericController", function () {
$('.selected-items-box').bind('click', function (e) {
e.stopPropagation();
$('.image-select-wrapper .list').toggle('slideDown');
});
$(document).bind('click', function () {
$('.image-select-wrapper .list').slideUp();
});
this.ListObject = getAirlines();//this list should be populated from one of the other controllers
this.toggleSelected = function (selectedItem) {
angular.forEach(this.ListObject, function (ListItem) {
ListItem == selectedItem ? ListItem.selected = true : ListItem.selected = false;
});
};
this.getSelectedItem = function (item) {
return item.selected;
};
});
Use function parameters to making a factory more versatile.
app.factory("getGeneric", function($http) {
var apiUrl = "http:/my.com/api/"
//Use function parameter
return function (arg1) {
//return promise
return $http.get(apiUrl + arg1);
}
});
Then in your controllers.
app.controller("AirlineController", function (getGeneric) {
var vm = this;
//use function parameter
var airlinesPromise = getGeneric("Airlines"); //service returns promise
airlinesPromise.then( function onFulfilled(response) {
vm.Airlines = response.data;
});
});
app.controller("CitiesController", function (getGeneric) {
var vm = this;
//use function parameter
var citiesPromise = getGeneric("Cities"); //service returns promise
citiesPromise.then( function onFulfilled(response) {
vm.Cities = response.data;
});
});
Please notice that most servive APIs are asynchronous and do not return data immediately. The AngularJS $http service returns promises and data needs to be extracted from the promise with its .then method.
Another point is make factories generic and make controllers lean and specific. Controllers should be lean and specific to their HTML.
You can certainly achieve that. You can have a factory/service that has the methods with parameters that you can pass from the controller. For example I have two controllers and one service that both the controllers are calling.
Based on the the parameter values passed, the service will return different set of data. I'm using the $scope but you can use this but the idea remains the same.
angular.module('SelectOptionModule')
.controller('AirlineController', function ($scope, AirlineService) {
$scope.Airline = AirlineService.GetAirLines("a")
});
angular.module('SelectOptionModule')
.controller('Airline2Controller', function ($scope, AirlineService) {
$scope.Airline = AirlineService.GetAirLines("b")
});
angular.module('SelectOptionModule')
.factory('AirlineService', AirlineService);
function AirlineService() {
function GetAirLines(value) {
if (value == "a")
{
return [{ "Id" : "1", "Name" : "AA" } ]
}
if (value == "b") {
return [{ "Id": "2", "Name": "Delta" }]
}
}
return {
GetAirLines: GetAirLines
};
}
The View can be like to test this out.
<div ng-app='SelectOptionModule' >
<div ng-controller="AirlineController">
{{ Airline }}
</div>
<div ng-controller="Airline2Controller">
{{ Airline }}
</div>
</div>

error while using a factory to return data to controller

I am newbie to angular and I am fetching data from json file using a service and then returning the data to controller. When i click the button the controller method is not getting executed and there are no errors in console.log. what am i missing here?
My service code:
Service.js
app.factory('MovieService', function ($http) {
var url = "js/data.json";
var data;
return {
getData: function() {
return $http.get(url)
.success(function(response) {
data = response;
return data;
})
.error(function (error){
console.log("error");
})
}
};
});
Controller.js
app.controller('MainController1', ['$scope', '$log','$location','MovieService', function($scope,$log,$location,MovieService) {
console.log("click");
var getData = function() {
// This service's function returns a promise
MovieService.getData()
.then(function(data) {
// promise fulfilled
console.log("controller data");
$scope.custdata = data;
console.log($scope.custdata);
}, function(error) {
// promise rejected, could log the error with:
console.log("error");
});
};
}])
index.html
<div class="main" ng-controller="MainController1 as main">
<input type="button" name="getdata" value ="Get data" ng-click="main.getData ()"></input>
</div>
data
[
{
"Id": "1",
"Name": "Harry Potter"
},
{
"Id": "2",
"Name": "Jurassic Park"
}
]
You need to bind controller function on scope.
$scope.getData = function() { }"
instead
var getData = function() { }
and call it in template like
ng-click="getData ()"
You are using the controller as alias syntax.
In this case, your controller functions that need to be accessed from the view should be assigned to this.
So, define your function as a property of this and not as an independent function - like so:
this.getData = function () {...}
You are using var getData which will make the function a local function and not expose it.
Few things should be notice:-
1) You should use this instead of var to bind the function to the controller in controller as syntax:-
this.getData = function() {//your logic}
2) You are wrapping promise twice first in success() or error() then in another then() function instead do it like this:-
In service:-
getData: function() {
return $http.get(url);
}
In controller:-
MovieService.getData()
.then(function(response) {
// promise fulfilled
console.log("controller data");
$scope.custdata = response.data;
console.log($scope.custdata);
}, function(error) {
// promise rejected, could log the error with:
console.log("error");
});
3) <input> should not close like </input> it is not having closing tag.
Hope it help :)
PLUNKER

how to call a function in directive after receiving data from two different services?

I have lot of service call in my controller, after getting the data from those service, i want to trigger a function in directive.
// This is my controller
function myctrl() {
Serv.query({"type":"PageDetails"}, request.pageDetails, function(data) {
if(data.status=="SUCCESS") {
$scope.display.pageDetails = data.list;
Serv.query({"type":"userDetails"}, request.userDetails, function(data) {
if(data.status=="SUCCESS") {
$scope.display.userDetails = data.list;
$scope.loaded = true;
}
});
}
});
}
// This is my service factory
app.factory('KYC',function($resource) {
return $resource('serviceUrl/:type',{}, {
query: {method: 'POST', isArray: false},
get: {method: 'GET', isArray: false}
});
});
// this is my directive
app.directive('showSlowly',function() {
return {
restrict: 'EA',
link: function(scope, element, attrs){
scope.$watch('loaded', function(isLoaded) {
if(isLoaded) {
setTimeout(function() {
$(element).animate({height:"140px"}, 'slow', function(){
$(element).css({overflow:"visible"});
});
}, 300);
}
});
}
};
});
This is not the best way.
Can any one help to get this done with $q. Thanks in advance.
In your example, it seems like you want code to execute once the data has loaded. It seems like your code should be working as is, but if you want to try something different that uses $q, then you can make $scope.loaded a promise. Your other code can reference that promise, and pass it methods to run when the promise is resolved (which is a lot like setting up a $watch to run a method when the value changes)
Controller code:
Very similar to what you have now... first, make sure you're injecting $q in your controller/app. Then, create a promise and store it in loaded:
var loadedDefer = $q.defer();
$scope.loaded = loadedDefer.promise;
Then inside your query success, instead of setting loaded = true, use
loadedDefer.resolve(true);
(you could pass any object to the resolve method, but I chose true because that's what your current code is already looking for.)
Directive code:
Instead of using $watch, you can use the promise directly.
$scope.loaded.then( function(isLoaded){...} );
This setup works very similarly to what you've already done, but it's good to get familiar with $q and use it when it's appropriate.
At last i am able to achieve this with $q.all().
//this is my controller code
function myctrl($q, $scope) {
var deferredObjs = {
"d1":$q.defer,
"d2":$q.defer
}
$scope.promises = { "p1":d1.promise,"p2":d2.promise};
Serv.query({"type":"PageDetails"}, request.pageDetails, function(data) {
if(data.status=="SUCCESS") {
$scope.display.pageDetails = data.list;
$scope.apply(function() {
deferredObjs.d1.resolve();
}
}else {
$scope.apply(function() {
deferredObjs.d1.reject();
}
}
});
Serv.query({"type":"userDetails"}, request.userDetails, function(data) {
if(data.status=="SUCCESS") {
$scope.display.userDetails = data.list;
$scope.apply(function() {
deferredObjs.d2.resolve();
}
}else {
$scope.apply(function() {
deferredObjs.d2.reject();
}
}
});
}
// this is my directive
app.directive('showSlowly',function($q) {
return {
restrict: 'EA',
link: function(scope, element, attrs){
$q.all([scope.promises.p1.then(function(){}), scope.promises.p2.then(function(){}]).then(function() {
setTimeout(function() {
$(element).animate({height:"140px"}, 'slow', function(){
$(element).css({overflow:"visible"});
});
}, 300);
});
}
};
});

Passing argument(s) to a service in AngularJs

I am trying to configure my first tidbits of the AngularJs for a trivial stuff, but unfortunately unsuccessful at it after considerable amount of time.
My Premise:
Users select one of the options from a dropdown and have an appropriate template loaded into a div below the select. I have set up the service, a custom directive (by following the ans by #Josh David Miller on this post, and a controller in place. The ajax call in service is working fine except that the params that I pass to the server is hardcoded. I want this to be the 'key' from the dropdown selected by user. At the moment I am failing to have this code passed to the service.
My configuration:
var firstModule = angular.module('myNgApp', []);
// service that will request a server for a template
firstModule.factory( 'katTplLoadingService', function ($http) {
return function() {
$http.get("${createLink(controller:'kats', action:'loadBreedInfo')}", {params:{'b1'}}
).success(function(template, status, headers, config){
return template
})
};
});
firstModule.controller('KatController', function($scope, katTplLoadingService) {
$scope.breed = {code:''}
// here I am unsuccessfully trying to set the user selected code to a var in service,
//var objService = new katTplLoadingService();
//objService.breedCode({code: $scope.breed.code});
$scope.loadBreedData = function(){
$scope.template = katTplLoadingService();
}
});
firstModule.directive('showBreed', function ($compile) {
return {
scope: true,
link: function (scope, element, attrs) {
var el;
attrs.$observe( 'template', function (tpl) {
if (angular.isDefined(tpl)) {
el = $compile(tpl)(scope);
element.html("");
element.append(el);
}
});
}
};
})
and the HTML setup is
<form ng-controller="KatController">
<select name="catBreeds" from="${breedList}" ng-change="loadBreedData()"
ng-model="breed.code" />
<div>
<div show-breed template="{{template}}"></div>
</div>
</form>
I need the currently hardcoded value 'b1' in the $http ajax call to be the value in $scope.breed.code.
Your ajax request is async while your controller behaves as if the request were sync.
I assume that the get request has everything it needs to perform right.
First pass a callback to your service (note the usage of fn):
firstModule.factory( 'katTplLoadingService', function ($http) {
return {
fn: function(code, callback) { //note the callback argument
$http.get("${createLink(controller:'kats', action:'loadBreedInfo')}",
params:{code: code}}) //place your code argument here
.success(function (template, status, headers, config) {
callback(template); //pass the result to your callback
});
};
};
});
In your controller:
$scope.loadBreedData = function() {
katTplLoadingService.fn($scope.breed.code, function(tmpl) { //note the tmpl argument
$scope.template = tmpl;
});
}
Doing so your code is handling now your async get request.
I didn't test it, but it must be doing the job.
I think you defined the factory not in right way. Try this one:
firstModule.factory('katTplLoadingService', ['$resource', '$q', function ($resource, $q) {
var factory = {
query: function (selectedSubject) {
$http.get("${createLink(controller:'kats', action:'loadBreedInfo')}", {
params: {
'b1'
}
}).success(function (template, status, headers, config) {
return template;
})
}
}
return factory;
}]);
firstModule.controller('KatController', function($scope, katTplLoadingService) {
$scope.breed = {code:''}
$scope.loadBreedData = function(){
$scope.template = katTplLoadingService.query({code: $scope.breed.code});
}
});

Resources