I have an angular js controller that I am writing in a way so other controllers can inherit from it. Instead of defining the function as a single one in the angular's controller function I am writing it in the following way:
function SomeCtrl($scope)
{
this.some_field = "1234";
this.scope.externalMethod = angular.bind(this, this.externalMethod);
this.scope.anotherMethod = angular.bind(this, this.anotherMethod);
return this;
}
SomeCtrl.prototype.externalMethod = function()
{
//Do something
//....(Don't worry about this method, its just to highlight that I can call this method from $scope.externalMethod)
}
//These are the methods of interest
SomeCtrl.prototype.instanceMethodOne = function()
{
//Do something....
}
SomeCtrl.prototype.anotherMethod = function()
{
this.instanceMethodOne(); //---> Problem here!
//Carry on
//....
}
angular.module('some_module') //Previously defined
.controller('SomeCtrl', SomeCtrl)
So the problem that I am having now is to have a reference (this) inside the method "anotherMethod", which calls a class instance method "instanceMethodOne". This resolves to null as the self reference "this" is not resolved at that point. Is there any way to reference an object inside its instance method like in this case?
The problem has been resolved.
UPDATE AND FIXED:
Okay so I have looked into this problem. I should actually update the problem. It actually looks more like this:
function SomeCtrl($scope)
{
this.some_field = "1234";
this.scope.externalMethod = angular.bind(this, this.externalMethod);
this.scope.anotherMethod = angular.bind(this, this.anotherMethod);
return this;
}
SomeCtrl.prototype.externalMethod = function()
{
//Do something
//....(Don't worry about this method, its just to highlight that I can call this method from $scope.externalMethod)
}
//These are the methods of interest
SomeCtrl.prototype.instanceMethodOne = function()
{
//Do something....
}
SomeCtrl.prototype.anotherMethod = function()
{
var aCallBack = function()
{
//Some stuff...
this.instanceMethodOne(); //---> Problem here!
}
//Carry on
//....
}
angular.module('some_module') //Previously defined
.controller('SomeCtrl', SomeCtrl)
So the fix was to first create a reference to the object's method. That way I could call it within the callback function. The "this" in the callback function was referring to that function itself. Sorry for false alarm...This is fixed now! Thanks #sma for looking into it.
Related
I have 2 components which are both accessing a service. One component delivers an object and the other one is supposed to display it or just receive it. The problem is that after the initialization process is finished the variable in the display component doesn't change.
I have tried using $scope , $scope.$apply(), this.$onChanges aswell as $scope.$watch to keep track of the variable, but it always stays the same.
This controller from the display component provides a text, which is from an input field, in an object.
app.controller("Test2Controller", function ($log, TestService) {
this.click = function () {
let that = this;
TestService.changeText({"text": that.text});
}
});
That is the the service, which gets the objekt and saves it into this.currentText.
app.service("TestService", function ($log) {
this.currentText = {};
this.changeText = function (obj) {
this.currentText = obj;
$log.debug(this.currentText);
};
this.getCurrentText = function () {
return this.currentText;
};
});
This is the controller which is supposed to then display the object, but even fails to update the this.text variable.
app.controller("TestController", function (TestService, $timeout, $log) {
let that = this;
this.$onInit = function () {
this.text = TestService.getCurrentText();
//debugging
this.update();
};
//debugging
this.update = function() {
$timeout(function () {
$log.debug(that.text);
that.update();
}, 1000);
}
//debugging
this.$onChanges = function (obj) {
$log.debug(obj);
}
});
I spent quite some time searching for an answer, but most are related to directives or didn't work in my case, such as one solution to put the object into another object. I figured that I could use $broadcast and $on but I have heard to avoid using it. The angular version I am using is: 1.6.9
I see a problem with your approach. You're trying to share the single reference of an object. You want to share object reference once and want to reflect it wherever it has been used. But as per changeText method, you're setting up new reference to currentText service property which is wrong.
Rather I'd suggest you just use single reference of an object throughout and it will take care of sharing object between multiple controllers.
Service
app.service("TestService", function ($log) {
var currentText = {}; // private variable
// Passing text property explicitly, and changing that property only
this.changeText = function (text) {
currentText.text = text; // updating property, not changing reference of an object
$log.debug(currentText);
};
this.getCurrentText = function () {
return currentText;
};
});
Now from changeText method just pass on text that needs to be changed to, not an new object.
I need to execute functions of some controllers when my application ends (e.g. when closing the navigator tab) so I've thought in a service to manage the list of those functions and call them when needed. These functions changes depending on the controllers I have opened.
Here's some code
Controller 1
angular.module('myApp').component('myComponent', {
controller: function ($scope) {
var mc = this;
mc.saveData = function(objectToSave){
...
};
}
});
Controller 2
angular.module('myApp').component('anotherComponent', {
controller: function ($scope) {
var ac = this;
ac.printData = function(objects, priority){
...
};
}
});
How to store those functions (saveData & printData) considering they have different parameters, so when I need it, I can call them (myComponent.saveData & anotherComponent.printData).
The above code is not general controller but the angular1.5+ component with its own controller scope. So the methods saveData and printData can only be accessed in respective component HTML template.
So to utilise the above method anywhere in application, they should be part of some service\factory and that needs to be injected wherever you may required.
You can create service like :
angular.module('FTWApp').service('someService', function() {
this.saveData = function (objectToSave) {
// saveData method code
};
this.printData = function (objects, priority) {
// printData method code
};
});
and inject it wherever you need, like in your component:
controller: function(someService) {
// define method parameter data
someService.saveData(objectToSave);
someService.printData (objects, priority);
}
I managed to make this, creating a service for managing the methods that will be fired.
angular.module('FTWApp').service('myService',function(){
var ac = this;
ac.addMethodForOnClose = addMethodForOnClose;
ac.arrMethods = [];
function addMethodForOnClose(idModule, method){
ac.arrMethods[idModule] = {
id: idModule,
method: method
}
};
function executeMethodsOnClose(){
for(object in ac.arrayMethods){
ac.arrMethods[object].method();
}
});
Then in the controllers, just add the method needed to that array:
myService.addMethodForOnClose(id, vm.methodToLaunchOnClose);
Afterwards, capture the $window.onunload and run myService.executeMethodsOnClose()
So I have Googled this quite allot today, and I'm afraid the answers I got, did not satisfy the problem I'm facing. I'm trying to call a method within a controller (controller as syntax) by only using a string value.
I tried window['someFunctionName'](); as well as this['someFunctionName'](); and vm['someFunctionName'](), but non of them seems to work. I'm guessing it's because the function I'm calling is not in a global space, but rather local to the controller - example:
/* #ngInject */
function dashboardController(logger, _, moment) {
var vm = this;
vm.foo = foo
function getSomeData() {
// do something....
}
function foo() {
window['getSomeData']();
this['getSomeData']();
vm['getSomeData']();
}
}
It feels so trivial ... I could do this soooooo easily in C#, but struggling with something that feels so silly!
Any advice?
Thanks!
If you don't want your function to be accessible from your template, you can still create a kind of container for your functions :
var functionContainer = {};
functionContainer.getSomeData = function() {
// Do some stuff
}
Going further, your "get data" function should be in a service that you will inject into your controller.
angular.module('myApp').factory('getDataService', function(){
return {
'getSomeData': getSomeData,
'getOtherData': getOtherData
};
function getSomeData() {
// do some stuff
}
function getOtherData() {
// do other stuff
}
});
/* #ngInject */
function dashboardController(logger, _, moment, getDataService) {
var vm = this;
vm.foo = function () {
getDataService['getSomeData']();
};
}
Can you try
/* #ngInject */
function dashboardController(logger, _, moment) {
var vm = this;
vm.foo = foo
vm.getSomeData = function() {
// do something....
}
function foo() {
vm.getSomeData();
}
}
Just simply call getSomeData(); from foo.
Like:
function foo() {
getSomeData();
}
See this fiddle
I wrote an Angular factory to store an object that I need to pass to diferent controllers. The factory looks like this:
app.factory('SearchEngineConfigs', function () {
var configs = {
isInternational: false,
TripType: 1,
journeys: []
}
var _setInternational = function(val) {
configs.isInternational = val;
}
var _setTripType = function(val) {
configs.TripType = val;
}
var _setJourneys = function(journeys) {
configs.journeys = journeys;
}
var _getConfigs = function() {
return configs;
}
return {
setInternatioanl: _setInternational,
setTripType: _setTripType,
setJourneys: _setJourneys,
getConfigs: _getConfigs
}
});
So I have this factory injected in my controllers. In one of the controllers I'm setting the values of configs' factory like this:
SearchEngineConfigs.setInternatioanl($scope.searchEngine.isInternational);
SearchEngineConfigs.setTripType($scope.searchEngine.TripType);
SearchEngineConfigs.setJourneys($scope.journeys.slice());
So far so good. What is happening now is that everytime I change let's say the $scope.searchEngine.isInternational without calling the factory method SearchEngineConfigs.setInternatioanl this change still being reflected into the factory and thus, updating this property in another controller that is using that factory at the same time of the first controller. How can I avoid this to happen? I only want to change the value of the object inside the factory when I explicity make a call to the correponding factory method.
You could use angular.copy to avoid allowing any references to your internal state objects for existing outside your factory.
Note that you could have to do this on both the input and output, as either could create a leak.
One way of ensuring this was consistant would be to use a decorator function:
function decorate(func) {
return function() {
const argumentsCopy = _.map(arguments, a => angular.copy(a));
const result = func.apply(factory, argumentsCopy);
return angular.copy(result);
};
}
...which in turn is used like this:
var factory = {
setInternatioanl: decorate(_setInternational),
setTripType: decorate(_setTripType),
setJourneys: decorate(_setJourneys),
getConfigs: decorate(_getConfigs)
}
return factory
You can either use the new keyword to have different instances of the service.
Something like, var searchEngineConfigs = new SearchEngineConfigs(); and then use it to invoke factory methods.
Or, you can use the angular.copy() while setting the variables in your service to remove the reference which is causing the update in service.
Something like,
var _setInternational = function(val) {
configs.isInternational = angular.copy(val);
}
When I load a view, I'd like to run some initialization code in its associated controller.
To do so, I've used the ng-init directive on the main element of my view:
<div ng-init="init()">
blah
</div>
and in the controller:
$scope.init = function () {
if ($routeParams.Id) {
//get an existing object
});
} else {
//create a new object
}
$scope.isSaving = false;
}
First question: is this the right way to do it?
Next thing, I have a problem with the sequence of events taking place. In the view I have a 'save' button, which uses the ng-disabled directive as such:
<button ng-click="save()" ng-disabled="isClean()">Save</button>
the isClean() function is defined in the controller:
$scope.isClean = function () {
return $scope.hasChanges() && !$scope.isSaving;
}
As you can see, it uses the $scope.isSaving flag, which was initialized in the init() function.
PROBLEM: when the view is loaded, the isClean function is called before the init() function, hence the flag isSaving is undefined. What can I do to prevent that?
When your view loads, so does its associated controller. Instead of using ng-init, simply call your init() method in your controller:
$scope.init = function () {
if ($routeParams.Id) {
//get an existing object
} else {
//create a new object
}
$scope.isSaving = false;
}
...
$scope.init();
Since your controller runs before ng-init, this also solves your second issue.
Fiddle
As John David Five mentioned, you might not want to attach this to $scope in order to make this method private.
var init = function () {
// do something
}
...
init();
See jsFiddle
If you want to wait for certain data to be preset, either move that data request to a resolve or add a watcher to that collection or object and call your init method when your data meets your init criteria. I usually remove the watcher once my data requirements are met so the init function doesnt randomly re-run if the data your watching changes and meets your criteria to run your init method.
var init = function () {
// do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
if( newVal && newVal.length > 0) {
unwatch();
init();
}
});
Since AngularJS 1.5 we should use $onInit which is available on any AngularJS component. Taken from the component lifecycle documentation since v1.5 its the preferred way:
$onInit() - Called on each controller after all the controllers on an
element have been constructed and had their bindings initialized (and
before the pre & post linking functions for the directives on this
element). This is a good place to put initialization code for your
controller.
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function ($scope) {
//default state
$scope.name = '';
//all your init controller goodness in here
this.$onInit = function () {
$scope.name = 'Superhero';
}
});
Fiddle Demo
An advanced example of using component lifecycle:
The component lifecycle gives us the ability to handle component stuff in a good way. It allows us to create events for e.g. "init", "change" or "destroy" of an component. In that way we are able to manage stuff which is depending on the lifecycle of an component. This little example shows to register & unregister an $rootScope event listener $on. By knowing, that an event $on bound on $rootScope will not be unbound when the controller loses its reference in the view or getting destroyed we need to destroy a $rootScope.$on listener manually.
A good place to put that stuff is $onDestroy lifecycle function of an component:
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function ($scope, $rootScope) {
var registerScope = null;
this.$onInit = function () {
//register rootScope event
registerScope = $rootScope.$on('someEvent', function(event) {
console.log("fired");
});
}
this.$onDestroy = function () {
//unregister rootScope event by calling the return function
registerScope();
}
});
Fiddle demo
Or you can just initialize inline in the controller. If you use an init function internal to the controller, it doesn't need to be defined in the scope. In fact, it can be self executing:
function MyCtrl($scope) {
$scope.isSaving = false;
(function() { // init
if (true) { // $routeParams.Id) {
//get an existing object
} else {
//create a new object
}
})()
$scope.isClean = function () {
return $scope.hasChanges() && !$scope.isSaving;
}
$scope.hasChanges = function() { return false }
}
I use the following template in my projects:
angular.module("AppName.moduleName", [])
/**
* #ngdoc controller
* #name AppName.moduleName:ControllerNameController
* #description Describe what the controller is responsible for.
**/
.controller("ControllerNameController", function (dependencies) {
/* type */ $scope.modelName = null;
/* type */ $scope.modelName.modelProperty1 = null;
/* type */ $scope.modelName.modelPropertyX = null;
/* type */ var privateVariable1 = null;
/* type */ var privateVariableX = null;
(function init() {
// load data, init scope, etc.
})();
$scope.modelName.publicFunction1 = function () /* -> type */ {
// ...
};
$scope.modelName.publicFunctionX = function () /* -> type */ {
// ...
};
function privateFunction1() /* -> type */ {
// ...
}
function privateFunctionX() /* -> type */ {
// ...
}
});