I have been learning Angular and everything's been going smoothly. I have decided to try and incorporate TypeScript into this and wrote a small test controller in an already existing Angular/WebApi project I have been working on.
The controller "works" and initializes just fine, however, my $scope properties are not being updated when set to an HTTP promise. I have attached a "working" example of my issue using the TypeScript generated JS file. When debugging, I can see the then get triggered on the promise and the data I expected does return, but the page displays {}
Here is my actual TypeScript file.
/// <reference path="../../tds/angularjs/angular.d.ts" />
module RecordEditor {
export interface Scope extends ng.IScope{
tableId: any;
}
export class Controller {
$scope: Scope;
api : any;
constructor($scope: Scope, myApi: any) {
this.api = myApi;
this.$scope = $scope;
this.$scope.tableId = this.api.getTestResponse();
}
}
}
var app = angular.module("myApp");
app.controller("RecordEditor.Controller", ['$scope', 'myApi', RecordEditor.Controller]);
That controller is the only thing I have actually written in TypeScript. All the other controllers are in JavaScript and the same api I built returns responses just fine with those.
Below is the actual runnable snippet code with the JavaScript version of my Controller.
(function() {
var app = angular.module('myApp', []);
}());
(function() {
var myApi = function($http) {
var getTestResponse = function() {
// Hope it is okay to use SE API to test this question. Sorry if not.
return $http.get("https://api.stackexchange.com/2.2/sites")
.then(function(response) {
return "Hello!";
});
};
return {
getTestResponse: getTestResponse,
};
};
var module = angular.module("myApp");
module.service("myApi", ['$http', myApi]);
}());
var RecordEditor;
(function(RecordEditor) {
var Controller = (function() {
function Controller($scope, myApi) {
var _this = this;
this.api = myApi;
this.$scope = $scope;
this.$scope.response = this.api.getTestResponse();
this.$scope.ctorTest = 42;
}
return Controller;
})();
RecordEditor.Controller = Controller;
})(RecordEditor || (RecordEditor = {}));
var app = angular.module("myApp");
app.controller("RecordEditor.Controller", ['$scope', 'myApi', RecordEditor.Controller]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script>
<div ng-app="myApp" ng-controller="RecordEditor.Controller">
<div >
Scope property set in constructor: {{ctorTest}}
</div>
<div >
Scope response value: {{response}}
</div>
<div >
Scope response should have been "Hello!"
</div>
</div>
Can anyone actually tell me what I am doing wrong? Everything looks fine to me.
var getTestResponse = function() {
return $http.get("https://api.stackexchange.com/2.2/sites")
.then(function(response) {
return "Hello!"; // <-- does not do what you think it does
});
};
/** elsewhere **/
this.$scope.response = this.api.getTestResponse();
The inner return statement here does not return a value from getTestResponse. Instead, it returns a value from the Promise which is basically discarded. getTestResponse returns immediately with a Promise object that is of no particular use for binding to the scope.
Fixed version of the JS:
(function() {
var app = angular.module('myApp', []);
}());
(function() {
var myApi = function($http) {
var getTestResponse = function() {
// Hope it is okay to use SE API to test this question. Sorry if not.
return $http.get("https://api.stackexchange.com/2.2/sites")
.then(function(response) {
return "Hello!";
});
};
return {
getTestResponse: getTestResponse,
};
};
var module = angular.module("myApp");
module.service("myApi", ['$http', myApi]);
}());
var RecordEditor;
(function(RecordEditor) {
var Controller = (function() {
function Controller($scope, myApi) {
var _this = this;
this.api = myApi;
this.$scope = $scope;
/** FIXED **/
this.api.getTestResponse().then(function(v) { _this.$scope.response = v; });
this.$scope.ctorTest = 42;
}
return Controller;
})();
RecordEditor.Controller = Controller;
})(RecordEditor || (RecordEditor = {}));
var app = angular.module("myApp");
app.controller("RecordEditor.Controller", ['$scope', 'myApi', RecordEditor.Controller]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script>
<div ng-app="myApp" ng-controller="RecordEditor.Controller">
<div >
Scope property set in constructor: {{ctorTest}}
</div>
<div >
Scope response value: {{response}}
</div>
<div >
Scope response should have been "Hello!"
</div>
</div>
Ok here's the issue. You are trying to set your response to the actual callback function.
Instead, you have to set the response property inside callback using the response you got from the http call.
Here is the api function
var getTestResponse = function() {
return $http.get("http://vimeo.com/api/oembed.json?url=http%3A//vimeo.com/76979871");
};
Here is your controller code where you have to set your response property to bind to UI.
_this.api.getTestResponse().success(function(resp) {
_this.$scope.response = resp.height; //I just used height property from response as example
});
Here is the jsfiddle
http://jsfiddle.net/HB7LU/7386/
Make sure you first get your stackexachange call working. I used vimeo call as example.
Let me know if you have any questions.
So, in your TypeScript code, you have to change
this.$scope.tableId = this.api.getTestResponse();
to
this.api.getTestResponse().then((resp) =>
{
this.$scope.tableId = resp;
});
And make your API just return a promise.
Related
I'm using angularjs and I am trying to parse a value from a service to my $scope controller. The problem is that is loading first the $scope and then the service. As a result me my scope is always undefinied. I tried many solutions that I found by no-one is worked for me. Can anyone help me please?
My service:
'use strict';
angular.module('myApp')
.service('getCategoriesService', function ($rootScope) {
getCategories = function () {
var categoriesByLocation = 1;
return categoriesByLocation;
};
and my controller:
angular.module("myApp")
.controller('MyCtrl', function ($scope,getCategoriesService) {
$scope.resultCategory = getCategoriesService.getCategories();
});
Use the this. when declaring a function in a service.
.service('getCategoriesService', function ($rootScope) {
this.getCategories = function () {
var categoriesByLocation = 1;
return categoriesByLocation;
};
})
demo
var app = angular.module("myApp",[]);
app.controller('MyCtrl', function ($scope,getCategoriesService) {
$scope.resultCategory = getCategoriesService.getCategories();
});
app.service('getCategoriesService', function ($rootScope) {
this.getCategories = function () {
var categoriesByLocation = 1;
return categoriesByLocation;
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
{{resultCategory}}
</div>
Change it as
angular.module("myApp")
.controller('MyCtrl', function ($scope,getCategoriesService) {
getCategoriesService.getCategories().then((function (d) {
$scope.resultCategory = d;
}
});
I have a view for SidebarController like below -
<a ng-click="reachMe($event);$event.preventDefault()" ng-href="#/app/hello">
Before going to the link I want to call reachMe() to check some changes on page and need to show an alert if any changes made
function SidebarController($rootScope, $scope, $state, $location, SidebarLoader){
$scope.reachMe = function(event){
//here I want to call function isPageChanged() from StaticPageController
//something like this
// if StaticPageController.isPageChanged() return true
// then show alert
// else
// $location.url($href)
}
}
Update 1 :
Not sure about this, But give it a try.
<div ng-app="testApp" ng-controller="ControllerOne">
<button ng-click="methodA();"> Call Another Controller</button>
</div>
<script>
var app = angular.module('testApp', []);
app.controller('ControllerOne', function($scope, $rootScope) {
$scope.reachMe = function() {
var arrayData = [1,2,3];
$rootScope.$emit('callEvent', arrayData);
if($rootScope.isChanged){
// Show Alert
}else{
//Go to route
}
}
});
app.controller('ControllerTwo', function($scope, $rootScope,$state) {
$scope.checkSomethingChanged = function() {
alert("Hello");
$rootScope.isChanged = true;
}
$rootScope.$on('callEvent', function(event, data) {
console.log(data);
$scope.checkSomethingChanged();
});
});
Following method worked for me perfectly :
<div ng-app="testApp" ng-controller="ControllerOne">
<button ng-click="methodA();"> Call Another Controller</button>
</div>
<script>
var app = angular.module('testApp', []);
app.controller('ControllerOne', function($scope, $rootScope) {
$scope.methodA = function() {
var arrayData = [1,2,3];
$rootScope.$emit('callEvent', arrayData);
}
});
app.controller('ControllerTwo', function($scope, $rootScope) {
$scope.reachMe = function() {
alert("Hello");
}
$rootScope.$on('callEvent', function(event, data) {
console.log(data);
$scope.reachMe();
});
});
</script>
A controller is not the right concept for sharing functionality. Use a Factory or Service for that.
var logicFactory = function () {
return {
methodA: function () {
},
methodB: function()
{
}
};
}
You can then inject that factory into each controller where it is needed like:
var ControllerA = function ($scope,logicFactory) {
$scope.logic = logicFactory;
}
ControllerA.$inject = ['$scope', 'logicFactory'];
Another option is to use the broadcast/emit Patern. But I would use that only where really necessary:
Usage of $broadcast(), $emit() And $on() in AngularJS
I want to use ng-infinite-scroll (https://sroze.github.io/ngInfiniteScroll/). But when I want to call function from my service (injected to controller) nothing happened (this function doesn't trigger). When I call function not from the service but from $scope of the controller - everything works fine. How to call function from the injected service in infinite-scroll directive?
My HTML structure:
<div class="col-xs-12 publications-container" ng-controller="showPublicationsCtrl">
<h3 class="publ-heading">Proposed Publications</h3>
<ul class="list-group" infinite-scroll="publicationsFactory.getPublications()" infinite-scroll-disabled='publicationsFactory.busyLoadingData'>
<li ng-repeat="publication in publications" class="list-unstyled list-group-item" ng-cloak>
<p class="text-warning">Author: <i>{{publication.author}}, {{publication.timestamp | date: 'MMM. d, y'}}</i></p>
<p ng-text-truncate="publication.text" ng-tt-words-threshold="3">{{publication.text}}</p>
<p class="text-muted">Channels: <i>{{publication.channels.join(", ")}}</i></p>
</li>
</ul>
</div>
My showPublicationsCtrl controller:
twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory', '$scope', function (publicationsFactory, $scope) {
publicationsFactory.getPublications();
$scope.publications = publicationsFactory.publications;
}]);
My publicationsFactory service:
angular.module('twitterApiApp').factory('publicationsFactory', ['$http', function($http) {
var publicationsFact = {};
publicationsFact.publications = [];
publicationsFact.busyLoadingData = false;
publicationsFact.getId = function() {
publicationsFact.id = publicationsFact.publications.length > 0 ?
publicationsFact.publications[publicationsFact.publications.length-1]['id'] : 0;
};
publicationsFact.getPublications = function () {
console.log("Triggered");
if (publicationsFact.busyLoadingData) return;
publicationsFact.busyLoadingData = true;
publicationsFact.getId();
$http.get("/Publications?id_gte=" + publicationsFact.id + "&_limit=2").then(function(response) {
for (var i = 0; i < response.data.length; i++) {
publicationsFact.publications.push(response.data[i]);
};
});
publicationsFact.busyLoadingData = false;
};
return publicationsFact;
}]);
If I create some function in my controller, for example $scope.myFunction and then in HTML structute I assign infinite-scroll attribute to myFunction() the function will be successfully executed. So, I think maybe there are some mistakes in the way I inject the service in the controller. But everything else except ng-inginite-scroll works as planned.
infinite-scroll is binded to $scope.publicationsFactory :
<ul class="list-group" infinite-scroll="publicationsFactory.getPublications()" infinite-scroll-disabled='publicationsFactory.busyLoadingData'>
but publicationsFactory is not available in your scope, you must expose it like this :
twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory', '$scope', function (publicationsFactory, $scope) {
// Expose publication factory
$scope.publicationsFactory = publicationsFactory;
publicationsFactory.getPublications();
$scope.publications = publicationsFactory.publications;
}]);
How to update $scope.publications with latest publications retrieved using Factory
Your factory can return a promise resolved with the latest publications, change your code like this :
angular.module('twitterApiApp').factory('publicationsFactory', ['$http', '$q', function($http, $q) {
var publicationsFact = {};
publicationsFact.publications = [];
publicationsFact.busyLoadingData = false;
publicationsFact.getId = function() {
publicationsFact.id = publicationsFact.publications.length > 0 ?
publicationsFact.publications[publicationsFact.publications.length - 1]['id'] : 0;
};
/**
* Get latest publications
* #returns {object} A promise
*/
publicationsFact.getPublications = function() {
var deferred = $q.defer();
console.log("Triggered");
if (publicationsFact.busyLoadingData) return;
publicationsFact.busyLoadingData = true;
publicationsFact.getId();
$http.get("/Publications?id_gte=" + publicationsFact.id + "&_limit=2").then(function(response) {
for (var i = 0; i < response.data.length; i++) {
publicationsFact.publications.push(response.data[i]);
};
// Resolve promise with updates publications list
deferred.resolve(publicationsFact.publications);
}, function(error) {
// Reject promise with error message
deferred.reject(error.message);
});
publicationsFact.busyLoadingData = false;
// Return promise
return deferred.promise;
};
return publicationsFact;
}]);
Then, in your controller :
twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory', '$scope', function (publicationsFactory, $scope) {
// Expose publication factory
$scope.publicationsFactory = publicationsFactory;
publicationsFactory.getPublications()
.then(function(results) {
$scope.publications = results;
});
}]);
You should add this line to your ctrl:
$scope.getPublications = publicationsFactory.getPublications;
And then call that ctrl function from the view.
This way, you are binding reference to the service function to the controller scope which is available to you in the view.
Edit: Another option is to bind whole service to the ctrl property as someone else already suggested
I am trying to get the user query from html using ng-click. I want to make a https call using the value which I fetch from ng-click. I can see the data in Alert--1 but in Alert--2 i get undefined. on internet I read that passing values using services is the best practice.Please correct me if I am wrong.
My controller
mainApp.controller('searchController',function($scope,searchString){
$scope.getQuery = function(userq) // its ng-click="getQuery(userq)" on Search button
{
$scope.userq=userq;
alert('Alert--1'+userq); // its working fine
searchString.setSearchString(userq);
};
});
//====================
mainApp.controller('fetchQueryResultController',function($scope,searchString){
var searchStr = searchString.getSearchString();
alert('Alert--2--'+searchStr); // Undefined
// Later I'll use this value to fetch data from Watson search(Django) using $https call
});
My service:
mainApp.factory('searchString', function () {
var qVal ;
return {
setSearchString:function (query) {
qVal = query;
},
getSearchString:function () {
return qVal;
}
};
});
Routing:
.when('/search', {
templateUrl: "../static/views/seachResult.html",
controller: "fetchQueryResultController"
})
Is there any simpler way?
Using a service is OK. Take a look at this, is quite clear for begginers:
https://egghead.io/lessons/angularjs-sharing-data-between-controllers
alert('Alert--2'+searchStr); is showing undefined because it is being executed before $scope.getQuery obviously. Controller's initialization is done before ng-init evaluates the expression.
In your case I believe it is better to fire an event when the data is set, so the second controller gets notified. This is being done with $on and $emit.
Here is a plunker with your example: http://plnkr.co/edit/97mVwbWmoOH3F7m8wbN0?p=preview
var app = angular.module('plunker', []);
app.controller('searchController',function($scope,searchString){
$scope.searchText;
$scope.getQuery = function(userq) // its ng-click="getQuery(userq)" on Search button
{
$scope.userq=userq;
alert('Alert--1'+userq); // its working fine
searchString.setSearchString(userq, $scope);
};
});
//====================
app.controller('fetchQueryResultController',function($scope, $rootScope, searchString){
var searchStr = searchString.getSearchString;
$scope.getData = function(){
searchStr = searchString.getSearchString();
alert('Alert--2--'+ searchStr);
}
$rootScope.$on('dataModified', function(){
$scope.getData();
});
});
//====================
app.factory('searchString', function ($rootScope) {
var qVal ;
return {
setSearchString:function (query) {
qVal = query;
$rootScope.$emit('dataModified');
},
getSearchString:function () {
return qVal;
}
};
});
this alert undefined
var searchStr = searchString.getSearchString();
alert('Alert--2'+searchStr);
becuase qVal hasn't set yet
qVal set when getQuery get called but that time alert2 already executed
A simple solution is to have your factory return an object and let your controllers work with a reference to the same object:
JS:
// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('searchString', function() {
var qVal;
return {
setSearchString: function(query) {
qVal = query;
},
getSearchString: function() {
return qVal;
}
};
});
myApp.controller('setSearchController', function($scope, searchString) {
$scope.setQuery = function(userq) {
$scope.userq = userq;
searchString.setSearchString(userq);
};
});
myApp.controller('fetchQueryResultController', function($scope, searchString) {
$scope.getQuery = function(user) {
alert(searchString.getSearchString());
};
});
HTML:
<div ng-app="myApp">
<div ng-controller="setSearchController">
<button ng-click="setQuery('Shohel')">Set</button>
</div>
<hr />
<div ng-controller="fetchQueryResultController">
<button ng-click="getQuery()">Get</button>
</div>
</div>
Here is similar fiddle
I'm trying to inject the factory Application into the ApplicationService factory. Both are defined in the same module.
Application factory (application.model.js)
(function(Object, coreModule) {
'use strict';
// the factory to expose that allows the creation of application instances
var ApplicationFactory = function() {
console.log("Application factory!");
return {foo: 'bar'};
}
coreModule.factory('Application', [ApplicationFactory]);
})(Object, angular.module('core'));
ApplicationService factory (application.service.js)
(function(coreModule) {
'use strict';
var ApplicationService = function(Application) {
var api = {
shout = function() {console.log(Application);}
};
return api;
}
ApplicationService.$inject = ['Application'];
coreModule.factory('ApplicationService', [ApplicationService]);
})(angular.module('core'));
Then I'm injecting ApplicationService factory into a controller and calling the method shout. I get undefined when in the console's log, Application is always undefined. If in a controller I innject Application it works. So i know both factories are working standalone.
Both files are being imported in my index.html.
I've spent hours looking for the issue but I can't find it. What am I doing wrong?
Please see working demo below.
You've got 2 options.
a) Remove square brackets here:
coreModule.factory('ApplicationService', ApplicationService)
b) Add injected Application as first element before ApplicationService:
coreModule.factory('ApplicationService', ['Application', ApplicationService])
var app = angular.module('core', []);
app.controller('firstCtrl', function($scope, ApplicationService) {
ApplicationService.shout();
});
(function(Object, coreModule) {
'use strict';
// the factory to expose that allows the creation of application instances
var ApplicationFactory = function() {
console.log("Application factory!");
return {
foo: 'bar'
};
};
coreModule.factory('Application', [ApplicationFactory]);
})(Object, angular.module('core'));
(function(coreModule) {
'use strict';
var ApplicationService = function(Application) {
var api = {
shout: function() {
console.log(Application);
}
};
return api;
};
ApplicationService.$inject = ['Application'];
coreModule.factory('ApplicationService', ApplicationService);
})(angular.module('core'));
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="core">
<div ng-controller="firstCtrl">
</div>
</body>