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.
Related
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.
This question already has an answer here:
Difference between and when to use controllers vs directives?
(1 answer)
Closed 4 years ago.
I'm just beginning AngularJS. When I first started reading about it, it seemed from the beginner tutorials that the controller is the basic building block of an angular app. However, since learning about directives, I've been creating my own little Angular app with only directives, literally not a single controller. I can't see why I would ever need a controller.
The only thing I've ever seen done with a controller is add variables to scope:
angular.controller("myController",
function($scope)
{
$scope.x = 5;
$scope.y = 6;
}
)
But I can do that with a directive too, by using the scope argument passed to the link function.
Is there something else that can be done with controllers, which can't be done with directives? Or at least something which is easier to do with controllers than with directives?
For example, if I just needed to populate scope with some variables x an y, I can just do:
angular.directive(
"myDirective",
function()
{
return {
link: function(scope, element, attributes)
{
scope.x = 5;
scope.y = 6;
}
};
}
);
You can probably write pretty much everything your app needs in a link callback, sure. Note that I'm not even calling it a directive, I'm saying a link callback. A directive is something that defines a custom HTML tag and its associated functionality, a link callback is merely a specific part of that.
The thing is that this is little more than working with jQuery, or using addEventListener to attach behaviour to HTML elements. On the other hand, you can write controllers as classes instead of procedural code manipulating the scope object. Here's my preferred style to write angularjs in typescript:
export default class WidgetController {
error: string;
static $inject = ['$state', 'FooService', 'BarService'];
constructor(
protected $state: angular.ui.IStateService,
protected foo: FooService,
protected bar: BarService
) {}
get fooValue() {
return this.foo.baz;
}
doSomething() {
this.error = null;
this.bar.getSomething().then(data => {
if (data.error) {
this.error = data.error;
} else {
this.$state.go('success');
}
});
}
}
A template for this might look like this:
<h1>{{ $ctrl.fooValue }}</h1>
<button ng-click="$ctrl.doSomething()">Do!</button>
<p>{{ $ctrl.error }}</p>
The controller may be attached to the template using the ui-router:
import WidgetController from 'widget-controller';
module.config(['$stateProvider', ($state: angular.ui.IStateProvider) => {
$state.state('widget', {
controller: WidgetController,
controllerAs: '$ctrl',
templateUrl: '/templates/widget.html',
});
}]);
Or as a component:
module.component('widget', {
templateUrl: '/templates/widget.html',
controller: WidgetController,
bindings: {
error: '#'
}
});
Or using ng-controller or in a number of other ways.
It gives you more flexibility. It allows you to test the controller pretty easily in isolation, since it's just a regular class. It allows you to reuse the controller for different templates, and the same template for different controllers (yes, this can actually be really useful). It's IMO more readable and easier to understand. Specifically using $ctrl. in the template prevents you from building too interdependent nested scopes and explicitly binds the template to use only its controller, instead of some implicit scope.
There are many ways to do things, but the one thing I have figured out over time is that dealing with the scope object is both verbose and annoying, and can easily lead to spaghetti code. So, moving away from that, you're soon arriving at controllers as objects.
I'm trying to get into Nativescript + Angular2, and I read the following in the tutorial:
We’ll build this functionality as an Angular service, which is Angular’s mechanism for reusable classes that operate on data.
What they then do is to create a simple class, like this:
import { Injectable } from "#angular/core";
import { User } from "./user";
#Injectable()
export class UserService {
register(user: User) {
alert("About to register: " + user.email);
}
}
Now, I can't really see the difference between a normal class and a service - this is a very normal class definition.
So, why is it called an "Angular service"?
This creates a basic Angular service with a single method that takes an instance of the User object you created in the previous section.
Also, when using this "service" in the tutorial, it isn't clear to me when this class is instantiated - when is the construction executed? Is the object saved in memory for later use? The only call to the "userservice" in the tutorial is like this:
import { Page } from "ui/page";
import { Component, ElementRef, OnInit, ViewChild } from "#angular/core";
import { User } from "../../shared/user/user";
import { UserService } from "../../shared/user/user.service";
import { Router } from "#angular/router";
import { Color } from "color";
import { View } from "ui/core/view";
#Component({
selector: "my-app",
providers: [UserService],
templateUrl: "./pages/login/login.html",
styleUrls: ["./pages/login/login-common.css", "./pages/login/login.css"]
})
export class LoginComponent implements OnInit {
user: User;
isLoggingIn = true;
#ViewChild("container") container: ElementRef;
constructor(private router: Router, private userService: UserService, private page: Page) {
this.user = new User();
this.user.email = "bla#bla.com";
this.user.password = "1234";
}
//.... methods and stuff...
}
A class, in that context, is a regular class as in any other OO language: a "prototype" of objects which you can create instances simply using:
let myInstance = new MyClass(<arguments...>);
So, actually, an Angular service is also a class.
But consider services a special kind of class. The main difference between regular classes and service classes is their lifecycle, specially who creates their instance (who calls new).
Instances of a service are created - and managed (disposed) - by the Angular "container" (angular injector, actually).
You can also "inject" instances of service classes in constructors of other service classes (or other managed components).
A good resource in the capabilites of services is Angular's Dependency Injection Guide.
When is the construction executed?
The injector executes the construction. When? See below.
Is the object saved in memory for later use?
It could be. Depends on where you registered the service.
Before anything, know that Angular DI is a hierarchical injection system.
If you register the service with an Angular Module, the service will be created by the application's root injector. So everyone below it (aka everyone in that module) will receive the same instance of the service. In other words, Angular (will call the injector only once and) will create only one instance of the service class and pass that same instance to whoever asks for that service. And that instance will live as long as that module lives.
Now, if you register the service with a component, then the service will be registered with that component's injector. So when such component requests an instance of the service, angular will call the injector and create an instance. If any child of that component asks for an instance of such service, angular will provide the same instance. No one else, only children of the component, will receive that same instance. When that component dies, the service instance dies as well.
How does a "regular class" differ? It lacks the Injector?
The difference is not only the lack of an injector.
Angular aside, just JavaScript: you create an instance of a "regular class" by calling let instance = new MyRegularClass() somewhere in your code, right?
This instance has no "magical effects", it does nothing more than any class would (just regular JavaScript methods and properties). Example: if you need instances of other classes as arguments in the constructor, no one will "magically" create you those instances and pass them. You will have to create them manually, when calling new (e.g. new MyClass(new SomeOtherClassIDependOn(), ...)). If you want to instantiate SomeOtherClassIDependOn only once and reuse the same instance everywhere it is needed, you will have to save that instance and pass it wherever it is neeed yourself.
As services, though, angular can take some of that burden off your shoulders.
Now, before anything: since every service, deep down, is a class, someone has to call new MyServiceClass(). The difference is that someone is not you anymore. There is no new UserService() in your code. So, who is it? This someone is the Injector.
When Angular notices someone asks for a service, it calls for the injector to instantiate that service. The injector then calls let serviceInstance = new MyServiceClass(<dependencies>) and adds some "magic" to it (e.g. it can pass - inject - instances of other services to the constructor of a service), and make it available (save it) for anyone that requests that service in the scope you registered it.
Note: You can call new UserService(...) yourself, as it UserService is a class. But this instance is a regular object, not managed by angular, there is no magic (no constructor arguments will be injected, no instance is saved and reused).
I am writing my web apps with AngularJs and TypeScript. That works great, but I've a conceptual problem. In most cases I use AngularJs Services to encapsulate some functionality like a typed abstraction for localstorage.
But I am not sure how to handle some simple helper functions, create a simple helper service with AngularJs or simple class with TypeScript whats the best way in a AngularJs project.
I would also reuse this helper functions in my other AngularJs Projects
Example implemented as service at the moment:
module App.Views.Shared {
export interface IPasteSrv {
getPastedText($pasteEvent): string;
}
export class PasteSrv implements IPasteSrv {
static $inject = [];
constructor() { }
public getPastedText($pasteEvent): string {
var pastedText = "";
if (window.clipboardData) { //IE
pastedText = window.clipboardData.getData('Text');
} else if ($pasteEvent.originalEvent.clipboardData) {
try {
pastedText = $pasteEvent.originalEvent.clipboardData.getData('text/plain');
} catch (ex) {
pastedText = undefined;
}
}
if (pastedText) {
//verhindern das der Text im Model eingefügt wird.
$pasteEvent.preventDefault();
}
return pastedText;
}
//#region Angular Module Definition
private static _module: ng.IModule;
public static get module(): ng.IModule {
if (this._module) {
return this._module;
}
this._module = angular.module('pasteSrv', []);
this._module.service('pasteSrv', PasteSrv);
return this._module;
}
//#endregion
}
}
It depends on what you want to achieve. Usually I make a distinction between behavior that is generic and behavior that is common. Generic, broadly speaking, means sharable between projects and common means sharable throughout parts of your project.
What you qualify as simple helper functions I would probably qualify as generic. That code should not even have to depend on Angular and don't need to share state in any form. Typescript modules with exported functions would be a way to go. Of course an entire framework like Angular qualifies as generic as well, but since your question is a bit vague I'll just simply state that framework building requires a different perspective on code. The user is now another coder and you don't know what he/she might want to do with your framework in the future (the open/closed principle of the solid principles comes to mind here).
Your getPastedText function is generic; I would thus simply put it in a Clipboard module and export that function. There is no shared state, so there is no need for classes. There is no need to involve Angular, so no need to involve angular modules or services. Keep it simple.
I read this style guide for angular from johnpapa. There is a snippet:
/*
* recommend
* Using function declarations
* and bindable members up top.
*/
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.getAvengers = getAvengers;
vm.title = 'Avengers';
activate();
function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}
function getAvengers() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
So my question is in functions activate() and getAvengers(), they both reference variable (dataservice) and function (getAvengers()) outside of their scope. Is this proper use? Should I bind these 2 in the variable vm instead, e.g:
vm.getAvengers = getAvengers;
vm.dataservice = dataservice;
...
function activate() {
return vm.getAvengers().then(....);
}
function getAvengers() {
return vm.dataservice.getAvengers().then(.....);
}
Specifically for your case
Would say if you are meaning to use this within angular app would recommend not exposing the service, exposing it through this object does not add value and might down the road, when a less experienced developer modifies your code, might result in wonky access to shared dependencies.
If you want access to the dataservice objects functionality across multiple entities then register it as an angular service, and inject it to the different entities that need it.
In General
Both of the ways you are describing are perfectly correct use, but as is usually the case the answer which to use is "it depends."
Why you would use one for another would be if you wanted to expose the variable externally (i.e. if you wanted to let others access that object through the returned object, expecting others to dynamically change the service on your object)
So in this example you should ask yourself a few question
Do I want to expose this object through another object or do I want to let angular DI pass this along to the other controllers that need this functionality
Do I want to allow external entities to modify this object
Does exposing this service through my object make the use of the perceived use of this object more confusing?
But again for this particular case you should not expose it through your object ( through your variable vm, which is bound to the return object this, in this case )
The vm is a acronym for a view model (a object representation of your view) it is meant to be used within your view to bind elements, ui events to it. The dataservice and the logger seems to nothing to do with the view at all, they are just services used within a controller. If you assign them to the vm then you probably create a tightly coupling between your view and services thus it seems like a not a very good idea to me. You can think about the VM as a interface (glue) between your view and controller.
Here is a picture of the interactions between view model, controller, view and services.