Outils pour utilisateurs

Outils du site


lang:angular:projet

Création

Documentation

Création d'un projet de base : voir le tutorial de Angular Archive du v9.1.6 le 08/05/2020

Convertir un projet Angular en PWA : voir la page PWA de Angular Archive du v9.1.6 le 08/05/2020

Tutorial débutant mais clair : Comment créer une application web avec Angular Archive v11 21/03/21

Formation complète Le guide Angular - Marmicode Archive du 2019 le 28/07/2021 wget --recursive --no-clobber --page-requisites --html-extension --convert-links --restrict-file-names=windows --span-hosts --no-parent --content-disposition --domains=guide-angular.wishtack.io,gblobscdn.gitbook.com https://guide-angular.wishtack.io/

Projet unique

ng new angular

angular est le nom du dossier du nouveau projet

  • Erreurs possibles
...
CREATE angular/src/app/app.component.ts (211 bytes)
CREATE angular/src/app/app.component.css (0 bytes)
⠧ Installing packages (npm)...npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree 
npm ERR!
npm ERR! While resolving: angular@0.0.0
npm ERR! Found: jasmine-core@3.7.1
npm ERR! node_modules/jasmine-core
npm ERR!   dev jasmine-core@"~3.7.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer jasmine-core@">=3.8" from karma-jasmine-html-reporter@1.7.0
npm ERR! node_modules/karma-jasmine-html-reporter
npm ERR!   dev karma-jasmine-html-reporter@"^1.5.0" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR! See C:\Users\legar\AppData\Local\npm-cache\eresolve-report.txt for a full report. 

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\legar\AppData\Local\npm-cache\_logs\2021-07-14T20_17_00_602Z-debug.log
✖ Package install failed, see above.
The Schematic workflow failed. See above.

Cela peut arriver si la version d'Angular est un peu ancienne et que le fichier package.json.template est obsolète.

Supprimer le dossier créé et relancer :

ng new --skip-install [project]

Ici, on voit qu'il faut remplacer karma-jasmine-html-reporter@^1.5.0 par karma-jasmine-html-reporter@1.7.0 et jasmine-core@~3.7.0 par jasmine-core@>=3.8.

et lancer

npm install

unable to resolve dependency tree error for creating new angular project Archive du 07/05/2021 le 14/07/2021

Configuration

Elle se fait avec le fichier tsconfig.json. (TSConfig Reference Archive v5.0 le 15/01/2024)

  • Rubrique compilerOptions

Cette rubrique contient les options pour le compilateur tsc. tsc CLI Options Archive du 04/01/2024 le 10/01/2024

Pour utiliser les dernières fonctionnalités javascript, utiliser esnext.

tsconfig.json
{
  "compilerOptions": {
    ...,
    "target": "esnext",
    "module": "esnext",
    "lib": ["esnext", "dom"],
    ...
  }
}

Il est possible d'ajouter :

    "noImplicitOverride": true,

Et mettre à jour moduleResolution à node16 (node / node10 est déprécié) et propager les autres modifications :

    "moduleResolution": "node16",
    "module": "node16",

et dans package.json : “type”: “module”.

Workspace avec une librairie et une application

ng new workspace --create-application false
cd workspace
ng generate library my-lib
ng generate application my-app

How to Create Library in Angular Tutorial Archive du 01/07/2020 le 25/07/2021

Le tutoriel explique ça très bien.

Par contre, l'utilisation de ng serve sur l'application ne détecte pas les modifications de la librairie. Pour résoudre ce problème, il faut faire une build watch de la librairie :

ng build library --watch &
ng serve application

Ne pas oublier le & pour laisser tourner la tâche en fond ou ne pas le mettre et utiliser deux terminaux différents.

  • Exemple d'architecture des fichiers
.
├── CMakeLists.txt
├── README.md
├── angular.json
├── dist
│   ├── app-main
│   │   ├── 3rdpartylicenses.txt
│   │   ├── assets
│   │   │   └── ...
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   ├── ...
│   └── lib-jessica
│       ├── README.md
│       ├── esm2015
│       │   ├── ...
│       ├── fesm2015
│       │   ├── ...
│       ├── lib
│       │   ├── ...
│       └── ...
├── node_modules
│   └── ...
├── package-lock.json
├── package.json
├── projects
│   ├── app-main
│   │   ├── karma.conf.js
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── ...
│   │   │   ├── assets
│   │   │   ├── environments
│   │   │   │   ├── environment.prod.ts
│   │   │   │   └── environment.ts
│   │   │   ├── favicon.ico
│   │   │   ├── index.html
│   │   │   ├── main.ts
│   │   │   ├── polyfills.ts
│   │   │   ├── styles.css
│   │   │   └── test.ts
│   │   ├── tsconfig.app.json
│   │   └── tsconfig.spec.json
│   └── lib-jessica
│       ├── README.md
│       ├── assets
│       ├── karma.conf.js
│       ├── ng-package.json
│       ├── package.json
│       ├── src
│       │   ├── assets
│       │   │   └── ...
│       │   ├── lib
│       │   │   └── ...
│       │   ├── public-api.ts
│       │   ├── test.ts
│       ├── tsconfig.lib.json
│       ├── tsconfig.lib.prod.json
│       └── tsconfig.spec.json
├── tree.log
└── tsconfig.json

Workspace and project file structure Archive du v12.1.5 le 31/07/2021

Nouvelle vue

Exemple

cd my-app/src/app
ng generate component ui/main

Cela devrait mettre à jour le fichier my-app/src/app/app.module.ts en ajoutant :

import { MainComponent } from './ui/main/main.component';

et en mettant à jour declarations :

declarations: [AppComponent, MainComponent],

Une fois le composant activé, si le routing est activé, il reste à lui fournir un path d'accès. Modifier le fichier my-app/src/app/app-routing.module.ts :

+import { MainComponent } from './ui/main/main.component';
+
-const routes: Routes = [];
+const routes: Routes = [
+  { path: '', component: MainComponent }
+];

Erreurs possibles

  • Could not find an NgModule. Use the skip-import option to skip importing in NgModule.

Il faut lancer la commande ng generate component ui/main depuis le dossier cd my-app/src/app.

  • error NG3001: Unsupported private class xxx. This class is visible to consumers via yyy → xxx, but is not exported from the top-level library entrypoint.

La création d'un composant depuis une librairie ne le met pas automatiquement à disposition des librairies / applications externes.

This class is visible to consumers via SomeModule -> SomeComponent, but is not exported from the top-level library entrypoint Archive du 07/02/2020 le 03/08/2021

Formulaire

Simple

Le code ci-dessous est la méthode réactive. La méthode template est déconseillée.

  • Code HTML

Le code spécifique à Angular est entre [] ou ().

<form [formGroup]="meyerhorForm">
  <label for="width">Width: </label>
  <input id="width" type="text" formControlName="width" />
  <br />
  <label for="load">Load: </label>
  <input id="load" type="text" formControlName="load" />
  <br />
  <label for="eccentric">Eccentric: </label>
  <input id="eccentric" type="text" formControlName="eccentric" />
  <br />
  <button [disabled]="!meyerhorForm.valid" type="submit" (click)="submit()">
    SUBMIT
  </button>
</form>
  • Code Angular
file.component.ts
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import * as Module from './jessica-web';
import { validNumber } from '../util/validator/valid-number.validator';
import { Subscription } from 'rxjs';
import { debounceTime, filter, switchMap } from 'rxjs/operators';
 
@Component({
  selector: 'lib-lib-jessica',
  templateUrl: './lib-jessica.component.html',
  styles: [],
})
export class LibJessicaComponent implements OnInit, OnDestroy {
  private logger?: any;
 
  // Les données accessibles depuis le fichier html doivent être publiques.
  meyerhorForm: FormGroup;
 
  // Par convention, les variables observables ont pour suffixe $.
  private obs$!: Subscription;
 
  submit() {
    console.log(Number(this.meyerhorForm.value.width));
    console.log(Number(this.meyerhorForm.value.load));
    console.log(Number(this.meyerhorForm.value.eccentric));
  }
 
  private instance?: any;
 
  constructor() {
    this.meyerhorForm = new FormGroup({
      // Chaque champ doit être complété et être des nombres.
      width: new FormControl(null, [Validators.required, validNumber]),
      load: new FormControl(null, [Validators.required, validNumber]),
      eccentric: new FormControl(null, [Validators.required, validNumber]),
    });
    Module.default().then(async (instance: any) => {
      this.instance = instance;
      this.logger = new this.instance.SpdlogStdoutMt('log');
    });
  }
 
  ngOnInit(): void {
    // On s'inscrit dans ngOnInit.
    this.obs$ = this.meyerhorForm.valueChanges
      // Limite à une requête toutes les 200ms.
      .pipe(debounceTime(200))
      .pipe(
        // On n'applique l'action que si le formulaire est valide.
        filter(
          () => this.meyerhorForm.valid && this.instance !== undefined
        ),
        switchMap((/*data*/) => {
          ...
 
          return newvalue;
        })
      )
      // Applique la fonction subscribe pour chaque valeur dans switchMap.
      .subscribe((newvalue) => console.log(newvalue));
  }
 
  ngOnDestroy(): void {
    // On se désinscrit dans ngOnDestroy pour éviter les fuites mémoires.
    this.obs$!.unsubscribe();
  }
}

The RxJS library Archive du 12.1.4 le 28/07/2021

  • Notes

Si on veut que switchMap renvoie plusieurs valeurs, il faut return [{ a: valeur1, b: valeur2 }]; si la fonction est synchrone. Mais si la fonction est asynchrone, il faut return { a: valeur1, b: valeur2 };. switchMap est similaire à un map en programmation fonctionnelle. Mais puisque les events sont asynchrones, si un switchMap n'est pas terminé quand le suivant arrive, le plus ancien est abandonné : la fonction définie par subscribe n'est pas appelée mais la totalité de la fonction définie par le switchMap est exécutée.

Ne pas utiliser @Input() meyerhorForm?: FormGroup; sans le définir dans le constructeur. C'est la méthode template.

  • Validateur
valid-number.validator.ts
import { ValidatorFn } from '@angular/forms';
 
export const validNumber: ValidatorFn = (control) => {
  // value ne doit pas être vide. isNan renvoie false si "" ou null.
  if (isNaN(Number(control.value))) {
    return {
      validNumber: {
        reason: 'invalid number',
        value: control.value,
      },
    };
  }
 
  return null;
};

Imbriqué

Dividing a form into multiple components with validation (model driven) Archive du 07/04/2017 le 01/08/2021 Archive du projet

Angular Custom Form Controls with Reactive Forms and NgModel Archive du 19/10/2016 le 01/08/2021 Archive du projet

Angular - Reactive forms Archive du 12.1.5 le 01/08/2021 Archive du projet

Angular Forms Guide - Template Driven and Reactive Forms Archive du 26/01/2021 le 01/08/2021

Validating form input Archive du 12.1.5 le 01/08/2021

Angular: Nested Reactive Forms Using ControlValueAccessors(CVAs) Archive du 09/01/2019 le 01/08/2021 Archive du projet

Never again be confused when implementing ControlValueAccessor in Angular forms Archive du 14/09/2017 le 01/08/2021

C'est le formulaire enfant qui sera inclus dans le parent. L'enfant doit implémenter les interfaces ControlValueAccessor, Validator et ajouter les providers. Le parent doit implémenter le Subscriber.

Dans le cas de formulaire à plus de 2 niveaux : le niveau le plus haut implémente uniquement Subscriber, les niveaux les plus bas implémentent uniquement ControlValueAccessor, Validator et providers, les niveaux intermédiaires doivent tout implémenter.

  • Formulaire de base de l'enfant
foundation-strip-form.component.html
<!-- Le nom de formGroup doit être le même que la variable dans le fichier TypeScript -->
<div [formGroup]="foundation">
  <!-- html5 : le for va avec le id -->
  <!-- le formControlName doit avoir le même nom que le nom dans le fichier TypeScript -->
  <label for="width">Width: </label>
  <input id="width" type="text" formControlName="width" />
</div>

L'interface sera pratique pour exploiter les données depuis la form parent.

foundation-strip.ts
export interface FoundationStrip {
  width: number;
}
foundation-strip-form.component.ts
/* eslint-disable max-len */
import { Component, forwardRef } from '@angular/core';
import {
  ControlValueAccessor,
  FormGroup,
  Validators,
  NG_VALUE_ACCESSOR,
  NG_VALIDATORS,
  ValidationErrors,
  FormBuilder,
  Validator
} from '@angular/forms';
import { PartialObserver } from 'rxjs';
import { validNumber } from '../../../util/validator/valid-number.validator';
/* eslint-enable max-len */
 
@Component({
  selector: 'lib-foundation-strip-form',
  templateUrl: './foundation-strip-form.component.html',
  styleUrls: ['./foundation-strip-form.component.css'],
  providers: [
    {
      // Pour faire fonctionner la fonction writeValue.
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FoundationStripFormComponent),
      multi: true
    },
    {
      // Pour forcer l'implémentation du validator.
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => FoundationStripFormComponent),
      multi: true
    }
  ]
})
export class FoundationStripFormComponent
  implements ControlValueAccessor, Validator
{
  foundation: FormGroup;
 
  // FormBuilder est injecté. Son utilisation est moins verbeuse que "new FormGroup".
  constructor(private fromBuilder: FormBuilder) {
    this.foundation = this.fromBuilder.group({
      // Validators.required : doit être non vide
      // validNumber : validateur perso pour valider un nombre.
      width: [null, [Validators.required, validNumber]]
    });
  }
 
  // ControlValueAccessor
  public onTouched!: () => void;
 
  // val est l'interface contenant les données
  writeValue(val: FoundationStrip): void {
    val && this.foundation.setValue(val, { emitEvent: false });
  }
  registerOnChange(
    fn?: PartialObserver<{ [key: string]: string | undefined }>
  ): void {
    this.foundation.valueChanges.subscribe(fn);
  }
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    isDisabled ? this.foundation.disable() : this.foundation.enable();
  }
 
  validate(): ValidationErrors | null 
    // Si la form foundation ne contient que les sous-formulaires
    // dont les validateurs ne sont pas déclarées l'instanciation
    // this.foundation = this.fromBuilder.group, il faudra faire :
    // return this.form.controls.subform1.valid && this.form.controls.subform2.valid ?
    return this.foundation.valid
      ? null
      : {
          invalidForm: {
            valid: false,
            message: 'basicInfoForm fields are invalid'
          }
        };
  }
}
  • Formulaire de base du parent
meyerhof-form.component.html
<h3>Nested FormGroup</h3>
 
<form [formGroup]="form">
  <!-- formControlName est bien le nom dans le fichier TypeScript parent et non enfant -->
  <lib-foundation-strip-form
    formControlName="foundation"
  ></lib-foundation-strip-form>
  <!-- Divers autre formulaire -->
  <lib-vertical-eccentric-form
    formControlName="load"
  ></lib-vertical-eccentric-form>
  <button type="submit" (click)="submit()">Submit</button>
</form>
 
<!-- Le texte sera mise à jour en temps réel -->
<pre>Form Values: {{ form.valid }} {{ form.value | json }}</pre>
meyerhof-form.ts
/* eslint-disable max-len */
import { FoundationStrip } from '../foundation-strip/foundation-strip';
import { VerticalEccentric } from '../../load/vertical-eccentric/vertical-eccentric';
/* eslint-enable max-len */
 
export interface MeyerhofForm {
  foundation: FoundationStrip;
  load: VerticalEccentric;
}
meyerhof-form.component.ts
import {
  Component,
  EventEmitter,
  OnDestroy,
  OnInit,
  Output
} from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Subscription } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators';
 
import { MeyerhofForm } from './meyerhof-form';
 
@Component({
  selector: 'lib-meyerhof-form',
  templateUrl: './meyerhof-form.component.html',
  styleUrls: ['./meyerhof-form.component.css']
})
// On alloue l'observateur réactif dans OnInit et on le libère dans OnDestroy.
export class MeyerhofFormComponent implements OnInit, OnDestroy {
  form: FormGroup;
  private obs$!: Subscription;
 
  constructor(private fromBuilder: FormBuilder) {
    this.form = fromBuilder.group({
      foundation: [''],
      load: ['']
    });
  }
 
  submit(): void {
    console.log('MeyerhofFormComponent');
    console.log(JSON.stringify(this.form.controls.foundation.value['width']));
    console.log(JSON.stringify(this.form.controls.load.value['load']));
    console.log(JSON.stringify(this.form.controls.load.value['eccentric']));
  }
 
  ngOnInit(): void {
    this.obs$ = this.form.valueChanges
      .pipe(debounceTime(200))
      .pipe(filter(() => this.form.valid))
      // On force l'interface de l'objet a.
      .subscribe((a: MeyerhofForm) => {
        console.log('123/' + JSON.stringify(a.foundation.width) + '/456');
      });
  }
 
  ngOnDestroy(): void {
    this.obs$?.unsubscribe();
  }
}

Transfert de données par évènement

Sharing data between child and parent directives and components Archive du 12.1.5 le 01/08/2021 Archive du projet

Angular 8: Send Form Data from Child Output to Parent with Reactive Forms Archive du 15/11/2019 le 01/08/2021

Le présent paragraphe utilise l'annotation @Output ce qui est un template-driven form. Pour une form réactive, il suffit d'utiliser la méthode du paragraphe précédent en gardant le schéma : l'enfant implémente ControlValueAccessor et Validator, le parent implémente la Subscription.

Dans la form contenant les données, il faut ajouter un attribut d'évènements avec le décorateur @Output.

child.component.ts
export class MeyerhofFormComponent implements OnInit {
 
  @Output() changeEvent = new EventEmitter<MeyerhofForm>();
 
  ngOnInit(): void {
    this.obs$ = this.form.valueChanges
      .pipe(filter(() => this.form.valid))
      .pipe(debounceTime(200))
      .subscribe((a: MeyerhofForm) => {
        // Plutôt que de traiter les données, elles sont envoyés brutes aux parents.
        this.changeEvent.emit(a);
      });
  }
}
parent.component.html
<lib-meyerhof-form (changeEvent)="compute($event)"></lib-meyerhof-form>
parent.component.ts
export class MeyerhofCalcComponent {
 
  compute(newItem: MeyerhofForm): void {
    ...
  }
}

Erreurs

  • error NG8002: Can't bind to 'formGroup' since it isn't a known property of 'div'

Dans xxx.module.ts, il faut ajouter l'import en tête du fichier et l'ajouter également dans l'annotation NgModule :

xxx.module.ts
import { ReactiveFormsModule } from '@angular/forms';
 
@NgModule({
  imports: [..., ReactiveFormsModule]
})
export class ...

Can't bind to 'formGroup' since it isn't a known property of 'form' Archive du 25/08/2016 le 03/08/2021

  • error NG6004: Present in the NgModule.exports of xxx but neither declared nor imported

Dans xxx.module.ts, une classe dans la rubrique exports doit être aussi dans la rubrique declarations ou imports.

xxx.module.ts
import { XXXComponent } from '...';
 
@NgModule({
  declarations: [XXXComponent],
  exports: [XXXComponent]
})
export class ...

Source : Produce a `ts.Diagnostic` for an exported directive or pipe which was not declared or imported by the NgModule in question. Archive du 15/02/2021 le 03/08/2021

Autre message d'erreur possible:

error NG8001: 'xxx' is not a known element:
1. If 'xxx' is an Angular component, then verify that it is part of this module.
2. If 'xxx' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.

Il faut respecter les mêmes contraintes que ci-dessus et aussi ajouter la déclaration dans le fichier public-api.ts.

public-api.ts
export * from '...';
  • TypeError: control.registerOnChange is not a function

Dans le code HTML, les balises contenant formControlName doivent être encapsulées dans des balises <form [formGroup]="...">. Par exemple:

<form [formGroup]="meyerhorForm">
  <label for="width">Width: </label>
  <input id="width" type="text" formControlName="width" />
</form>

What causes the "control.registerOnChange is not a function" error Archive du 04/01/2017 le 03/08/2021

  • NullInjectorError: No provider for FormBuilder!

Cela se produit lors des tests.

Il faut ajouter ReactiveFormsModule dans les imports de xxx.spec.ts.

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ReactiveFormsModule],
      declarations: [...]
    }).compileComponents();
  });

Nullinjectorerror: no provider for formbuilder (I'm importing ReactiveFormsModule) Archive du 04/03/2019 le 05/08/2021

  • No value accessor for form control with name

Ou encore ERROR Error: No value accessor for form control with unspecified name attribute.

Peut se déclencher lors des tests et pas en production.

Dans la page HTML, il faut ajouter ngDefaultControl aux éléments possédant un formControlName et n'étant pas de type input (par exemple, un composant Angular affichant un autre formulaire).

What is ngDefaultControl in Angular? Archive du 28/09/2017 le 06/08/2021

Compilation

Options

Les options sont dans les fichiers tsconfig.json, dans le champ compilerOptions.

{
  "compilerOptions": {
    ...
  },
}

Intro to the TSConfig Reference Archive du v4.1 le 09/10/2021

Javascript

  • json

Pour autoriser l'import depuis des fichiers json, il faut ajouter :

  "resolveJsonModule": true
  • Import Javascript

Pour autoriser l'import depuis des fichiers javascript, il faut ajouter :

  "allowJs": true
  • Import Javascript non module

Pour convertir des imports javascript en des modules, il faut ajouter :

  "allowSyntheticDefaultImports": true

Pas besoin d'utiliser esModuleInterop si ce n'est pas nécessaire.

Séparer la build développement et production

Après de nombreux tests, je n'ai pas réussi à avoir une application avec un module et de la compiler, en même temps, en version de développement et de production. C'est possible de compiler les deux mais chaque compilation va remplacer la précédent.

peerDependencies et les librairies

Les peerDependencies sont les dépendances des librairies.

Si le projet final utilise les mêmes dépendances, il va y avoir conflit sur la localisation des node_modules à utiliser.

ERROR: src/lib/data/geotechnical/meyerhof/meyerhof-form.component.ts:49:13 - error TS2345: Argument of type 'MonoTypeOperatorFunction<any>' is not assignable to parameter of type 'OperatorFunction<any, any>'.  
Types of parameters 'source' and 'source' are incompatible.
Type 'import("G:/Github/jessica/src/angular/node_modules/rxjs/internal/Observable").Observable<any>' is not assignable to type 'import("G:/Github/jessica/src/angular/projects/jessica/node_modules/rxjs/dist/types/internal/Observable").Observable<any>'.
The types of 'operator.call' are incompatible between these types.
Type '(subscriber: import("G:/Github/jessica/src/angular/node_modules/rxjs/internal/Subscriber").Subscriber<any>, source: any) => import("G:/Github/jessica/src/angular/node_modules/rxjs/internal/types").TeardownLogic' is not assignable to type '(subscriber: import("G:/Github/jessica/src/angular/projects/jessica/node_modules/rxjs/dist/types/internal/Subscriber").Subscriber<any>, source: any) => import("G:/Github/jessica/src/angular/projects/jessica/node_modules/rxjs/dist/types/internal/types").TeardownLogic'.
Types of parameters 'subscriber' and 'subscriber' are incompatible.
Type 'Subscriber<any>' is missing the following properties from type 'Subscriber<any>': syncErrorValue, syncErrorThrown, syncErrorThrowable, _unsubscribeAndRecycle, and 2 more.

et Angular détecter une incompatibilité qui n'en ai pas une, même si les deux modules ont la même version.

Il faut donc configurer le fichier tsconfig.app.json.

tsconfig.app.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": [],
    "paths": {
      // Dépendances du projet et des librairies
      "@angular/*": ["./projects/app-main/node_modules/@angular/*"],
      "@ngx-translate/*": ["./projects/app-main/node_modules/@ngx-translate/*"],
      "cldr": ["./projects/app-main/node_modules/cldrjs/dist/cldr"],
      "cldr/*": ["./projects/app-main/node_modules/cldrjs/dist/cldr/*"],
      "globalize/*": [
        "./projects/app-main/node_modules/globalize/dist/globalize/*"
      ],
      // Librairies
      "jessica": ["./projects/jessica/dist"],
      "toolbox": ["./projects/toolbox/dist"]
    }
  },
  ...
}

Il doit bien y avoir un moyen d'éviter ça mais je n'ai pas trouvé.

Ajout de fonctionnalités

Ajouter d'une dépendance javascript

Si le code Javascript n'est pas utilisé dans un code TypeScript

Télécharger bootstrap et copier les dossiers css et js dans projet\ui\assets\bootstrap.

Ajouter la dépendance au fichier package.json :

npm install --save bootstrap@4.5.0

et ajouter les fichiers .css et .js dans tous les .html. Pour cela ajouter aux rubriques build-angular:browser et build-angular:karma (ci-besoin pour les tests) :

            "styles": [
              "src/styles.css",
              "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "src/assets/params/css/index.css"
            ],
            "scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
              "src/assets/params/js/index.js"
            ]
lang/angular/projet.txt · Dernière modification : 2024/01/15 00:28 de root