I found a very useful tutorial on creating an angular factory that takes parameters. However useful, there's a hiccup.
Below, the factory creates an instance of a function-object/constructor. The author of the tutorial does not explain where the this "Inventory" constructor should be placed.
Would the "Inventory" constructor go into a separate file, say as an module/IFFE?
/* WHERE DOES THIS INVENTORY OBJECT GO?? */
function Inventory($http, url, project_id) {
/** The public method for getting the project price **/
this.price = function(callback) {
$http.get(url+"?project="+project_id)
.success(function(value) {
callback(value);
});
};
};
angular.factory('InventoryFactory',[
'$http',
/** This is the factory method that Angular will execute only ONCE **/
function InventoryFactory($http) {
/** This is the function that will be injected into the directive, and called multiple times by the programmer **/
return function(url, product_id) {
/** this is the new object that will be created and used by the programmer **/
return new Inventory($http, url, product_id);
};
}]);
angular.directive('inventoryStatus',['InventoryFactory',function(InventoryFactory) {
return {
link: function($scope,$el,$attr) {
var inventory = InventoryFactory('/api/projects',$scope.project_id);
inventory.price(function(value){
$scope.price = value;
});
}
}
}]);
Thanks in advance!
Related
I have 2 js files namely collegeApp.js and branchApp.js. I have two controllers CollegeController.js which is inside collegeApp.js ng-app and BranchController.js which is inside branchApp.js ng-app.
From my html I am redirecting to another page.
Here is my html
<li data-ng-click="getBranchByBranchId(branch.branchId); setBranchId(branch.branchId)">
{{branch.branchName}}
</li>
This html page is in collegeApp.js.After clicking on branch name I am calling method And its controller looks like this.
CollegeController.js
var CollegeController = function($scope, $rootScope, $http, $location, $route,CollegeService,$routeParams) {
$rootScope.pageTitle = $route.current.title;
$scope.getBranchId = function() {
CollegeService.getBranchId().then(function(response) {
$scope.branchId = response.data;
});
}
$scope.setBranchId=function(branchId) {
CollegeService.setBranchId(branchId);
$rootScope.passBranchId = branchId;
window.location.href="./branch?
branchId='+$rootScope.passBranchId";//Here I am redirecting to branch page with id.//
}
}
The branch page is in branchApp.js and above code is in collegeApp.js.
Now in BranchController.js I am trying to catch branchId sent from previous page.
BranchController.js
var BranchController = function($scope, $rootScope, $http, $location, $route,BranchService,$routeParams)
{
$scope.branchId = $rootScope.passBranchId;//Here i am trying to get branchId//
console.log($scope.branchId);//I am getting undefined.
}
I tried $rootScope,$routeParams.But none of them worked.
Is there any possible way that i can pass branchId from collegeApp to branchApp? or am i missing something?
When redirecting your page use $location.path('/branch/' + branchId)
Plus you already have $location in your controller.
Then you'll want to use $routeParams to find the id in your url.
Update route config to find params, should look like this
$routeProvider
...
.when('branch/:branchId', {
templateUrl: 'views/branches.html',
controller: 'BranchController'
})
Then get the value like so
$scope.branchId = $routeParams.branchId;
I realized that you wanted to share information between multiple modules within the same application. Here is a completed code sample to test the scenario.
/**
* Service definition which holds the passed values
*/
angular.module('myapp')
.config('collegeService', collegeService);
collegeService.$inject = [];
function collegeService() {
var branchId = null;
return {
getBranchId: getBranchId,
setBranchId: setBranchId
};
function getBranchId() {
/**
* Implement a promise based approach if the branch ID reads from an external source
* else just return it as given below
*/
return branchId;
}
function setBranchId(brId) {
branchId = brId
}
}
/**
* First controller definition
*/
angular.module('myapp')
.controller('CollegeController', CollegeController);
CollegeController.$inject = ['$scope', 'collegeService'];
function CollegeController($scope, collegeService) {
$scope.getBranchId = function() {
/**
* Use promise based approach as below if the read method returns a promise
*/
collegeService.getBranchId().then(function(response) {
$scope.branchId = response.data;
});
/**
* Uses a simple approach as below if the read method returns the value
*/
// $scope.branchId = collegeService.getBranchId();
};
$scope.setBranchId = function(branchId) {
CollegeService.setBranchId(branchId);
}
}
/**
* Second controller definition
*/
angular.module('myapp')
.controller('BranchController', BranchController);
BranchController.$inject = ['$scope', 'collegeService'];
function BranchController($scope, collegeService) {
$scope.init = function() {
$scope.branchId = collegeService.getBranchId();
};
/**
* Invokes the init method during the Controller getting instantiated
*/
$scope.init();
}
I finally found the solution.I just added this line and it worked.
Inside SchoolController.js
$scope.setBranchId=function(branchId)
{
window.localStorage.setItem("branchId", branchId);
}
And in BranchController.js
$scope.branchId = window.localStorage.getItem("branchId");
Now i am able to use Id anywhere in controller and also i am able to pass Id from collegeApp.js to branchApp.js ng-apps.
I'm working on a project that uses IIFE, a concept that I'm still beginning to grasp. My service seems to be fine, I'm using some Jasmine to determine that it is being defined, but when I try to inject it into my controller I get this error:
Unknown provider: StudentsServiceProvider <- StudentsService <- StudentsController
here is the controller in question:
(function() {
'use strict';
angular
.module('ngInterview.students')
.controller('StudentsController', StudentsController);
StudentsController.$inject = ['StudentsService'];
function StudentsController(StudentsService) {
/**
* Model
*/
var vm = this;
/**
* Initialization
*/
activate();
/**
* Implementations
*/
function activate() {
// Initialization code goes here
vm.students = StudentsService.getStudents();
}
}
})();
And here is the service, just in case I messed up in there somehow:
(function() {
'use strict';
angular
.module('ngInterview.api.students')
.service('StudentsService', StudentsService);
StudentsService.$inject = ['$http'];
function StudentsService($http) {
/**
* Exposed functions
*/
this.getName = getName; // This function serves no purpose. It's just here as an example.
this.getStudents = function() {
return $http({
url: "CUSTOM_URL_HERE",
method: "GET"
}).then(function successCallback(res) {
return res;
}, function errorCallback(res) {
return this.getStudents();
});
}
/**
* Implementations
*/
function getName() {
return 'studentsService';
}
}
})();
All of the files listed above are included in the index.html. If I take out the references to StudentsService, I get no errors and all of the files get instantiated correctly.
Since the service StudentsService is in another module, you have to inject the 'ngInterview.api.students' module in the main module, as below:
angular
.module('ngInterview.students', ['ngInterview.api.students'])
I have a simple factory in AngularJS:
(function(){
'use strict';
angular
.module('myModule', [])
.factory('myService', service);
function service(){
var products= function(p1, p2, p3, ..., pn) {
var url = "http://something.url/api/action";
var data = {
'p1': p1,
'p2': p2,
...
'pn': pn,
}
// return data
return $http
.post(url, data)
.then(function (response) {
return response.data;
});
}
return {
Products : products
};
}
})();
I use this service inside a controller like this:
myInjectedService
.Products(vm.p1, vm.p1, ... , vm.pn)
.then(successCallbackFn)
.catch(failureCallbackFn);
Each parameter (p1, ..., pn) are used to filter the final result. This works like a charm! But with a little drawback: there are to many accepted arguments for Products and is really difficult to know if I'm sending the right parameters and this sounds a little error prone. What I would is a fluent API for service that make everything more human readable, this would be great:
myInjectedService
.Products()
.FilterById(p1)
.WhereCategoryIs(p2)
...
.WhereSomethingElseIs(pn)
.Send()
.then(successCallbackFn)
.catch(failureCallbackFn);
Previously the task of HTTP call was handled by Products call. Right now Products(), only make an empty query (i.e. {}). Each subsequent FilterByX will enrich the query (i.e. {'productId': 'xxx-yyy-1111'}). Calling Send() will make the real HTTP POST call. This call will use the data builded through various filter applied. How can I do that? I'm playing with prototype but without success.
You can archieve what you want by define a new class and use prototype like this.
In a fluent method, remember to return the object itself.
function service(){
var products = function(url) {
// Define a new Product class
var Products = function() {
this.url = url;
this.data = {};
};
// Add the function
Products.prototype.FilterById = function(id) {
this.data.id = id;
// To make it fluent, return the object itself
return this;
};
Products.prototype.FilterByCategory = function(category) {
this.data.category = category;
return this;
};
Products.prototype.send = function() {
console.log(this.data);
};
// Return an instance of the Products class
return new Products();
};
return {
Products : products
};
};
service().Products().FilterById(1).FilterByCategory("Item").send();
You can read more about it here: https://www.sitepoint.com/javascript-like-boss-understanding-fluent-apis/
We have been developing a big product with AngularJS and only recently tried to use use closure compiler for syntax checking with the help of jsdoc comments.
I ran into this problem and can't find any help online, including in SO.
Consider a model class written as a service, and using the class name as a type:
ourmodule.factory('OurModel', function() {
/**
* #constructor
*/
var OurModel = function() {};
return OurModel;
});
ourmodule.controller('Controller1', ['$scope', 'OurModel', function($scope, OurModel) {
/**
* #return {OurModel}
*/
$scope.getNewModel = function () {
return new OurModel();
}
}]);
Closure compiler can't recognize 'OurModel'. What am I missing ?
Closure compiler can't guess that the OurModel that you inject to your controller is the same you declared in the factory, angularJS injection pattern make closure compiler useless in that case.
If you declare OurModel in the parent scope, no warning:
var ourmodule = {
factory: function(a, b){},
controller: function(a, b){}
};
/**
* #constructor
*/
var OurModel = function(){};
ourmodule.controller('Controller1', ['$scope', function($scope) {
/**
* #return {OurModel}
*/
$scope.getNewModel = function () {
return new OurModel();
}
}]);
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 */ {
// ...
}
});