angular.injector not behaving as imagined - javascript

I am trying to use angular.injector as a service locator to retrieve a service in the base class from which many controllers are derived. I'm doing this because I don't want to have to modify the constructor signature of dozens of controllers in order to get hold of an instance of my service. I am more than happy to sacrifice having a rogue service like this in order to avoid lots of risky work on my controllers.
My base controller looks like this:
module Controllers {
"use strict";
export class MyBaseController {
static $inject = ["$q"];
protected qService: angular.IQService;
private myService: Services.MyService;
constructor($q: angular.IQService) {
this.qService = $q;
// falls over on this next line
this.myService = angular.injector(["myApp"]).get("MyService");
}
protected myBehaviour(details: any) {
this.myService.myServiceMethod(details);
}
}
}
My service looks like this:
module Services {
"use strict";
export class MyService {
static $inject = ["$modal"];
private modal;
private modalInstance;
constructor($modal) {
this.modal = $modal;
}
public myServiceMethod() {
// do something with $modal
}
}
}
The error on the line in my controller looks like this:
Error: [$injector:unpr] Unknown provider: $rootElementProvider <- $rootElement <- $animate <- $compile <- $modalStack <- $modal <- ApplicationErrorService
Am I misunderstanding the function of injector? Is the problem that I can't create an instance of the service because the service itself has dependencies that cannot be resolved?
Much appreciation of any help on this one.

Is the problem that I can't create an instance of the service because the service itself has dependencies that cannot be resolved
Yes. Specifically $rootElement is not available. This is a tell tale sign that the code is executing before angular has been bootstrapped : https://docs.angularjs.org/api/ng/function/angular.bootstrap

Related

Factory Design Pattern with Dependency Injection in Angular

I'm trying to use the factory design pattern in Angular, but I think I'm doing something wrong, or at least wondering if there's a better way. I have a factory which returns a car class depending on the type the user specifies (e.g., "mazda" returns a Mazda class, and "ford" returns a Ford class). Each car class uses multiples services.
constructor(
private userService: UserService,
private logService: LogService
) {}
create(
info
):
| Mazda
| Tesla
| Ford
switch (info.type) {
case 'mazda':
return new Mazda(info, this.userService, this.logService);
case 'tesla':
return new Tesla(info, this.userService, this.logService);
case 'ford':
return new Ford(info, this.userService, this.logService);
}
}
}
My issue is when I'm creating the factory in a component, I need to inject the dependencies.
this.carFactory = new CarFactory(this.userService, this.logService);
It seems weird that my component would need to know about which dependencies my factory needs. Is there a way to create a factory in Angular where you don't need to inject the dependencies into the factory? Something like
this.carFactory = new CarFactory();
you can solve this as following.
Declare above class as provider (Mazda,Tesla,Ford)
In factory class inject 1 dependancy "Injector".
In create function return as following
return this.injector.get(Tesla)
by this way you can resolve your dependancy dynamically.
You could define the CarFactory as a dependency:
#Injectable(providedIn: 'root') // or provided wherever you need it
export class CarFactory {...}
and then just inject it, where you want to use it:
constructor(private carFactory: CarFactory) {
const car = carFactory.create({type: 'tesla'});
}
Since the CarFactory is a dependency in the dependency injection hierarchy, it can inject, whatever is provided in this part of the Angular app.

Conditionally include service in angular controller [duplicate]

I have defined a service like this :
angular.module('myApp').service('myService', [
'$rootScope',
...
...
I want my service to be instantiated only for new user (i.e. when user.createdAt > today).
So is there a way to conditionally inject my service or at least destroy my service without any side effect if the user is an old one.
You can use the $injector service to get inject-ibles dynamically if you need to:
app.controller('DemoCtrl', function($injector) {
this.clicky = function() {
myFact = $injector.get('myFactory');
myFact();
};
});
app.factory('myFactory', function() {
return function() {
alert('foobar!');
};
});
Here's a full running demo: http://jsbin.com/bemakemaja/1/edit
And the $injector docs: https://docs.angularjs.org/api/auto/service/$injector
As a general guideline though I'd recommend not designing your services such that simply injecting them has side effects.
use factory instead of service, so that you can have some checking logic in your service
angular.module('myApp').factory('myService', function () {
if (newUser) {
return { // your service implementation here
}
}
return undefined;
};
So is there a way to conditionally inject my service or at least destroy my service without any side effect if the user is an old one.
Best to capture this logic inside the service:
class Service{
static $inject = ['$rootScope'];
constructor($rootScope){
if (user.createdAt > today){ /*something*/ }
else {/*other thing*/}
}
}
NOTE: services are singletons so conditional injection doesn't see like a desirable solution.
Also to learn about $inject : https://www.youtube.com/watch?v=Yis8m3BdnEM

AngularJS/Typescript Integration Pattern - Scope Methods

I am attempting change the way I am writing AngularJS apps from a plain-javascript to using TypeScript as a pre-processor.
I am struggling to reconcile the two approaches when it comes to scoped method calls.
For illustrative purposes, let's consider the common menu use-case; I wish to highlight a specific menu item which is currently displayed. The HTML template looks like this:
<ul class="nav navbar-nav">
...
<li ng-class="{active: isSelected('/page1')}">Page 1</li>
...
</ul>
This anticipates a scoped function called isSelected. With old-school javascript coding, I' write this as:
$scope.isSelected = function(path) {
return $location.path().substr(0, path.length) == path;
}
This anonymous function declaration doesn't really seem to honor the more traditional class model of TypeScript. In typescript, I find myself tempted to write this:
export interface MenuScope extends ng.IScope {
isSelected(path: String): boolean;
}
export class MenuController {
location: ng.ILocationService;
scope: MenuScope;
constructor($scope: MenuScope, $location: ng.ILocationService) {
this.scope = $scope;
this.location = $location;
this.scope.isSelected = function(path) { return this.isSelected(path) }.bind(this);
}
isSelected(path: String): boolean {
return this.location.path().substr(0, path.length) == path;
}
}
In this case, isSelected belongs to the controller, rather than the scope. This seems sensible. However, the "link" between the scope and controller still relies on an anonymous method.
Even worse, I've had to explicitly bind the context of this to ensure I can write this.location to access the location service in the implementation of isSelected().
One of the benefits I am looking for from TypeScript is a clearer way of writing code. This indirection through a binded anonymous function seems to be the antithesis of this.
You shouldn't store your $scope as a variable in this but instead use this as the $scope, the way to do this is to do $scope.vm = this; in the beginning of the constructor, now every function and variable of the class will be part of the $scope.
You can't avoid this.location = $location because this is the syntax of TypeScript.
BTW you should use $inject for the dependencies.
Here is a simple app with a controller and a service. I use this style in my projects:
/// <reference path="typings/angularjs/angular.d.ts" />
module App {
var app = angular.module("app", []);
app.controller("MainController as vm", Controllers.MainController);
app.service("backend", Services.Backend);
}
module App.Controllers {
export class MainController {
public persons: Models.Person[];
static $inject = ["$location", "backend"];
constructor(private $location: ng.ILocationService, private backend: Services.Backend) {
this.getAllPersons();
}
public isSelected(path: string): boolean {
return this.$location.path().substr(0, path.length) == path;
}
public getAllPersons() {
this.backend.getAllPersons()
.then((persons) => {
this.persons = persons;
})
.catch((reason) => console.log(reason));
}
}
}
module App.Services {
export class Backend {
static $inject = ["$http"];
constructor(private $http: ng.IHttpService) { }
public getAllPersons(): ng.IPromise<Models.Person[]> {
return this.$http.get("api/person")
.then((response) => response.data);
}
}
}
module App.Models {
export interface Person {
id: number;
firstName: string;
lastName: string;
}
}
I have modules of app, controllers, services and models.
controller defined as a class but must be registered to app through controller as syntax. So everything you define in class is accessible through vm in the view (controller scope). Here we have persons, isSelected and getAllPersons.
You can inject every injectable through static $inject that is a string[], then add them as constructor parameters respectively. This role is also usable when defining services and it is minifiable.
You can also inject $scope to your controller class to access scope specific tools such as $apply, on etc.
Instead of defining factories you can define services to be able to define them as a class.
Injecting in services is the same as injecting in controllers.
You can define return type of you http calls as ng.IPromise<Model> then return response.data to ensure that type of your return method is just entities, not http related data.
We were considering a similar conversion (e.g. from Javascript to Typescript for Angular). There were certain things (like your example) that looked very odd as we started to implement. The quickest way to go from what you have is to use the controller as syntax. This way, you expose methods directly on the controller.
<!-- use controller as syntax -->
<div ng-controller="MenuController as menu">
<ul class="nav navbar-nav">
...
<li ng-class="{active: menu.isSelected('/page1')}">Page 1</li>
...
</ul>
</div>
This would allow you to get past the need to bind the scope's method back to that on the controller. Things I don't like about this approach:
Each injectable (e.g. $scope, $location) is now available directly through the controller. This might not be a big deal, but seems undesirable when you want to know exactly what the controller can do and keeping things properly scoped.
The generated code of classes seems overly cluttered and not optimized for minification... This is more of a pet peeve of mine where you trade the ease of using something familiar like class for code that still has room for optimization. See generated code for Inheritance at the Typescript Playground (extends function generated for each .js file where you want to extend a class unless you have your references are on point, the prototype of the function could be cached to add methods to it instead of ClassName.prototype.method for each and every method...), but I digress
The other option is to not use classes, but to stick to strongly typed functions:
export function MenuController($scope: MenuScope, $location: ng.ILocationService): any {
$scope.isSelected = function(path:string): boolean {
return $location.path().substr(0, path.length) == path;
}
}
Since angular is responsible for instantiating the controller, the return type any doesn't matter. But you could get caught if you had a typo on your $scope's method.
To get around this, you can go a step further by using controller as syntax. The example below won't let you actually new up your controller yourself (e.g. new MenuController($location) would fail with only void function can be called with new keyword), but this is negligible since angular handles the instantiation for you.
export interface IMenuController {
isSelected(path: string): boolean;
}
export function MenuController($location: ng.ILocationService): IMenuController {
var self:IMenuController = this;
self.isSelected = function(path:string): boolean {
return $location.path().substr(0, path.length) == path;
}
// explicitly return self / this to compile
return self;
}
TL DR: I'm a fan of the compile time checking of types, and would love to use the concept of class. However, I don't think it completely fits with the Angular 1.x model. It seems like this is designed to work for Angular2. Use strongly typed functions instead for Angular 1.x.

Angular, inject factory into provider

Having a little trouble doing this, I know you need to do it with $get, however the examples I find don't quite fall into the structure I need.
My provider looks like so
.provider('moduleOb', function(){
var modules = {};
//outer closure to inject "this"
function outerAddState(moduleObj) {
};
//outer function so we can push into module
function modulePush(currentModule, name){
}
return {
goState: function(name, stateName, options) {
//I need to use this function in here which injected from a factory
checkUrl.requestUrl();
},
moduleConstructor: function(name, cb) {
},
$get: function $get() {
return this;
}
}
})
So I'm just planting the get right now at the bottom as you can see. I need to be able to inject the checkurl factory somehow so I have access to it in goState. How would I format it so I can use the get to do so?
So if I separate the factory out into a separate module I can inject it with
var injector = angular.injector(['urlParsing']);
var checkUrl = injector.get('checkUrl')
inside the provider. However, the checkurl function has a dependency on $location. So the injector cannot find $location now --
Error: [$injector:unpr] Unknown provider: $locationProvider <- $location <- checkUrl
Can I inject the location provider too? Is this the best way of doing this?
Maybe with a get ($location)? Or maybe there is a better work around. Could use some help, this has been holding me up all day! Thanks for reading.
Edit2: After a few days of wrestling with this - I realize I'm just looking how to inject multiple modules ( a factory and it's dependancies of $log and $location) to a provider. The real reason being so I can let people set what I want them to set in the .config.
I have temporarily circumvented this by changing this provider to a factory and using the .run instead of the .config BUT I would still love to be able to use .config instead.

What is the proper way to load dependencies in a controller super class of an AngularJS application?

I am building an AngularJS application that provides basic CRUD features. That app is built using TypeScript to take advantage of types and classes. I have a base controller that needs access to several AngularJS dependencies. Originally, I injected the dependencies as follows
File: base-controller.ts
class BaseController {
private $location;
private $routeParams;
constructor($location, $routeParams) {
/* Manually inject dependencies */
this.$location = $injector.get('$location');
this.$routeParams = $injector.get('$routeParams ');
}
}
File: list-controller.ts
class WorkRequestListController extends BaseController {
/* Write code to override base controller or implement local methods */
}
angular.module('app.workRequests')
.controller('WorkRequestListController', ['$location', '$routeParams',
($location, $routeParams) => new WorkRequestListController($location, $routeParams)
]);
This solution works, but requires my subclass to be aware of the dependencies required by my base class. If I ever need another dependency in my base class, I would have to change every subclass and the statement which instantiates the controller. To fix these issues, I now simply pass $injector into my base class as follows:
File: base-controller.ts
class BaseController {
private $location;
private $routeParams;
constructor($injector) {
/* Load dependencies */
this.$location = $injector.get('$location');
this.$routeParams = $injector.get('$routeParams');
}
}
File: list-controller.ts
class WorkRequestListController extends BaseController {
/* Write code to override base controller or implement local methods */
}
angular.module('app.workRequests')
.controller('WorkRequestListController', ['$injector',
($injector) => new WorkRequestListController($injector)
]);
Here's my question: Is this the correct way to load dependencies in a super class without forcing the subclass or other code to be aware of the dependencies?
Is this the correct way to load dependencies in a super class without forcing the subclass or other code to be aware of the dependencies?
This is a way. Angular doesn't recommend inheritance in controllers. It prefers composistion. The common functionality should go in a service/factory that gets injected into your controllers.
There's actually a really simple way to implement a base controller using the $controller service. I wrote a blog post about it recently, here's the code snippet showing how it works:
'use strict';
angular.module('Diary')
// base controller containing common functions for add/edit controllers
.controller('Diary.BaseAddEditController',
['$scope', 'DiaryService',
function ($scope, DiaryService) {
$scope.diaryEntry = {};
$scope.saveDiaryEntry = function () {
DiaryService.SaveDiaryEntry($scope.diaryEntry);
};
// add any other shared functionality here.
}])
.controller('Diary.AddDiaryController',
['$scope', '$controller',
function ($scope, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
}])
.controller('Diary.EditDiaryController',
['$scope', '$routeParams', 'DiaryService', '$controller',
function ($scope, $routeParams, DiaryService, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
$scope.diaryEntry = data;
});
}]);

Categories

Resources