AngularJS Model Binding and passing model value to a Controller Method - javascript

I am adding Social Logins to my web app. Now I do a get on our webapi to get the available logins and then use an ng-repeat to list the buttons.
I have the following service;
var _getExternalProviders = function () {
var returnUrl = "#";
var externalProviderUrl = ngAuthSettings.apiServiceBaseUri + "api/Account/ExternalLogins?returnurl=" + returnUrl
+ "&generateState=true";
return $http.get(externalProviderUrl).then(function (results) {
return results;
});
};
i then call this service from my controller;
authService.getExternalProviders().then(function (results) {
$scope.externalProviders = results.data;
},
function (err) {
$scope.message = err.error_description;
});
and my view is as follows;
<div data-ng-controller="loginController">
<div data-ng-repeat="provider in externalProviders">
<button class="btn btn-large btn-{{provider.name.toLowerCase() == 'microsoft' ? 'windows' : provider.name.toLowerCase()}} btn-block" type="button" data-ng-click="authExternalProvider('{{provider.name}}')"><i class="fa fa-{{provider.name.toLowerCase() == 'microsoft' ? 'windows' : provider.name.toLowerCase()}}"></i> | Connect with {{provider.name}}</button>
</div>
</div>
(which is added to the parent view using an ng-include)
<div ng-include="'app/views/externalProviders.html'">
</div>
Now this is working and the buttons are returning and rendering great, and when I inspect the html
data-ng-click="authExternalProvider('{{provider.name}}')"
is rendering as
data-ng-click="authExternalProvider('Google')"
for example, however when i click the element the function is being passed '{{provider.name}}' as a string instead.
The cothroller method for the ng-click is as follows;
$scope.authExternalProvider = function (provider) {
console.log(provider);
var redirectUri = location.protocol + '//' + location.host + '/authcomplete.html';
var externalProviderUrl = ngAuthSettings.apiServiceBaseUri + "api/Account/ExternalLogin?provider=" + provider
+ "&response_type=token&client_id=" + ngAuthSettings.clientId
+ "&redirect_uri=" + redirectUri;
window.$windowScope = $scope;
var oauthWindow = window.open(externalProviderUrl, "Authenticate Account", "location=0,status=0,width=600,height=750");
};
Can anyone tell me what I am doing wrong please?

data-ng-click="authExternalProvider('{{provider.name}}')"
gets interpreted as JavaScript so what you really want is
data-ng-click="authExternalProvider(provider.name)"

Related

ReferenceError: lecturerFac is not defined

When I load the html page, my controller retrieves data from an API end point regarding a course. The page gets populate with the data about the course. But at the same time I want to populate part of the page with data about the lecturer of the course (their image, name , description etc ...). I pass the lecturer name to the method using the ng-init directive but I get a
ReferenceError: lecturerFac is not defined.
I am not sure but I believe the issue is the way I am calling the getLecturer() function using the ng-init directive.
What I want to happen when the page loads is have the Lecturer's details displayed on the page along with the course details.
courses.html
<div class="container" ng-controller="CoursesDetailsCtrl">
<div class="row" >
<div class="col-4" ng-model="getLecturer(courses.lecturer)">
<div>
<h3>{{lecturer.name}}</h3>
<<div>
<img class="img-circle" ng-src="{{lecturer.picture}}" alt="" />
</div>
<p>{{lecturer.description}}</p> -->
</div>
</div>
<div class="col-8">
<div class="myContainer" >
<h2>{{courses.name}}</h2>
<div class="thumbnail">
<img ng-src="{{courses.picture}}" alt="" />
</div>
<div>
<p>{{courses.description}}</p>
</div>
</div>
</div>
</div>
</div>
CoursesDetailsCtrl
todoApp.controller('CoursesDetailsCtrl', ['coursesFac','lecturerFac','$scope','$stateParams', function CoursesCtrl(coursesFac, lecturerFac, $scope, $stateParams){
$scope.getLecturer = function(name){
lecturerFac.getLecturerByName(name)
.then(function (response) {
$scope.lecturer = response.data;
console.log($scope.lecturer);
}, function (error) {
$scope.status = 'Unable to load lecturer data: ' + error.message;
console.log($scope.status);
});
};
}]);
lecturerFac
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
lecturerFac.getLecturer = function () {
return $http.get(urlBase);
};
lecturerFac.getLecturerByName = function (name) {
return $http.get(urlBase + '/' + name);
};
return lecturerFac;
}]);
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
var service = {};
service.getLecturer = function () {
return $http.get(urlBase);
};
service.getLecturerByName = function (name) {
return $http.get(urlBase + '/' + name);
};
return service;
}]);
i Think the cause of this error is the lecturerFac variable is not initialize in the factory. Create an empty object call lecturerFac in the factory and return it.
todoApp.factory('lecturerFac', ['$http', function($http) {
var urlBase = '/api/lecturer';
var coursesFac = {};
var lecturerFac= {};/////////////////////
lecturerFac.getLecturer = function() {
return $http.get(urlBase);
};
lecturerFac.getLecturerByName = function(name) {
return $http.get(urlBase + '/' + name);
};
return lecturerFac;
}]);
Also avoid calling functions inside the ng-model. When you bind something with ng-model it must be available for both reading and writing - e.g. a property/field on an object. use ng init instead

Declare function inside scope and call it later

Calling the callService function fails. Instead none of my console messages are showing in the console except for 'making a controller....'. I'm using the directive ng-click="callService()" to make the call from an HTML button. I'm new to angular, can someone point me in the right direction? Code is below.
(function() {
console.log('making a controller....');
'use strict';
angular.module('myModule').controller('myController', myController);
myController.$inject = ['$scope','$http'];
function myController($scope, $http) {
console.log("controller initialized...");
$scope.callService = function(){
console.log("callService called...");
var urlSearchService = 'http://domain/proj/rs/stuff/moreStuff';
var skuVal = $scope.skuField;
var mVenVal = $scope.mVendorField;
//need to somehow specifiy that xml is a #FormParam
var xmlItemSearchRequest = "<ItemSearchRequest>"
+"<skuid>" + skuVal + "</skuid>"
+"<mvendor>" + mVenVal + "</mvendor>"
+"</ItemSearchRequest>";
console.log('calling: ' + urlSearchService + 'sending xml: ' + xmlItemSearchRequest);
$http.post(urlSearchService, xmlItemSearchRequest).
success(function(data){
$scope.searchResults = data;
console.log('call to ' + urlSearchService + ", was a success.");
}).error(function(data, status) {
console.error('Calling error', status, data);
});
};
};
})();
You are declaring the callService function inside the scope of the controller function, so it won't be accessible from the $scope. You need to add it to the $scope in order to be able to use it in your templates.
Instead of:
var callService = function(){
Do:
$scope.callService = function(){
As per your latest comment, you are not binding correctly the controller.
This:
<div data-ng-controller="inventorySearchController"><input type="button" class="btn btn-primary btn-lg" ng-click="callService()" value="Search" /></div> –
Should be:
<div data-ng-controller="myController"><input type="button" class="btn btn-primary btn-lg" ng-click="callService()" value="Search" /></div> –

Refresh a ng-repeat list from server every x seconds

As the title suggests I want to be able to refresh a ng-repeat list from the server every 30 seconds or so. More data can be added on the backend, so I want my list to reflect that. Right now I have the regular $http.get( ) working fine which is here:
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
};
and the portion of the web page it supplies is:
<div style="margin: 1em">
<h4>Search</h4>
<div role="form">
<!-- start dropdown -->
<div class="form-group">
<select class="form-control" ng-options="model as model.name for model in allModels" ng-model="selectModel" ng-change="select()">
<option value="">Choose Model</option>
</select>
</div>
<!-- /end dropdown-->
<div class="form-group">
<input id="start_date" type="text" class="form-control" placeholder="Threat Date">
</div>
</div>
<div>
<table class="table table-hover table-striped" ng-show="records">
<thead>
<th>#</th>
<th>Name</th>
<th>Score</th>
</thead>
<tr data-ng-repeat=" item in records | orderBy : '-score' | limitTo : 10 " ng-click="moreInfo(item)">
<td>{{$index+1}}</td>
<td>{{item.name.slice(5)}}</td>
<td>{{item.score.toFixed(3)}}</td>
</tr>
</table>
</div>
</div>
Is there a way to choose a time # which the list will refresh? And it has to be without hitting a refresh button or something like that. Thanks in advance.
EDIT This is the error I get when I try and use $interval as suggested:
ReferenceError: $interval is not defined
at Scope.$scope.select (http:xxxxxxxxxx.com/UserQuery/js/script.js:24:7)
at fn (eval at <anonymous> (https://code.angularjs.org/1.4.0-rc.0/angular.js:12822:15), <anonymous>:2:209)
at Scope.$get.Scope.$eval (https://code.angularjs.org/1.4.0-rc.0/angular.js:15465:28)
at https://code.angularjs.org/1.4.0-rc.0/angular.js:21825:13
at https://code.angularjs.org/1.4.0-rc.0/angular.js:24485:9
at forEach (https://code.angularjs.org/1.4.0-rc.0/angular.js:332:20)
at NgModelController.$$writeModelToScope (https://code.angularjs.org/1.4.0-rc.0/angular.js:24483:5)
at writeToModelIfNeeded (https://code.angularjs.org/1.4.0-rc.0/angular.js:24476:14)
at https://code.angularjs.org/1.4.0-rc.0/angular.js:24470:9
at validationDone (https://code.angularjs.org/1.4.0-rc.0/angular.js:24398:9)
SOLUTION With combined efforts from this and another question, I came to a solution. First off, like many on this question mentioned, the key here is the use of $interval. There are a few important things to not about using it though.
It must be included in the dependencies for the controller as
#mcpDESIGNS mentioned.
In my case, where there is a dropdown therefor multiple things I want
to $interval over, it is important to close one when you open a new
one.
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
$interval.cancel(mainInterval);
$scope.url = '';
url = 'http:xxxxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
mainInterval = $interval(function() {
console.debug("UPDATING....");
console.debug("GETTING NEW FROM " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
}, 5000);
};
Have a look at this:
https://docs.angularjs.org/api/ng/service/$interval
It wraps JavaScript's native setInterval function. You can set it to do the poll every 30 seconds.
It also returns a promise so you can cancel the interval when required.
However, please bear this in mind:
"Intervals created by this service must be explicitly destroyed when you are finished with them. In particular they are not automatically destroyed when a controller's scope or a directive's element are destroyed. You should take this into consideration and make sure to always cancel the interval at the appropriate moment. See the example below for more details on how and when to do this."
EDIT
Taking your code:
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
};
Try changing to this:
$scope.select = function() {
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
$interval(function() {
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
}, 30000);
};
If that works you can then refactor the actual $http.get out into a named function to remove the code smell.
Just use a $interval() around your $http to make it refresh every 30 seconds.
$interval(function () {
$http({
/* run your AJAX and update your $scope / etc */
});
}, 30000); // in milliseconds
Note: $interval must be dependency injected into your controller / service / etc to work!
// for examples sake
.controller('MyController', ['$interval', function ($interval) { }]);
Use ng-table instead.
Have a look http://bazalt-cms.com/ng-table/
And u can call its reload propery, which will refresh your table,
The reload you can call inside $timeout service provided by angular.
$timeout(function(){
tablename.reload();
},3000);
or
Just call the select function inside the timeout
$timeout(function(){
$scope.select();
},3000);
Try this:
Ok, I think the plan is each time select is fired, the existing interval needs to be cancelled and another one started. Have a look at this:
var intervalObj;
$scope.select = function() {
if (intervalObj !== null) {
$interval.cancel(intervalObj);
intervalObj = null;
}
$scope.searchText = '';
$scope.selectedItem = null;
var url = 'http:xxxxxxxxxxxx.com';
url += $scope.selectModel.name;
console.debug("GOING TO: " + url);
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
intervalObj = $interval(function() {
$http.get(url).success(function(data2) {
$scope.records = [];
data2.forEach(function(r) {
$scope.records.push(r);
});
});
}, 30000);
};
I have not been able to fully test this, but the principle is sound.

Why can't I do a $scope update within a jQuery scope?

I'm using typeahead to get some suggestions on an input text, this is inside a div controlled by an Angular controller.
The code for the suggestion tasks works with a jQuery plugin, so when I select, something I'm trying to assign a value to $scope, however this is NEVER happening.
I already tried getting the scope of the element with var scope = angular.element($("#providersSearchInput").scope() and then assign it as suggested here but it didn't work.
This is what I'm trying:
<div class="modal-body" ng-controller="ProvidersController" ng-init="orderReviewTab = 'observations'">
<input type="text" id="providersSearchInput" data-provide="typeahead" class="form-control input-md" placeholder="Buscar proovedores">
{{currentProvider}}
</div>
The controller looks like this:
tv3App.controller('ProvidersController', function($scope, $rootScope, $http, $timeout) {
var resultsCache = [];
$("#providersSearchInput").typeahead({
source: function (query, process) {
return $.get("/search/providers/?query=" + query, function (results) {
resultsCache = results;
return process(results);
},'json');
},
matcher: function (item) {
var name = item.name.toLowerCase();
var email = item.email.toLowerCase();
var contact_name = item.contact_name.toLowerCase();
//console.log(name);
var q = this.query.toLowerCase();
return (name.indexOf(q) != -1 || email.indexOf(q) != -1 || contact_name.indexOf(q) != -1);
},
scrollHeight: 20,
highlighter: function (itemName) {
var selected = _.find(resultsCache,{name:itemName});
var div = $('<div></div>');
var name = $('<span ></span>').html('<strong style="font-weight:bold">Empresa: </strong> ' + selected.name);
var contact = $('<span ></span>').html(' <strong style="font-weight:bold">Contacto: </strong> ' + selected.contact_name);
var email = $('<span ></span>').html(' <strong style="font-weight:bold">e-mail:</strong> ' + selected.email);
return $(div).append(name).append(contact).append(email).html();
},
minLength: 3,
items: 15,
afterSelect: function (item) {
console.log(item);
$scope.$emit('providerSelected',item);
}
});
$scope.$on('providerSelected', function (event,provider) {
console.log(provider);
$scope.currentProvider = provider;
$scope.$apply();
});
});
Edit
I tried this to check any changes:
$scope.$watch('currentProvider',function (newValue,oldValue) {
console.log(oldValue);
console.log(newValue);
});
So when selecting something it actually triggers and $scope.currentProvider seems to be updated but its never getting rendered at view ...
get https://angular-ui.github.io/bootstrap/
once you do, in your code make sure
angular.module('myModule', ['ui.bootstrap']);
and for typeahead have
<input type="text" ng-model="currentProvider" typeahead="provider for provider in getProviders($viewValue) | limitTo:8" class="form-control">
In your controller make sure you have
$scope.getProviders = function(val){
return $http.get('/search/providers/?query=' + val).then(function(response){
return response.data;
})
}
This should do the trick although I haven't tested

Grails non null params reaching controller as null?

I am having some trouble trying to get params from my GSP to my controller from a Javascript click handler that looks like this:
$('#save').click(function () {
var uniqueId = "${recordToEdit.uniqueId}";
var secondaryId = "${recordToEdit.secondaryId}";
console.log(removedYellowIssues);
<g:remoteFunction controller="customer"
action="saveModifiedIndividualRecord"
params='{uniqueId: uniqueId,
secondaryId: secondaryId,
yellowIssuesRemoved: removedYellowIssues,
redIssuesRemoved: removedRedIssues}'/>
});
When the "save" button is pressed this is what I see in the javascript console:
["No address provided."]
so you can see the the 'removedYellowIssues' list is NOT empty. It's a Javascript list containing one string. However, here is what my controller thinks:
<><><> Parameters ***:
<><><> uniqueId: 239400B
<><><> secondaryId: 1
<><><> Red issues removed: null
<><><> Yellow issues removed: null
Here is the controller action:
def saveModifiedIndividualRecord() {
println "<><><> Parameters ***: "
println "<><><> uniqueId: " + params.uniqueId
println "<><><> secondaryId: " + params.secondaryId
println "<><><> Red issues removed: " + params.redIssuesRemoved
println "<><><> Yellow issues removed: " + params.yellowIssuesRemoved
}
Here is more of the Javascript code containing the above save button snippet.
var currentYellowIndex = 0;
var allYellowIssues = $('#allYellowIssues'); // The unordered list 'ul'
var removedYellowIssues = []; // An array to keep track of issues removed
if (allYellowIssues.length) { // If there are issues to be displayed
var yellowElements = document.getElementsByName('yellowIssue');
var yellowListSize = yellowElements.length;
yellowElements[currentYellowIndex].className = "display";
$('#yellowStartIndex').html(currentYellowIndex + 1);
$('#yellowSizeIndex').html(yellowListSize);
$('#nextYellowIssue').click(function () {
if (currentYellowIndex < yellowListSize-1) {
yellowElements[currentYellowIndex++].className = "display-none";
yellowElements[currentYellowIndex].className = "display";
$('#yellowStartIndex').html(currentYellowIndex + 1);
}
});
$('#previousYellowIssue').click(function () {
if (currentYellowIndex > 0) {
yellowElements[currentYellowIndex--].className = "display-none";
yellowElements[currentYellowIndex].className = "display";
$('#yellowStartIndex').html(currentYellowIndex + 1);
}
});
$('#clearYellowFlag').click(function () {
removedYellowIssues.push(yellowElements[currentYellowIndex].innerHTML);
yellowElements[currentYellowIndex].className = "display-none";
yellowElements[currentYellowIndex].remove();
yellowListSize = yellowElements.length;
if (yellowListSize == 0)
$('#yellowIssues').hide();
else {
currentYellowIndex = 0;
yellowElements[currentYellowIndex].className = "display";
$('#yellowStartIndex').html(currentYellowIndex + 1);
$('#yellowSizeIndex').html(yellowListSize);
}
});
}
$('#save').click(function () {
var uniqueId = "${recordToEdit.uniqueId}";
var secondaryId = "${recordToEdit.secondaryId}";
console.log(removedYellowIssues);
<g:remoteFunction controller="customer"
action="saveModifiedIndividualRecord"
params='{uniqueId: uniqueId,
secondaryId: secondaryId,
yellowIssuesRemoved: removedYellowIssues,
redIssuesRemoved: removedRedIssues}'/>
});
The last part of the GSP is where the save button itself is defined as follows:
<br>
<button id="save"> Save </button>&nbsp&nbsp&nbsp
<button id="cancel" class="close" type="button"> Cancel </button>
I feel like the { } in the params should be [ ] instead. The g:remoteFunction is a GSP tag and the params should be a map.
<g:remoteFunction controller="customer"
action="saveModifiedIndividualRecord"
params='[uniqueId: uniqueId,
secondaryId: secondaryId,
yellowIssuesRemoved: removedYellowIssues,
redIssuesRemoved: removedRedIssues]'/>
However, you really shouldn't use that tag (I think it is deprecated in the latest versions). You should just do an a post via jQuery:
$.post("${g.createLink(action: 'saveModifiedIndividualRecord')", {uniqueId: uniqueId, ...}, function(result) {
...
});

Categories

Resources