npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@plus-minus/decorators

v0.2.0

Published

An Angular decorators

Downloads

4

Readme

@plus-minus/decorators

An Angular decorators.

Installation

  • npm install --save @plus-minus/decorators

Usage

Mixins

Decorator that helps you implement incomplete multiple inheritance.

Mixin classes do not support DI. Used to refine the behavior of other classes, not intended to spawn self-used objects. The mixins are useful for bringing common functionality into a single place and being reusable.

  class SubscriptionMixin {
     // ...
  }
 
  class GoodbyeMixin {
     // ...
  }
 
  class GreeterMixin {
     public get fullName(): string {
       return `${this.firstName} ${this.surName}`;
     }
 
     public constructor(
       private firstName: string,
       private surName: string
     ) {
     }
 
     public greet(): string {
       return `Hello, ${this.firstName} ${this.surName}!`;
     }
  }

The parent class needs to implement the mixin classes (only the declaration of properties or methods) so that the project can be built without errors at the compilation stage.

If the mixin class has private fields or methods that you do not want to declare in the parent class, then you can create your own type/interface and declare it:

  type GreeterMixinType = Omit<GreeterMixin, 'firstName' | 'surName'>;
  // or
  interface GreeterInterface extends Omit<GreeterMixin, 'firstName' | 'surName'> {}

If you want to pass parameters to the constructor of the mixin class, you need to pass an array where the first element will be the mixin class itself, and all subsequent elements are its parameters.

  import { Mixins } from '@plus-minus/decorators';
 
  @Component({...})
  @Mixins([
     SubscriptionMixin,
     GoodbyeMixin,
     [GreeterMixin, 'John', 'Wick'],
  ])
  class HelloWorld implements GreeterMixinType, SubscriptionMixin, GoodbyeMixin {
     public fullName: string;
     public greet: () => string;
     // Other declarations: SubscriptionMixin, GoodbyeMixin
 
     public constructor() {
       alert(this.greet());
     }
 }

####You can use ready-made mixins from @plus-minus/decorators/mixin-classes

  • SubscriptionMixin - this mixin can unsubscribe from: setInterval, settimeout and Observable subscriptions. Use this type SubscriptionMixinType to declare mixin methods in your class.
    @Component({...})
    @Mixins([SubscriptionMixin])
    export class SomeComponent implements SubscriptionMixinType {
      public addSubId: (id: number | Subscription) => void;
      public clearSubById: (id: number | Subscription) => void;
      public clearAllSubs: () => void;
    
      public constructor() {
          const subTimeoutId = setTimeout(() => alert('Hello World!'), 5000);
          const intervalSubId = interval(1000).subscribe(console.log);
          this.addSubId(subTimeoutId);
          this.addSubId(intervalSubId);
        
          setTimeout(() => this.clearSubById(subTimeoutId), 3000);
          setTimeout(() => this.clearAllSubs(), 6000);
        }
    }

@Memorize

In computing, memoization or memoisation is an optimization technique used
primarily to speed up computer programs by storing the results of expensive
function calls and returning the cached result when the same inputs occur
again.
Notes
Memoization better used only with pure functions.

You can pass a parameter object MemorizeOptions, where:

  • size is the size of the list that needs to be cached, the default value is 20, this means that we can cache a list that does not exceed 20 elements.
  • duration is cache expiration time in milliseconds. Default value is 0ms.
interface MemorizeOptions {
  size?: number;
  duration?: number;
}

Where can you use Memoization? For example, you have a list of products,

interface Product {
  title: string;
  vendorCode: string;
  cost: number; // in cents
}

the name of which is concatenated from several properties from the product object, and the price adjusted based on the exchange rate against the dollar, then your list will, at best, be recalculated once per change detection.

@Injectable()
class ProductService {
  public findAll(): Product[] {
    return [
      {
        title: 'Subframe for Lexus CT',
        vendorCode: '2062CF0EF',
        cost: 41500,
      },
      {
        title: 'Gearbox for Lexus CT',
        vendorCode: '1FCA2E90D',
        cost: 66500,
      },
      {
        title: 'Rear bumper for Lexus IS',
        vendorCode: '186882573',
        cost: 23000,
      },
    ];
  }
}

If you use memoization, then it will be executed once, and the cached value of the result of your method will be given to all subsequent changes.

import { Memorize } from '@plus-minus/decorators';

@Component({
  selector: 'pm-root',
  template: `<div class="col-12">
    <div *ngFor="let product of productService.findAll();" class="d-flex flex-column mb-4">
      <span class="title">{{displayTitle(product)}}</span>
      <span class="cost">{{displayCost(product.cost, currentUAExchangeRate) | currency:'USD'}}</span>
    </div>
  </div>`,
})
export class AppComponent {
  public currentUAExchangeRate = 28.24;

  public constructor(
    public productService: ProductService,
  ) {
  }

  @Memorize()
  public displayTitle({ title, vendorCode }: Product): string {
    return `#${ vendorCode } ${ title }`;
  }

  @Memorize()
  public displayCost(cost: number, exchangeRate: number): number {
    return cost / 100 * exchangeRate;
  }

  @Memorize({ duration: 3000 })
  public someCapitalize(str: string): string {
    return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
  }
}

You can also use the memoization function directly in your code.

import { memorizeFn } from '@plus-minus/decorators';

const displayTitleMemo = memorizeFn(({ title, vendorCode }: Product): string => {
    return `#${ vendorCode } ${ title }`;
}, { 
  size: 5,
  duration: 3000,
});

You can also combine the @Mixins decorator with the @Memorize decorator.

class ProductMixin {
  @Memorize()
  public displayTitle({ title, vendorCode }: Product): string {
    return `#${ vendorCode } ${ title }`;
  }

  @Memorize()
  public displayCost(cost: number, exchangeRate: number): number {
    return cost / 100 * exchangeRate;
  }
}

@Component({/*...*/})
@Mixins([ProductMixin])
export class ProductsComponent implements ProductMixin {
  public displayTitle: (product: Product) => string;
  public displayCost: (cost: number, exchangeRate: number) => number;
}

@RuntimeType

Decorator @RuntimeType(...) is intended for type checking at runtime. It can check primitives, functions and classes, but it cannot compare objects that have been created, for example, via an object literal. Decorator @T(...) is necessary to mark the type of method parameters.

Decorator @RuntimeType(RuntimeTypeOptions) accepts an object with options.

  • level - error output level. By default, this setting is RuntimeTypeLevel.Throw
interface RuntimeTypeOptions {
  level?: RuntimeTypeLevel;
}

With the setGlobalRuntimeTypeOptions(RuntimeTypeOptions) function, you can set options globally for the entire application.

setGlobalRuntimeTypeOptions({
  level: environment.production ? RuntimeTypeLevel.Warn : RuntimeTypeLevel.Error,
});

If you specify options at the method decorator level, then they will take precedence over global ones.

@Component(...)
export class SomeComponent {
  public constructor() {
    this.someMethod('23');
  }

  @RuntimeType({ level: RuntimeTypeLevel.Log })
  public someMethod(@T(Number) param?: any): void {
    // ...
  }
}
> The parameter 'param' must be of type 'Number', but not the type 'string'.

#####More examples

class DataModel {
  // ...
}

class OtherClass  {
  // ...
}

class SecondClass extends DataModel {
  // ...
}

@Component(...)
export class SomeComponent {
  public constructor() {
    this.someMethod('23' as any, new SecondClass(), new OtherClass());
  }

  @RuntimeType()
  public someMethod(
    @T(Number) first: number,
    @T(DataModel) second: DataModel, 
    @T(DataModel)third: DataModel
  ): void {
    // ...
  }
}
// > The parameter 'first' must be of type 'Number', but not the type 'String'.
// > The parameter 'third' must be of type 'DataModel', but not the type 'OtherClass'.

@OverrideProps

This is decorator that overrides the property value in the target class. The decorator take key-value pairs as input, where the key is the name of the target class property and its value, respectively.

For example, we have a User service and a versioned API, but some endpoints are gradually moving to a new version, then using this decorator, you can easily change the version or link to another API server.

@Injectable()
class UserService {
  private version = 'v1.0';
  private serverUrl = 'http://localhost:3000';

  public get apiUrl(): string {
    return `${ this.serverUrl }/${ this.version }`;
  }
  
  constructor(private http: HttpClient) {
  }
    
  @OverrideProps({
     serverUrl: 'http://localhost:9999',
  })
  public findAll(): Observable<User[]> {
    return this.http.get<Response<User[]>>(`${ this.apiUrl }/users`)
      .pipe(map(({ data }) => data));
  }
    
  @OverrideProps({
    version: 'v2.0',
  })
  public findByPk(id: string): Observable<User> {
    return this.http.get<Response<User>>(`${ this.apiUrl }/users/${ id }`)
      .pipe(map(({ data }) => data));
  }
}

@Component({...})
export class UsersComponent {
  public constructor(
    private userService: UserService,
  ) {

    // Request URL: http://localhost:9999/v1.0/users
    this.userService.findAll().subscribe(() => {/*...*/});

    const userId = '123e4567-e89b-12d3-a456-426614174000';

    // Request URL: http://localhost:3000/v2.0/users/123e4567-e89b-12d3-a456-426614174000
    this.userService.findByPk(userId).subscribe(() => {/*...*/});
  }
}