29

I have an angular 2 webpack project where I currently have some functions that are repeated throughout several components. I would like to inherit all of these components from a "master" class OR component (whichever works), in order to be able to call my functions from all my components that need them.

As an example, if I have a function foo in 3 different components:

foo(s: string){
  console.log(s);
}

I would like you move this function to another file/class/components:

class parent{
  foo(s: string){
    console.log(s);
  }
}

And having someway to call my foo function from my a given component. For instance:

class child{
  constructor(){
    foo("Hello");
  }
}

How would I do this using Angular 2 / Typescript?

Sina Sohi
  • 2,719
  • 9
  • 33
  • 50
  • You can either use inheritance to call these functions from your super class or make a class with static functions to call them from wherever you want. Which one suits you? – eko Nov 23 '16 at 13:25
  • Can you explain to me how to use either, or link me to a source that explains it? I am having trouble understanding where I am supposed to place another file that I can "import" and inherit from for example. – Sina Sohi Nov 23 '16 at 13:27
  • Somehow I've forgotten about dependency injection. As @Chrillewoodz suggested, doesn't dependency injection work? – eko Nov 23 '16 at 13:29

4 Answers4

57

I would use a service, here's a shortened example from one of my apps:

import {Injectable} from '@angular/core';
import * as _ from 'lodash';

@Injectable({
  providedIn: 'root' // Just do @Injectable() if you're not on Angular v6+
})

export class UtilsService {

  findObjectIndex(list: any[], obj: any, key: string): number {

    return _.findIndex(list, function(item) {
      return obj[key] === item[key];
    });
  }

  findObjectByQuery(list: any[], key: string, query: string): any {

    return _.find(list, function(item) {
      return item[key].toLowerCase() === query.toLowerCase();
    });
  }
}

Then you can inject this service into anything, which is really useful and you keep things DRY.

You would simply inject it like so:

import {UtilsService} from 'app/shared';

export MyComponent {

  constructor(private utils: UtilsService) {
    utils.findObjectIndex([], {}, 'id'); // just an example usage
  }
}

EDIT:

As @aalielfeky's answer says you could use static functions.

However, I would personally avoid static functions because they are borderline impossible to test properly as well as giving you hell once the time comes where you need to inject something in the constructor that is going to be used in one of the functions. Since static functions can't use anything that's injected.

Don't make the same mistake as me because you will end up having to rewrite a lot of code.

EDIT 2: You can also use another approach which is to have just normal functions. This can be a good idea if your functions doesn't require dependency injections, which is normally the case for simple helper functions. Simply create a file, say helpers.ts (or one file per function if you have many functions) and do:

export function sum(a: number, b: number) {
  return a + b;
}

or alternative syntax:

export sum(a: number, b: number) => {
  return a + b;
}

Now you can simply import this with either of these import statements (depending on if you have all functions in one file or one file per function):

import { sum } from 'helpers';
import { sum } from 'helpers/sum';

A good thing about this approach is that it's easily tree shakeable and also makes it slightly easier to unit test compared to using a service since you don't need to add extra code to your tests in order to get the service to work.

Chrillewoodz
  • 27,055
  • 21
  • 92
  • 175
  • Do we need to add `private` keyword in front of utils methods? – xkeshav Sep 25 '18 at 09:09
  • 2
    @pro.mean Doing so will result in the functions not being usable outside the service. So no. – Chrillewoodz Sep 25 '18 at 09:10
  • we can not use the function outside of service anyhow. always need to add `private alias: serviceName` in the constructor where we will use it. – xkeshav Sep 25 '18 at 10:06
  • 2
    @pro.mean It's because it's an injectable, which you inject by doing what you said. Adding private to the functions will not make them available even after injecting the service. – Chrillewoodz Sep 25 '18 at 11:15
  • Thank you for the explanation. if I write `public alias:serviceName` than it still works ( sometimes for some service) do not know what is the reason? – xkeshav Sep 25 '18 at 11:55
  • The best approach is in Edit #2 (importing helper functions). Always choose the simplest approach whenever possible. – Patrick May 03 '22 at 02:18
18

You can create class which all methods are static

export class Utils {
    public static log(msg:string){
        console.log(msg);
    }
}

then, just import it where you want to use

import {Utils} from './utils'

class parent{
   foo(s: string){
     Utils.log(s);
   }
}

class child{
   constructor(){
      Utils.log("Hello");
   }
}
aalielfeky
  • 330
  • 1
  • 4
4

If you want to use inheritance it's the extends keyword:

parent.class.ts

class parent{
  foo(s: string){
    console.log(s);
  }
}

child.component.ts

import { Component } from "@angular/core";
import { parent } from "./parent.class";

@Component({
  selector: 'child',
  template: `<div>{{myMessage}}</div>`
})
export class child extends parent {
  myMessage: string = "Hello";

  constructor(){
    super.foo(this.myMessage);
  }
}

http://plnkr.co/edit/iQfqphLCx62Qy5lYVJa5?p=preview

Note that any decorate information is lost, so don't apply it to the base class and expect that the children will have it.

That's pretty much it for using inheritance for these purposes. The other methods of a shared service or even a static class are also viable. It really depends on what you're trying to accomplish with them and what pattern best matches your use case.

silentsod
  • 8,165
  • 42
  • 40
4

You can create utils.ts which contains all common funcitons

export default class Utils {
    static doSomething(val: string) { return val; }
    static doSomethingElse(val: string) { return val; }
}

Then you can use as below

import Utils from './utils'

export class MyClass {
     constructor()
     {
         Utils.doSomething("test");
     }
}
venkat7668
  • 2,657
  • 1
  • 22
  • 26