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
Related
I've created a small feature alowing users to search for movie titles. This does a JSON requests from tmdb.org which returns things like titles, dates and url's posters.
The controller:
angular.module('movieSeat')
.factory('moviesearchFactory', ['$http', '$q', '$rootScope', function ($http, $q, $rootScope) {
var factory = {};
function httpPromise(url) {
var deferred = $q.defer();
$http({
method: 'JSONP',
url: url
})
.success(function (data) {
deferred.resolve(data.results);
})
.error(function () {
deferred.reject();
});
return deferred.promise;
}
factory.getMovies = function (searchquery) {
return httpPromise('http://api.themoviedb.org/3/' + 'search/movie?api_key=a8f7039633f2065942cd8a28d7cadad4' + '&query=' + encodeURIComponent(searchquery) + '&callback=JSON_CALLBACK')
}
return factory;
}]);
The factory:
angular.module('movieSeat')
.controller('moviesearchCtrl', ['$scope', 'moviesearchFactory', function ($scope, moviesearchFactory) {
$scope.createList = function (searchquery) {
$scope.loading = true;
moviesearchFactory.getMovies(searchquery)
.then(function (response) {
$scope.movies = response;
})
.finally(function () {
$scope.loading = false;
});
}
}]);
The template:
<div ng-controller="moviesearchCtrl" id="movieSearch">
<div class="spinner" ng-show="loading">Loading</div>
<input ng-model="searchquery" ng-change="createList(searchquery)" ng-model-options="{ debounce: 500 }" />
{{ search }}
<ul>
<li ng-if="movie.poster_path" ng-repeat="movie in movies | orderBy:'-release_date'">
<span class="title">{{ movie.title }}</span>
<span class="release_date">{{ movie.release_date }}</span>
<img ng-src="https://image.tmdb.org/t/p/w300_and_h450_bestv2/{{ movie.poster_path }}" class="poster"/>
</li>
</ul>
</div>
The problem with this feature is that the spinner class only waits for the requested data. But just loading some JSON doesn't take long, it's downloading the images from the api in the browser that takes a while.
This causes 2 things. First of the spinner is removed before the images are rendered in the browser and because the images are all loaded async it causes a waterfall effect.
The easiest way to resolve this problem would to delay the .then call in the controller until the images are downloaded for the user and then go into the .finally call.
But I can't find a way to create something like that. Any tips?
Try this and let me know:
The idea is to use a directive to emit a render finished event:
dashboard.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.onFinishRender);
});
}
}
}
});
In the controller keep the event listener that wait for the image load promises:
$scope.$on('dataLoaded', function(ngRepeatFinishedEvent) {
// your code to check whether images has loaded
var promises = [];
var imageList = $('#my_tenants img');
for(var i = 0; i < imageList.length; i++) {
promises.push(imageList[i].on('load', function() {}););
}
$q.all(promises).then(function(){
// all images finished loading now
$scope.loading = false;
});
});
and in the html side:
<div id = "my_tenants">
<div ng-repeat="tenant in tenants" on-finish-render="dataLoaded">
// more divs
</div>
</div>
I need to reflect some changes to controller B (inside some event) when I make change at controller A. For that I am using a service.
When I am changing service value from FirstCtrl, ng-change is not firing at SecondCtrl. Is there anything I have missed or need to change?
Please note that I am using angular 1.5.6. and don't want to use watch or even scope.
Below is my code.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
debugger
self.changeM = function() {
debugger
Data.FirstName = self.FirstName;
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.FirstName = Data;
self.changeM = function() {
alert(1);
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="FirstCtrl as c">
<input type="text" ng-model="c.FirstName" data-ng-change="c.changeM()">
<br>Input is : <strong>{{c.FirstName}}</strong>
<div ng-controller="SecondCtrl as c1">
Input should also be here: {{c1.FirstName}}
<input type="text" ng-model="c1.FirstName" data-ng-change="c1.changeM()">
</div>
</div>
<hr>
</div>
As you dont want to use $scope trying modifying the code in order to use $emit and $on feature in angular js to communicate between two controllers. You can refer this link.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
debugger
self.changeM = function() {
debugger
//Data.FirstName = self.FirstName;
Data.$on('emitData',function(event,args){
Data.FirstName=args.message
document.write(Data.FirstName)
})
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.FirstName = Data;
self.changeM = function() {
Data.$emit('emitData',{
message:Data.FirstName
})
};
}
]);
The only way then is to directly copy the reference of the data object within the controller. Note that you don't need ng-change to update the value then.
If you want something else, either wrap the FirstName in a sub object of Data and do the same i did :
Data = {foo:'FirstName'};
Or use $watch since it's the whole purpose of that function.
Here is a working code with copying the Data object in the controller.
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return {
FirstName: ''
};
});
myApp.controller('FirstCtrl', ['Data',
function(Data) {
var self = this;
self.Data=Data;
debugger
self.changeM = function() {
debugger
};
}
]);
myApp.controller('SecondCtrl', ['Data',
function(Data) {
var self = this;
self.Data = Data;
self.changeM = function() {
alert(1);
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="FirstCtrl as c">
<input type="text" ng-model="c.Data.FirstName" data-ng-change="c.changeM()">
<br>Input is : <strong>{{c.Data.FirstName}}</strong>
<div ng-controller="SecondCtrl as c1">
Input should also be here: {{c1.Data.FirstName}}
<input type="text" ng-model="c1.Data.FirstName" data-ng-change="c1.changeM()">
</div>
</div>
<hr>
</div>
The only way I know to solve the problem is using watch, unfortunately. (I am new to angular.)
From the ngChange document (https://docs.angularjs.org/api/ng/directive/ngChange):
The ngChange expression is only evaluated when a change in the input value causes a new value to be committed to the model.
It will not be evaluated:
if the value returned from the $parsers transformation pipeline has not changed
if the input has continued to be invalid since the model will stay null
**if the model is changed programmatically and not by a change to the input value**
I would like to create a service and a controller in AngualrJS. The problem is I need to access to $scope in my service.
I think the good solution is to put this service in the controller directly but I have no idea how to do it.
This is my HTML :
<div ng-controller="myController">
<input type="text" id="idInput" name="idInput" ng-model="nameModel">
<button class="btn btn-default" ng-click="functionWhenClick()">Execute</button>
</div>
This is my controller :
var variableModuleName = angular.module("nameModule",[]);
variableModuleName.controller('controllerName',function($rootScope,$scope, CommonService) {
$scope.nameModel = '';
$scope.scopeFunctionName = function () {
CommonService.myFunction($scope.nameModel);
};
});
This is my service :
variableModuleName.service('CommonService', ['dataService', function(dataService) {
this.loadData = function(param) {
dataService.getCommitData(param).then(function(res) {
if (res.error) {
$scope.chartData = res.chartData;
}
});
};
this.myFunction = function(concatURL){
this.loadData('URL' + concatURL);
}
}]);
I hope you will can help me.
Thanks.
First of all, You can't/shouldn't use $scope in a service. You can't inject $scope in the service. You can pass $scope as a function's parameter but that's a bad idea. Because, we don't want our service to play with all our $scope variables.
Now, to rewrite your service to return chartData from an async operation using dataService (assuming dataService.getCommitData(param) does have a call to server) , you need to handle the promise well.
var d3DemoApp = angular.module("D3demoApp",[]);
// service. Assuming dataService exists
d3DemoApp.service('CommonService', ['dataService', function(dataService) {
this.loadData = function(param) {
return dataService.getCommitData(param).then(function(res) {
// Should the if condition be res.error or !res.error
if (res.error) {
return res;
}
});
};
this.myFunction = function(parameterItem){
return this.loadData('http://localhost:3412/bubble/' + parameterItem);
console.log("Fonction appellée");
}
}]);
// controller
d3DemoApp.controller('controllerFilterSearch',function($rootScope,$scope, CommonService) {
$scope.searchText = '';
$scope.getSearchText = function () {
CommonService.myFunction($scope.searchText).then(function(res) {
$scope.chartData = res.chartData;
});
};
});
So, in the above code, I am basically returning a promise from this.loadData function. When we call CommonService.myFunction from controller, we get the response in the then resolved callback function and we set the chartData from response to $scope.chartData.
First don't use var d3DemoApp = angular.module("D3demoApp",[]) through your files.
Use angular.module("D3demoApp",[]) once to get your module instantiated and then get the reference of the existing one using angular.module("D3demoApp")
In your plknr :
You forget to include the service file
I don't see any definition of the dataService which is why you have the unknown provider dataServiceProvider error.
There are many ways to do this. My favorite is creating another service which has reference to the scope.
d3DemoApp.service('scopeServer', ['dataService', function(dataService) {
var scope;
return {
scope: function(_scope) {
if (typeof _scope !== 'undefined')
scope = _scope;
return scope;
}
}
}]);
This service maintains a reference to the scope in a singleton an returns it wherever you call scopeService.scope();
You can set the scope in your controller initially.
d3DemoApp.controller('controllerFilterSearch',function($rootScope,$scope, scopeServer) {
scopeServer.scope($scope);
});
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
<script src="controllerInput.js"></script>
<script src="app.js"></script>
<script src="serviceInput.js"></script> <!-- Include -->
</head>
<body ng-app="D3demoApp" ng-controller="controllerFilterSearch">
<input type="text" id="searchTextBox" name="searchTextBox" ng-model="searchText">
<button class="btn btn-default" ng-click="getSearchText()">Rechercher</button>
</body>
</html>
var d3DemoApp = angular.module("D3demoApp",[]);
d3DemoApp.controller('controllerFilterSearch',function($rootScope,$scope, CommonService) {
$scope.searchText = '';
$scope.getSearchText = function () {
CommonService.myFunction($scope.searchText);
};
});
service
d3DemoApp.service('CommonService', ['dataService', function(dataService) {
this.chartData = '';
this.loadData = function(param) {
dataService.getCommitData(param).then(function(res) {
if (!res.error) {
this.chartData = res.chartData;
}
});
};
this.myFunction = function(parameterItem){
this.loadData('http://localhost:3412/bubble/' + parameterItem);
console.log("Fonction appellée");
}
}]);
controller
var d3DemoApp = angular.module("D3demoApp",[]);
d3DemoApp.controller('controllerFilterSearch',function($rootScope,$scope, CommonService) {
$scope.searchText = '';
$scope.getSearchText = function () {
CommonService.myFunction($scope.searchText);
$scope.searchText = CommonService.chartData;
};
});
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
I have a scope variable, when it returns true, i need to trigger some events or do something. I my case, the every first time, the scope variable returns undefined and later it returns true. In this case i used $watch method to get the expected funcionality. Is there any alternative approach to do the same instead using $watch ?
scope.$watch () ->
scope.initiateChild
, (value) ->
if value is true
$timeout ->
scope.buildOnboarding()
, 1000
You can try using AngularJS $on(), $emit() and $broadcast().
Here is an example: http://www.binaryintellect.net/articles/5d8be0b6-e294-457e-82b0-ba7cc10cae0e.aspx
You can use JavaScript getters and setters without any expense of using $watch.
Write code in the setter to do what you want when angular changes the your model's value you are using in scope. It gets null or an a State object as user types. Useful for working with type ahead text boxes that have dependencies on each other. Like list of counties after typing state without user selecting anything.
Here is some pseudo style code to get the idea.
<input ng-model="searchStuff.stateSearchText" />
<div>{{searchStuff.stateObject.counties.length}}</div>
<div>{{searchStuff.stateObject.population}}</div>
$scope.searchStuff=new function(){var me=this;};
$scope.searchStuff.stateObject = null;
$scope.searchStuff.getStateObjectFromSearchText = function(search){
// get set object from search then
return stateObject;
};
$scope.searchStuff._stateSearchText= "";
Object.defineProperty($scope.searchStuff, 'stateSearchText', {
get: function () {
return me._stateSearchText;
},
set: function (value) {
me,_stateSearchText = value;
me.stateObject = getStateObjectFromSearchText (value);
}
});
See this fiddle: http://jsfiddle.net/simpulton/XqDxG/
Also watch the following video: Communicating Between Controllers
A sample example is given below
Html:
<div ng-controller="ControllerZero">
<input ng-model="message" >
<button ng-click="handleClick(message);">LOG</button>
</div>
<div ng-controller="ControllerOne">
<input ng-model="message" >
</div>
<div ng-controller="ControllerTwo">
<input ng-model="message" >
</div>
javascript:
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}
ControllerZero.$inject = ['$scope', 'mySharedService'];
ControllerOne.$inject = ['$scope', 'mySharedService'];
ControllerTwo.$inject = ['$scope', 'mySharedService'];