Angular 1.3 breaking changes - scope set but reset before $apply - javascript

AngularJs 1.3.x, simple controller works but as soon as I re-write it using Typescript and Injection, it fails. If I reference 1.2.x it starts working again.
//This works in 1.3.x
scopeApp.controller('MyController', ['$scope', function ($scope) {
$scope.username = 'World';
$scope.sayHello = function () {
$scope.greeting = 'Hello ' + $scope.username + '!';
};
}]);
http://plnkr.co/edit/ssSuuZuGlrypemx3BU5r?p=preview
//This DOES NOT works in 1.3.x but does in 1.2.x
//The following code is produced via Typescript:
var MainFeature;
(function (MainFeature) {
var MainCtrl = (function () {
function MainCtrl($scope) {
this.scope = $scope;
this.name = "Sirar";
this.message = '';
}
MainCtrl.prototype.SetMessage = function () {
this.message = 'Hello' + this.name;
};
return MainCtrl;
})();
MainFeature.MainCtrl = MainCtrl;
})(MainFeature || (MainFeature = {}));
scopeApp.controller("MainCtrl", ["$scope", function ($scope) {
return new MainFeature.MainCtrl($scope);
}]);
Breaking changes docs that have valuable information but didn't help:
https://docs.angularjs.org/guide/migration
http://ng-learn.org/2014/06/Migration_Guide_from_1-2_to1-3/
http://wildermuth.com/2014/11/11/Angular_1_3_and_Breaking_Change_for_Controllers

You need to pass the constructor function, not some other function like you did. As I explained in another answer the controller is not created by calling new. It's created as follows:
instance = Object.create(controllerPrototype);
...
return fn.apply(self, args);
The catch is that the return value is not used, but the instance. In your case this would mean:
instance = Object.create({}); // should be MainCtrl.prototype
...
return fn.apply(self, args);
So "MainCtrl" ends up as empty object. You have to do what you should have done in the first place, pass the constructor:
scopeApp.controller("MainCtrl", ["$scope", MainFeature.MainCtrl]);

Related

AngularJS: What is 'this' when AngularJS controller is a member function of a class?

I use a member function of a class to be AngularJS controller:
Note: the code below is compiled from some TypeScript code.
function Clazz(x) {
this.Member = x;
this.Func= function ($scope) {
$scope.message = '' + this.Member; // this.Member is undefined
}
}
app.controller('TaxCtrl', new Clazz('Hello').Func );
The Func is called when the I switch to the TaxCtrl, but the this seems not to be the instance of Clazz because this.Member is always undefined.
When Func is a member of the Clazz instance then this will work. When angular constructs the function as an object is is creating a new object where this is referencing the instance of Func.
Here is a jsbin example:
http://jsbin.com/rocopiwila/edit?js,console
If you are trying to share properties down to controllers use services and injectables instead.
Try this:
function Clazz(x) {
this.Member = x;
self = this;
this.Func= function ($scope) {
$scope.message = '' + self.Member;
}
}
app.controller('TaxCtrl', new Clazz('Hello').Func );

Binding with service variable using ControllerAs syntax without $scope?

I'm familiarizing myself with controllerAs syntax in AngularJS, and I've come to a problem when I need to do a simple binding to a service variable. Typically a $scope.$watch or $scope.$on would do, but that would involve injecting $scope, which seems to defeat the purpose of controllerAs.
Currently what I have is that after clicking one of the buttons and calling config.setAttribute(attr), the controller calls the service's setAttribute function, but not getAttribute, so config.attribute never changes.
Is there something I'm overlooking in how I'm approaching this? Would I need to inject $scope or change the controller syntax to use $scope instead?
View:
<div data-ng-controller="ConfigCtrl as config">
<h3>Customize</h3>
<pre>Current attribute: {{config.attribute}}</pre>
<label>Attributes</label>
<div data-ng-repeat="attr in config.attributes">
<button ng-click="config.setAttribute(attr)">{{attr.name}}</button>
</div>
</div>
Service:
(function() {
'use strict';
angular.module('app')
.factory('Customization', Customization);
function Customization() {
var service = {
attribute: null,
getAttributes: getAttributes,
setAttribute: setAttribute,
getAttribute: getAttribute
}
return service;
/////
function getAttributes() {
return [
{name: 'Attr1', value: '1'},
{name: 'Attr2', value: '2'} // etc.
];
}
function setAttribute(attr) {
service.attribute = attr;
}
function getAttribute() {
return service.attribute;
}
}})();
Controller:
(function(){
'use strict';
angular.module('app')
.controller('ConfigCtrl', ConfigCtrl);
function ConfigCtrl(Customization){
var vm = this;
vm.attribute = Customization.getAttribute(); // bind
vm.attributes = [];
// Functions
vm.setAttribute = Customization.setAttribute;
init();
/////
function init(){
// Get attributes array
vm.attributes = Customization.getAttributes();
}
}})();
Here is what my controller looks like after injecting $scope and adding the watch for attribute:
(function(){
'use strict';
angular.module('app')
.controller('ConfigCtrl', ConfigCtrl);
function ConfigCtrl($scope, Customization){
var vm = this;
vm.attribute;
vm.attributes = [];
// Functions
vm.setAttribute = Customization.setAttribute;
init();
/////
function init(){
// Get attributes array
vm.attributes = Customization.getAttributes();
}
$scope.$watch(function() {
return Customization.getAttribute()
}, function() {
vm.attribute = Customization.getAttribute();
});
}})();
I also have the Karma test in case anyone is interested:
(function() {
'use strict';
describe('ConfigCtrl', function () {
var ConfigCtrl, scope;
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ConfigCtrl = $controller('ConfigCtrl',
{$scope: scope}
);
}));
describe('#setAttribute', function(){
it('sets the current attribute', function(){
var selected = {
name:'Attr1',
value:'1'
};
ConfigCtrl.setAttribute(selected);
scope.$apply();
expect(ConfigCtrl.attribute).to.eql(selected);
});
});
});
})();
Thanks for the help. I'm welcome to any better answers anyone else might have.

AngularJS : how to retrieve a return value from a service

I am following a tutorial on Angular Services and testing the below mentioned code. I am able to get the value in view from a TestService like -
TestService.name & TestService.$get()
but I want to know what if I need to get a value returned from the function like - return "From myFun"+this.name; in this case.
Code -
var myApp = angular.module('myApp', []);
myApp.controller('mainCtrl', function($scope, TestService){
$scope.service = "Data From Service: "+TestService;
});
var myFun = function() {
this.name = "FirstName";
this.$get = function() {
this.name = "Second Name";
return "From $get: "+this.name;
};
return "From myFun"+this.name;
};
// A service returns an actual function
myApp.service('TestService', myFun);
Service
A service is a constructor function which creates the object using the
new keyword. You can add properties and functions to a service object
by using the this keyword. Unlike a factory, it doesn't return
anything (it returns an object which contains method).
Service does place with this which is context of that service, and then return that context.
Simply word you can not return object from the service, you could do that using factory because factory does return an object.
Factory
var myApp = angular.module('myApp', []);
myApp.controller('mainCtrl', function($scope, TestService){
$scope.service = "Data From Service: "+TestService;
});
var myFun = function() {
var name = "FirstName";
return "From myFun"+this.name;
};
// A service returns an actual function
myApp.factory('TestService', myFun);
But in above case you can only return one value at a time, for adding function you need to modify the object which you are going to return from the factory.
Modified Factory
var myApp = angular.module('myApp', []);
myApp.controller('mainCtrl', function($scope, TestService) {
$scope.service = "Data From Service: " + TestService;
});
var myFun = function() {
var TestService = {};
TestService.name = "FirstName";
TestService.get = function() {
TestService.name = "Second Name";
return "From myFun" + TestService.name;
};
};
// A service returns an actual function
myApp.factory('TestService', myFun);
Working Plunkr
For more details read this answer where explained how service and factory are implemented.

Javascript call function in Angular

I had a strange problem in my Angular app so I asked some simple Javascript questions to make myself clear about the fundamentals (please see Javascript Callbacks). The solution worked fine for pure Javascript but when applied to my Angular app the problem still occurs.
I have defined a service which creates new instance of an object.
appModule.factory('myService', ["$rootScope", "$http", "DataCall", "$routeParams", "$log", function($rootScope, $http, DataCall, $routeParams, $log ) {
/////////////////////////// My- Object /////////////////////////////////////
MyObject= function(a, b, callback) {
var thisTemp = this;
this.accounts = [];
thisTemp.accounts[0] = 1;
thisTemp.accounts[1] = 2;
DataCall.get('xxx',function(data, status){
callback.call(this);
});
}
});
In my controller I create the new object and assign it to a scope variable
within controller....
$scope.pageInit = function () {
$scope.currentObject = {};
$scope.currentObject = new myService.MyObject('a', 'b', function(){
alert(this.account[0]);
});
This still doesn't work. Within the call back function 'this' refers to the 'window' rather than to currentObject.
Any help would be highly appreciated.
MyObject= function(a, b, callback) {
var thisTemp = this;
this.accounts = [];
tmpThis.accounts[0] = 1;
tmpThis.accounts[1] = 2;
DataCall.get('xxx',function(data, status){
// it is here 'this' become 'window'
callback.call(this);
});
}
});
since you willing to read how javascript callback, I leave you to figure it out.
There's no need to explicitly instantiate your service in the controller. Angular will already do this for you when it's needed.
So you could re-write your service to something like this.
appModule.factory('myService', ["$rootScope", "$http", "DataCall", "$routeParams", "$log", function($rootScope, $http, DataCall, $routeParams, $log ) {
/////////////////////////// My- Object /////////////////////////////////////
myService = {
accounts : [],
accounts[0] : 1,
accounts[1] : 2,
dataCall : function(a, b, callback) {
callback.call(this);
}
}
return myService;
});
within controller....
$scope.pageInit = function () {
$scope.myService = myService;
$scope.myService.currentObject = myService.dataCall('a', 'b', function(){
alert($scope.myService.accounts[0]);
});
In your MyObject you get hold of a reference to 'this' (which refers to the function MyObject) and you called it thisTemp. Since you got hold of that reference in a variable, you should use that variable instead of 'this' unless you want to reference another context.
appModule.factory('myService', ["$rootScope", "$http", "DataCall", "$routeParams", "$log", function($rootScope, $http, DataCall, $routeParams, $log ) {
/////////////////////////// My- Object /////////////////////////////////////
function MyObject(a, b, callback) {
var thisTemp = this;
this.accounts = [];
thisTemp.accounts[0] = 1;
thisTemp.accounts[1] = 2;
DataCall.get('xxx',function(data, status){
callback.call(thisTemp);
});
}
return {
MyObject: MyObject
}
}]);
within controller....
$scope.pageInit = function () {
$scope.currentObject = {};
$scope.currentObject = new myService.MyObject('a', 'b', function(data){
alert(data.account[0]);
});

AngularJS function hoisting

I have a 'beginner' question. Why does this error out? I call the function in the code, but the function is defined further down.
AngularJS version:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.name = 'Someone';
$scope.doStuff(); // run the defined function, but errors out
$scope.doStuff= function(){ // function definition
console.log('did something');
}
}
http://jsfiddle.net/2fjuJ/4/
But this one works fine:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.name = 'Someone';
$scope.doStuff = function(){
console.log('did something');
}
$scope.doStuff();
}
http://jsfiddle.net/2fjuJ/5/
You're not declaring a new variable when you write $scope.doStuff = function () {...}, you're assigning a property, which does not get hoisted. at the time you call $scope.doStuff(), $scope looks like:
{
name: "Someone"
}
As you can see there's no "doStuff" property until the next line executes.

Categories

Resources