{"id":26560852,"url":"https://github.com/jlguenego/angular-tools","last_synced_at":"2026-05-07T13:38:57.122Z","repository":{"id":150325470,"uuid":"437044245","full_name":"jlguenego/angular-tools","owner":"jlguenego","description":":toolbox: Misc tools for Angular ERP application","archived":false,"fork":false,"pushed_at":"2022-05-13T08:34:21.000Z","size":479,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-22T13:36:31.982Z","etag":null,"topics":["angular","dark-theme","light-theme","offline","pwa"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jlguenego.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-10T16:30:25.000Z","updated_at":"2022-01-07T13:01:27.000Z","dependencies_parsed_at":"2023-04-11T21:47:01.407Z","dependency_job_id":null,"html_url":"https://github.com/jlguenego/angular-tools","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jlguenego/angular-tools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlguenego%2Fangular-tools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlguenego%2Fangular-tools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlguenego%2Fangular-tools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlguenego%2Fangular-tools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jlguenego","download_url":"https://codeload.github.com/jlguenego/angular-tools/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlguenego%2Fangular-tools/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266004474,"owners_count":23862962,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","dark-theme","light-theme","offline","pwa"],"created_at":"2025-03-22T13:30:13.525Z","updated_at":"2026-05-07T13:38:57.084Z","avatar_url":"https://github.com/jlguenego.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\" style=\"text-align: center; color: green; font-weight: bold\"\u003e\n  \u003ch1\u003e@jlguenego/angular-tools\u003c/h1\u003e\n  \u003cimg src=\"docs/logo.svg\" height=\"50px\"\u003e\n  \u003cp\u003eSimple and useful tools for angular apps.\u003c/p\u003e\n\u003c/div\u003e\n\n```\ncd \u003cangular-app\u003e\nnpm i @jlguenego/angular-tools\n```\n\n- [JlgWidgetsModule](#jlgwidgetsmodule)\n  - [directive autofocus](#directive-autofocus)\n- [Validators](#validators)\n  - [JlgValidators class](#jlgvalidators-class)\n    - [integer](#integer)\n  - [DuplicateAsyncValidator](#duplicateasyncvalidator)\n- [Interceptors](#interceptors)\n  - [Credentials](#credentials)\n  - [Network](#network)\n  - [Timeout](#timeout)\n- [Services](#services)\n  - [AngularToolsConfigService](#angulartoolsconfigservice)\n  - [ColorSchemeService](#colorschemeservice)\n  - [CrudService](#crudservice)\n  - [NetworkService](#networkservice)\n  - [TitleService](#titleservice)\n- [Module Connect](#module-connect)\n  - [interfaces](#interfaces)\n    - [User](#user)\n  - [Services](#services-1)\n    - [Authentication](#authentication)\n    - [Authorization](#authorization)\n    - [OAuth2](#oauth2)\n  - [Guards](#guards)\n    - [Authentication](#authentication-1)\n    - [Authorization](#authorization-1)\n- [Module OfflineStorage](#module-offlinestorage)\n  - [Interceptors](#interceptors-1)\n    - [NetworkInterceptor](#networkinterceptor)\n  - [Services](#services-2)\n    - [CacheService](#cacheservice)\n    - [NetworkService](#networkservice-1)\n- [Authors](#authors)\n\n# JlgWidgetsModule\n\n## directive autofocus\n\nLike the HTML autofocus attribute, but refresh when the component starts.\n\nJust set the focus,\n\n```html\n\u003cinput jlgAutofocus /\u003e\n```\n\nor set the focus and select all the input text.\n\n```html\n\u003cinput jlgAutofocus=\"select\" /\u003e\n```\n\n# Validators\n\nThe Angular out of the box validators are great but we need more.\n\n## JlgValidators class\n\n### integer\n\nThis validator checks if the input is an integer.\n\n```ts\nnew FormControl(1, [Validators.required, JlgValidators.integer]);\n```\n\n## DuplicateAsyncValidator\n\nDo a http request to get a JSON array response. If the response is not empty then the field is invalid. The error object is:\n\n```js\n{\n  duplicate: {\n    value: control.value;\n  }\n}\n```\n\nExample:\n\n```ts\nnew FormControl('', {\n  validators: [\n    Validators.required,\n    Validators.maxLength(10),\n    Validators.minLength(3),\n  ],\n  asyncValidators: [\n    this.duplicateAsyncValidator.validate(\n      (val: string) =\u003e '/api/articles?filter[name]=' + val\n    ),\n  ],\n}),\n```\n\n# Interceptors\n\n## Credentials\n\nSet the `withCredentials: true` to all HTTP requests.\n\nIn `app.module.ts` set the providers as follows:\n\n```ts\nproviders: [\n    {\n      provide: HTTP_INTERCEPTORS,\n      useClass: CredentialsInterceptor,\n      multi: true,\n    },\n  ],\n```\n\n## Network\n\nThe NetworkService can work correctly only if the NetworkInterceptor is\ninstalled. This interceptor detects if the browser can access to the\nback-end server and set the NetworkService status$ observable accordingly.\n\nThe interceptor will set the network status to `online` or `offline` according the HTTP response.\n\n```ts\nproviders: [\n    {\n      provide: HTTP_INTERCEPTORS,\n      useClass: NetworkInterceptor,\n      multi: true,\n    },\n  ],\n```\n\n## Timeout\n\nThe TimeoutInterceptor adds a timeout on every HTTP request made with the HttpClient service.\n\n```ts\nproviders: [\n  {\n    provide: HTTP_INTERCEPTORS,\n    useClass: TimeoutInterceptor,\n    multi: true,\n  }\n],\n```\n\n# Services\n\n## AngularToolsConfigService\n\nThis service is only for configuring all the other tools of this library.\n\nTo change the configuration, you should subclass this service, and change only the parameters you need.\n\nIn the `app.module.ts` file:\n\n```ts\nproviders: [\n  // ...\n  {\n    provide: AngularToolsConfigService,\n    useClass: CustomAngularToolsConfigService,\n  },\n],\n```\n\nYou create a service called `CustomAngularToolsConfigService` like this:\n\n```ts\nimport { Injectable } from \"@angular/core\";\nimport { AngularToolsConfigService } from \"@jlguenego/angular-tools\";\n\n@Injectable({\n  providedIn: \"root\",\n})\nexport class CustomAngularToolsConfigService extends AngularToolsConfigService {\n  override timeoutMsg = `Le serveur n'a pas répondu dans le délai imparti de ${this.timeoutDelay}ms.`;\n}\n```\n\nYou can see all the configuration parameters inside the `AngularToolsConfigService` class.\n\n## ColorSchemeService\n\nThis service tracks and controls the prefered color scheme in css, and the primary hue.\n\n- The theme adds a `dark` or `light` class to the body HTML element that is synchronized with the css prefered color scheme.\n- The theme adds in the local storage the user preferences about the `dark` or `light` wanted color scheme.\n- The service exposes a method `toggleColorScheme` to toggle the color scheme.\n- The service uses 2 favicons that must be located under `assets/favicon-dark.svg` and `assets/favicon-light.svg`.\n\n```ts\n// to change the theme from 'dark' to 'light' and vice versa.\ntoggleColorScheme();\n```\n\n## CrudService\n\nAllow to Create, Retrieve, Update, Delete documents on a REST backend.\n\nThis service is designed to work well with the [crudity library](https://github.com/jlguenego/crudity).\nIt works with Observables.\n\nFirst declare a service like this:\n\n```ts\nimport { Injectable } from \"@angular/core\";\nimport { CrudService } from \"@jlguenego/angular-tools\";\nimport { Article } from \"../interfaces/article\";\n\n@Injectable({\n  providedIn: \"root\",\n})\nexport class ArticleService extends CrudService\u003cArticle\u003e {\n  getEndpoint(): string {\n    return \"/api/articles\";\n  }\n}\n```\n\nThis service exposes the following:\n\n- `documents$`: BehaviorSubject\u003cArticle[]\u003e that reflect the list of documents\n- `retrieveAll(): Observable\u003cvoid\u003e`\n- `add(): Observable\u003cvoid\u003e`\n- `remove(set: Set\u003cT\u003e): Observable\u003cvoid\u003e`\n\nSo components can call these at will.\n\n## NetworkService\n\nGive an observable reflecting the network status (`online`, `offline`).\n\n## TitleService\n\nThis service set the `document.title` (title in the browser tab) to `\u003cdefaultTitle\u003e: \u003cpageTitle\u003e`.\nThe pageTitle is set to the route definition under `data.title`.\n\nExample:\n\n`app-routing.module.ts`\n\n```ts\n{\n  path: 'legal',\n  component: LegalComponent,\n  data: {\n    title: 'Mentions Légales',\n  },\n},\n```\n\n`app.component.ts`\n\n```ts\nimport { Component } from \"@angular/core\";\nimport { TitleService } from \"@jlguenego/angular-tools\";\n\n@Component({\n  selector: \"app-root\",\n  templateUrl: \"./app.component.html\",\n  styleUrls: [\"./app.component.scss\"],\n})\nexport class AppComponent {\n  constructor(private titleService: TitleService) {\n    this.titleService.setDefaultTitle(\"Gestion Stock\");\n  }\n}\n```\n\nwill set the `document.title` to `Gestion Stock: Mentions Légales` when the router navigates to `/legal`.\n\nTHe service take the data recursively through the children page. If the children has no data title then it tries to get the parent data title.\n\n# Module Connect\n\nThis module gives all user management tools.\n\n## interfaces\n\n### User\n\n```ts\nexport interface User {\n  id: string;\n  displayName: string;\n  email: string;\n  identityProvider: string;\n}\n```\n\n## Services\n\n### Authentication\n\nThe authentication service gives an behavior subject `user$` that returns `undefined` when no user is connected, and a user object when someone is connected.\n\nThe service has the following methods:\n\n- `isConnected()`: It gives a way to test if someone is connected to the back-end by calling an API.\n- `disconnect()`: disconnect a user\n- `setAfterLoginRoute()`: Specify the route url to go after being logged.\n\nAs you can see, you cannot connect through the authentication service. You need the `Oauth2Service` to connect.\n\n### Authorization\n\nThe authorization service gives access to the authorization config for a given connected user.\n\n- `getAuthConfig()`: get the user authorization config from the back-end.\n- `canGoToPath(path: string): Observable\u003cboolean\u003e`: indicates if the connected user can go the specified route path.\n- `can(privilege: string): Observable\u003cboolean\u003e`: indicates if the connected user has the given _privilege_.\n\nA **privilege** is just a string, that the developer can configure on the\nback-end side, in the authorization config object in order to specify what the\nuser can do or cannot do with the front-end. The developer can disable/enable\nbutton, hide/show text, etc. according a privilege.\n\n### OAuth2\n\nThe OAuth2 service allows to connect a user with the OAUTH2 protocol and a given\nidentity provider (like Github, Google, Facebook, Twitter, Microsoft Azure AD,\netc.)\n\nIt just gives what the front-end needs: the url config for the \"Connect with ...\" button/link.\nThe rest is done by the back-end. The method is the following:\n\n- `getAuthorizeUrl(provider: string): string`: for a given provider (GITHUB, etc.), return the url that the connect button needs.\n- `config$` is a BehaviorSubject that show the full configuration for all\n  providers. If you need the provider list, look at this object.\n\n## Guards\n\n### Authentication\n\nThis guards redirects to the `/user/login` page if the user is not authenticated.\n\nThe guard uses the `AuthenticationService`.\n\n### Authorization\n\nThis guards redirect to the `/403` page if the user is not authorized to go the desired route path.\n\nThe guards uses the `AuthorizationService`.\n\nExample:\n\nTips: you should always test the authentication guard before the authorization guard.\n\n```ts\nconst routes: Routes = [\n  {\n    path: \"\",\n    component: StockComponent,\n    canActivate: [AuthenticationGuard, AuthorizationGuard],\n  },\n  {\n    path: \"add\",\n    component: AddComponent,\n    canActivate: [AuthenticationGuard, AuthorizationGuard],\n  },\n];\n```\n\n# Module OfflineStorage\n\nThis module allows the Angular application to run HTTP request in offline mode.\nThe offline mode is detected via the `NetworkInterceptor`.\n\nIn the `app.module.ts` declare the module with the forRoot method:\n\n```ts\n imports: [\n    BrowserModule,\n    AppRoutingModule,\n    // ...\n    OfflineStorageModule.forRoot({ /* config */ }),\n  ],\n```\n\n## Interceptors\n\n### NetworkInterceptor\n\nThe `NetworkInterceptor` is already added to the interceptors when the module is declared.\n\n## Services\n\n### CacheService\n\nPart of the OfflineStorage module.\n\nThe CacheService gives some primitives that allows a persistant cache to be used to manage _progressive web request_.\nIn this library we call **Progressive web request** an HTTP request that has the following properties:\n\n- GET method:\n  - online: the request is done with the back-end, then the response is stored in the front-end cache.\n  - offline: the response is extracted from the front-end cache.\n- POST/PUT/PATCH/DELETE methods:\n  - online: the request is done on the back-end, and normally the user does not need the response.\n  - offline: the request cannot be done on the back-end, so the request is stored in the front-end cache. The stored request is called an _offline order_. The offline order will be executed as soons as the front-end will be able to reach the back-end (online mode). The request is also functionnaly executed, but with the front-end cache.\n\nThe cache service gives the way to load image that are stored in the cache into an image HTML element `\u003cimg\u003e`. The method is `cacheService.loadImage(img: HTMLImageElement, url: string)`.\n\n### NetworkService\n\nThe network service exposes an observable that reflects the network status (online, or offline) in real time.\n\n# Authors\n\nJean-Louis GUENEGO \u003cjlguenego@gmail.com\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlguenego%2Fangular-tools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjlguenego%2Fangular-tools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlguenego%2Fangular-tools/lists"}