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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@superdevofficial/carrot

v0.3.7

Published

> Carrot est un module bit qui contient un certain nombre d'outil pour développer une application Angular avec une API FeathersJS > {.is-info}

Readme

Angular - Carrot

Carrot est un module bit qui contient un certain nombre d'outil pour développer une application Angular avec une API FeathersJS {.is-info}

Requirements

  • npm >= 6.10
  • Node >= 10.10
  • Angular Core >= 8.2.14
  • Angular Material
  • FeathersJS

Dependances

Liste reprise depuis le fichier package.json :

  • "mingo": "^3.0.2"
  • "@angular/material": "^8.0.1"
  • "@angular/router": "~8.2.14"
  • "@angular/core": "^8.0.0",
  • "@angular/forms": "^8.0.0",
  • "@angular/common": "^8.0.0",
  • "lodash": "^4.0.0",
  • "rxjs": "^6.0.0"

Ajoutez ces dependences dans le projet dans lequel vous inserez le module.

Installation dans une app Angular

Pour simplement l'utiliser dans l'application :

npm i @superdevofficial/superdev.angular.carrot

Ensuite il vous suffit d'utiliser les components/modules et autres classes de cette manière :

import { Model, Id, AlertService ... } from '@superdevofficial/superdev.angular.carrot';

Configuration Docker

Pour que Carrot puisse fonctionner dans une image Docker :

# ------------------------
# exemple : Ultima-Display
# ------------------------

FROM node:10-alpine AS base
WORKDIR /app

FROM base AS builder
ARG BIT_NODE_TOKEN
RUN apk add --update git && rm -rf /tmp/* /var/cache/apk/*

#prod or staging
ARG ANGULAR_ENV
RUN apk add --update git && rm -rf /tmp/* /var/cache/apk/*
COPY client/package.json client/package-lock.json ./
RUN npm set progress=false && npm config set depth 0 && npm cache clean --force && npm config set unsafe-perm true

# add dependencies to build packages
RUN apk --no-cache add --virtual native-deps \
  git g++ gcc libgcc libstdc++ linux-headers make python && \
  npm config set python /usr/bin/python && \
  npm install node-gyp -g && \
  echo -e "@bit:registry=https://node.bit.dev\n//node.bit.dev/:_authToken=$BIT_NODE_TOKEN" >> ~/.npmrc

## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
RUN npm i && mkdir dev && cp -R ./node_modules ./dev

WORKDIR /app/dev

COPY client .

## Build the angular app in production mode and store the artifacts in dist folder
RUN $(npm bin)/ng build --prod --configuration=$ANGULAR_ENV

FROM base AS release
WORKDIR /app

RUN npm config set unsafe-perm true

# Install app dependencies
RUN npm -g install buffet

COPY --from=builder /app/dev/dist /app

EXPOSE 8080
# serve app folder on port 8080
CMD ["buffet", "--notFoundPath", "index.html","--no-log","--no-watch"]

Améliorer Carrot

Il faut en premier lieu installer Bit sur sa machine local : Comment-installer-bit-pour-les-nuls

Puis importer Carrot dans un dossier ./modules/ via la commande :

bit import superdev.angular/carrot

Il faudra aussi configurer votre package.json pour prendre en compte le nouveaux module :

{
  "bit": {
    "env": {
      "compiler": "cas-aanzee.test/compilers/[email protected]"
    },
    "componentsDefaultDirectory": "src/modules/{name}",
    "packageManager": "npm",
    "overrides": {
      "*": {
        "dependencies": {
          "@angular/core": "-",
          "@angular/forms": "-",
          "@angular/common": "-",
          "lodash": "-",
          "rxjs": "-"
        },
        "devDependencies": {},
        "peerDependencies": {
          "@angular/core": "^8.0.0",
          "@angular/forms": "^8.0.0",
          "@angular/common": "^8.0.0",
          "lodash": "^4.0.0",
          "rxjs": "^6.0.0"
        }
      }
    }
  }
}

Une fois votre application configurée, il suffiera de modifier les fichiers directement dans le dossier /modules/carrot/**

Classes, Types, Bases

Id

Il s'agit d'un type qui représente les primaryKeys mongo et mysql :

export type Id = string | number

DeletedAt

Il s'agit d'un type à appliquer à la propriété deletedAt pour pouvoir gérer le softDelete de chez FeathersJS :

export type DeletedAt = null | Date | string | false | 0

export interface IDeletedAt {
  deletedAt?: DeletedAt
  isDeleted(): boolean
}

Model

Il s'agit d'une base pour les modèles de votre application pour fonctionner avec une API FeathersJS :

export interface IModel {
  id: Id
  className: string
  createdAt: string
  updatedAt: string
  validate(): boolean
  validateBeforePost(): boolean
  validateBeforeUpdate(): boolean
  sanitize(idKey: string): any
  sanitizeBeforePost(idKey: string): any
  sanitizeBeforeUpdate(idKey: string): any
  patchValue(datas: any)
  toObject(): object
  isDeleted(): boolean
}

Cette base permet d'accéder à la clé primaire du modèle via l'index id. Il n'y a plus besoin de se soucier de la BDD.

Pour étendre la classes, certaines conditions sont à prendre en compte :

export class MonModele extends Model implements IMonModele {
  /**
  _forbbidenFields: permet d'ignorer des champs lors de l'envoie à la BDD
  */
  protected _forbbidenFields: string[] = [
    '_primaryKey',
    '_primaryKeyIndex',
    '_className',
    '_forbbidenFields',
    '_fieldsNeedConstructor',
  ]

  /**
  _fieldsNeedConstructor: liste les champs qui font référence à un autre 
  modèle de votre application
  */
  protected _fieldsNeedConstructor: any = { user: User }

  /**
  !!required!!
  _primaryKeyIndex: il s'agit du nom de la valeur clé primaire 
  de votre modèle cté BDD ( "_id", "id" )
  */
  protected _primaryKeyIndex = '_id'

  /**
  La méthode "this.initialize(datas)" doit être appelé dans le constructor
  */
  constructor(datas: any = {}) {
    super()
    this.initialize(datas)
  }
}

QueryBuilder

Il s'agit d'une classe permettant de construire les queries d'une requête (post, get, update ...). Chaque queryBuilder doit être lié à une propriété d'un modèle ( exemple un queryBuilder pour les emails ). Voici l'interface du queryBuilder :

export interface IQueryBuilder {
  key: string
  name: string
  value: any
  formControl: FormControl
  applyFilter(query: IQuery): IQuery
  isQueryBuilder(): boolean
  reset()
  initFormControl()
}

Les queryBuilder sont définit au sein d'un QueryManager et peuvent se partager entre service. Il sont listé sous cette forme :

{
	name: 'email',
  key: 'index_de_la_bdd',
  filter: {
  	$lte: true,
    $in: null,
    $nin: null,
    $lt: null,
    $lte: null,
    $gt: null,
    $gte: null,
    $ne: null,
    $or: null,
    $search: null
	}
},

La propriété "filter" peut accepter tous les filtres listés plus haut. Mais il est possible de réaliser un filtre custom via un callback :

{
	name: 'email',
  key: 'index_de_la_bdd',
  filter: filter: function (query: IQuery) {
		if (Helpers.isRealNumber(parseInt(this.value)) && parseInt(this.value) !== 0) {
			query[this.key] = parseInt(this.value);
		} else if (this.value != null) {
			query.name = { $search: this.value };
		}

		return query;
	}
}

Un queryBuilder créé directement un FormControl utilisable n'importe où dans votre applciation. Ce formControl se met directement à jour dés que le queryBuilder voit sa valeur changer :

/**
 *	Dans un lointain component... Très lointain
 * 	qui étend la TableComponent
 */
maFunction(nouvelleValeur: any) {
 	if (nouvelleValeur) {
 		this.queriesBuilders.email.value = nouvelleValeur;
	} else {
  	this.queriesBuilders.email.reset();
  }

  this.queriesBuilders.email.formControl.onValueChange().subscribe(e => console.log(e));
}

Services

ApiService

C'est une classe abstraite à appliquer à votre service qui communiquera avec votre API FeathersJS. Voici son interface :

export interface IApiService {
  suffix: string
  getAppConfig(): { appDomain: string; apiDomain: string }
  getResultData(res: any)
  getApiUrl(suffix?: string): string
  getRaw<T>(url: String, options?: any): Observable<any>
  getOne<T>(T: new (any?) => T, url: String, options?: any): Observable<T>
  get<T>(T: new (any?) => T, url: String, options?: any): Observable<T[]>
  postRaw<T>(url: String, data?: any, options?: any): Observable<any>
  postOne<T>(
    T: new (any?) => T,
    url: String,
    data?: any,
    options?: any,
  ): Observable<T>
  updateOne<T>(
    T: new (any?) => T,
    url: String,
    data?: any,
    options?: any,
  ): Observable<T>
  updateRaw<T>(url: String, data?: any, options?: any): Observable<any>
  deleteRaw(url: String, options?: any): Observable<any>
}

Et voici un exemple d'application :

@Injectable()
export class MonApiService extends ApiService {
  suffix = '/mon-suffix-api'

  constructor(protected http: HttpClient) {
    super(http)
  }

  /**
   * Retourner la configuration Angular d'environnement
   */
  public getAppConfig(): { appDomain: string; apiDomain: string } {
    return { appDomain: AppConfig.appDomain, apiDomain: AppConfig.apiDomain }
  }
}

AuthenticationService

C'est une classe abstraite à appliquer à votre service qui gère les connexions et la gestion de l'utilisateur connecté avec votre API FeathersJS. Voici son interface :

export interface IAuthenticationService {
  authentificated: BehaviorSubject<boolean>
  getUser(): any
  setUser(user: any)
  getToken(): string
  isAuthentificated(): boolean
  login(email: string, password: string)
  verify(token: string): Observable<boolean | any>
  logout()
}

Et voici un exemple d'application :

@Injectable()
export class AuthenticationService extends CarrotAuthService {
  /**
   *	Ne pas oublier le prefix de la route auth
   */
  protected prefix: string = 'mon-prefix'

  /**
   *	La route réel de l'authentification
   */
  protected authPath: string = '/ud/authentication'

  /**
   *	Pour éviter des pertes de mémoires
   */
  protected currentUser: User

  constructor(protected http: HttpClient, protected api: UltimaApiService) {
    super(http, api)
  }

  /**
   * Get current authentified user
   * @return User
   */
  public getUser(): any {
    if (!this.currentUser) {
      let partialUser: any = this.get('currentUserAuth')
      if (partialUser) {
        this.currentUser = new User(partialUser)
      } else {
        this.currentUser = null
      }
    }

    return this.currentUser
  }

  /**
   * Get current authentified user
   * @return User
   */
  public setUser(user: any) {
    if (user) {
      this.currentUser = _.isString(user.className) ? user : new User(user)
      this.store('currentUserAuth', user)
    }
  }
}

FeathersService

Cette classe permet de reproduire directement un service FeathersJS pour accéder à une ressource. Pour fonctionner, il faut préciser un type de modèle à la place de T. Voici le cul :

export interface IFeathersService<T> {
  path: string
  model: new (any?) => T
  primaryKeyIndex: string
  find(queries?: any): Observable<T[]>
  get(id: Id, queries?: any): Observable<T>
  post(data: T, queries?: any): Observable<T | T[]>
  post(data: T[], queries?: any): Observable<T | T[]>
  update(id: Id, data: T, queries?: object): Observable<T>
  update(data: T, queries?: object): Observable<T>
  delete(idOrQueries?: Id | T, queries?: object): Observable<any>
}

ResolverService

Cette classe étend le FeathersService pour proposer une gestion global des ressources. A chaques requêtes, la liste des entités se met automatiquement à jour et il est possible d'y souscrire via un observable : entityList. Comme le FeathersService, elle a besoin d'un type de modèle pour fonctionner. Voici son interface :

export interface IResolverService<T> {
  currentEntity: T
  currentEntity$: Observable<T>
  entityList: T[]
  entityList$: Observable<T[]>
  optionsList: object
  isNew: boolean
  isListEmpty: boolean
  resolve(id?: Id): Observable<T>
  emitCurrentEntity(entity: T)
  getCurrentEntity(id: Id): Observable<T>
  postCurrentEntity(options?: object): Observable<T | T[] | boolean>
  updateCurrentEntity(options?: object): Observable<T | boolean>
  deleteCurrentEntity(options?: object): Observable<T>
  refreshCurrentEntity()
  refreshEntityList()
}

Ce service gère aussi les ressources appelées via une route grâce au Resolver de Angular. Ainsi il est possible d'accéder à l'entité demandée par une route via la variable currentEntity Un exemple d'utilisation du ResolverService dans un Resolver Angular:

@Injectable()
export class UsersResolver implements Resolve<Observable<User>> {
  constructor(private service: UsersService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
  ): Observable<User> {
    return this.service
      .resolve(route.params.id || null)
      .pipe(map((result: User) => (result instanceof User ? result : null)))
  }
}

Ce service peut aussi savoir si l'entité actuelle ( celle demandée par la route ) est nouvelle ou existante via la variable isNew.

Un ResolverService peut aussi attacher un QueryManager pour gérer de manière dynamique et cross-route les query de ses requêtes.

QueryManager

Il s'agit d'un service Angular qui permet, une fois attaché à un ResolverService, de gérer les queries de la requêtes (filtres, sorts, pagination ...). Voici son interface :

export interface IQuery {
  [x: string]: any
}

export interface IQueryManager {
  query$: Observable<IQuery>
  query: IQuery
  $sort: any
  $total: number
  $skip: number
  $limit: number
  $index: number

  buildFormGroup(): FormGroup
  getFormControl(key?: string): FormControl | FormControl[]
  has(key: string): boolean
  get(key: string): IQueryBuilder
  set(query: IQueryBuilder)
  sortBy(key: string, value: any)
  getFilter(key: string): any
  filterBy(key: string, value: any)
  resetFilter(key?: string)
  resetSort()
  resetPagination()
  resetAll()
}

Habituellement, il est constitué de plusieurs QueryBuilder qui correspondent chacuns à une propriété de la ressource ( du modèle ). Chaques queryBuilders va altérer les queries de la requêtes en fonction des valeurs qu'y lui sont attribués. Voici un exemple d'un QueryManager :

@Injectable({ providedIn: 'root' })
export class UserQueryManager extends QueryManager {
  constructor() {
    super([
      /**
       *	Voici un exemple d'un custom QueryBuilder
       */
      {
        name: 'term',
        filter: function (query: IQuery) {
          if (Helpers.isSet(this.value)) {
            query.$or = [
              { email: { $regex: this.value } },
              { firstName: { $regex: this.value } },
            ]
          }

          return query
        },
      },
      /**
       *	Par défaut un QueryBuilder appliquera un filtre type "like"
       * 	exemple ci-dessous : { query: { firstname: 'Bobby' } }
       */
      { name: 'firstName' },
      /**
       * 	exemple ci-dessous pour le filtre "status" :
       * 	{ query: { isActive: 'ma-valeur' } }
       */
      { name: 'status', key: 'isActive' },
      /**
       *	QueryBuilders par défaut pour gérer les variables softDelete,
       * 	createdAt et updatedAt
       */
      new UpdatedAtQueryBuilder(),
      new CreatedAtQueryBuilder(),
      new SoftDeleteQueryBuilder(),
    ])
  }
}

AlertService

L'alertService de chez Material est directement disponible via Carrot. On peut utiliser les méthode success et error pour directement afficher une alert. Voici son interface :

export interface IAlertService {
  success(message: string, keepAfterNavigationChange: boolean)
  error(message: string, keepAfterNavigationChange: boolean)
  getMessage(): Observable<AlertResponse>
}

Il faut penser à appeler le service dans le AppModule de votre applciation :

import { CarrotAlertModule } from '@superdevofficial/superdev.angular.carrot';

@NgModule({
  declarations: [
  	...
	],
  imports: [
		...,
    CarrotAlertModule
  ],
})
export class AppModule { }

Components

TableComponent

Ce component est à utilisé sur les listing d'éléments pour pouvoir gérer la pagination, les filtres, les sorts, la recherche. Voici son interface :

export interface ITableComponent<T> {
  $total(): number
  $skip(): number
  $limit(): number
  $index(): number
  queriesBuilders: { [x: string]: IQueryBuilder }
  onQueryChange(): Observable<IQuery>
  filterForm: FormGroup
  onListChange(entities: T[])
  onFilterChange(event: any)
  onSortChange(event: ISortChangeOptions)
  resetFilter(key?: string)
  resetSort()
  resetPagination()
  resetAll()
  resetPaginationAndFilter()
}

Il a besoin du service qui gère les éléments en question, d'un queryManager qui gère les filtres du service et du modèle représentant les éléments.

Le TableComponent donne aussi accès à deux autres propriétés protégées : service, queryManager {.is-warning}

Cette classe est a étendre sur un component qui utilise MatTable de Material :

@Component({
  selector: 'app-mon-component',
  templateUrl: './mon-component.component.html',
  styleUrls: ['./mon-component.component.scss'],
})
export class MonComponent extends TableComponent<MonModel, MonModelService> {
  get mesElements() {
    return this.service.entityList
  }

  get mesElements$() {
    return this.service.entityList$
  }

  constructor(protected injector: Injector) {
    /**
     * !!!	Le modelService et le modelQueryManager sont required !!!
     */
    super(injector, MonModelService, MonModelQueryManager)
  }

  ngOnInit() {
    super.ngOnInit()
    this.usersService.find().subscribe()
  }
}

Ainsi il est possible d'utiliser côté HTML, le formulaire de filtre, la table avec les sorts, la pagination :

<form [formGroup]="filterForm">
  <!-- connecter vos filtres ( queryBuilder ) avec un formControlName -->
  <input
    formControlName="search"
    matInput
    placeholder="{{'SEARCH'|translate}}"
  />
  <!-- resetPaginationAndFilter() permet de tout remettre à zéro -->
  <button mat-button (click)="resetPaginationAndFilter()">Reset</button>
  <!-- Vous pouvez accéder aux valeurs des filtres : -->
  <div *ngIf="queriesBuilders.search.value === 'hello'">
    Don't write hello into this search bar please !
  </div>
</form>

<!-- connecter le matSortChange avec le onSortChange($event) -->
<table
  mat-table
  [dataSource]="mesElements$ | async"
  matSort
  (matSortChange)="onSortChange($event)"
>
  <ng-container matColumnDef="name">
    <mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
    <mat-cell *matCellDef="let element">{{element.name}}</mat-cell>
  </ng-container>
  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</table>

<h3 *ngIf="mesElements.length === 0">Pas de résultats</h3>

<!--Les valeurs de la pagination sont à l'image de FeathersJS-->
<mat-paginator
  [length]="$total"
  [pageIndex]="$index"
  [pageSize]="$limit"
  [pageSizeOptions]="[5, 10, 25, 100]"
  (page)="onPaginationChange($event)"
></mat-paginator>

AlertComponent

Le alertComponent de chez Material est aussi disponible via Carrot :D

Helpers

Des helpers viennent compléter ceux déjà fournis par Lodash ( disponible dans le rayon suppositoire de Carrot ). Voici une fausse interface :

export interface IHelpers {
  static isId(value: any): boolean
  static isModel(value: any): boolean
  static isSet(value: any): boolean
  static isEmpty(value: any): boolean
  static isRealNumber(value: any)
}