=====Création===== ====Documentation==== Création d'un projet de base : voir le [[https://angular.io/tutorial|tutorial de Angular]] {{ :lang:angular:projet:angular9-tutorial.zip |Archive du v9.1.6 le 08/05/2020}} Convertir un projet Angular en PWA : voir la page [[https://angular.io/guide/service-worker-getting-started|PWA de Angular]] {{ :lang:angular:projet:angular_-_getting_started_with_service_workers_2020-05-08_7_14_25_pm_.html |Archive du v9.1.6 le 08/05/2020}} Tutorial débutant mais clair : [[https://www.ganatan.com/|Comment créer une application web avec Angular]] {{ :lang:angular:projet:ganatan-v11-210321.7z |Archive v11 21/03/21}} Formation complète [[https://guide-angular.wishtack.io/angular/|Le guide Angular - Marmicode]] {{ :lang:angular:projet:marmicode.tar.xz |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 [[https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/workspace/files/package.json.template|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 [[https://stackoverflow.com/questions/67433893/unable-to-resolve-dependency-tree-error-for-creating-new-angular-project|unable to resolve dependency tree error for creating new angular project]] {{ :lang:angular:projet:web_-_unable_to_resolve_dependency_tree_error_for_creating_new_angular_project_-_stack_overflow_2021-07-14_22_55_22_.html |Archive du 07/05/2021 le 14/07/2021}} ====Configuration==== Elle se fait avec le fichier ''tsconfig.json''. ([[https://www.typescriptlang.org/tsconfig|TSConfig Reference]] {{ :lang:angular:projet:typescript_tsconfig_reference_-_docs_on_every_tsconfig_option_14_01_2024_23_58_59_.html |Archive v5.0 le 15/01/2024}}) * Rubrique ''compilerOptions'' Cette rubrique contient les options pour le compilateur tsc. [[https://www.typescriptlang.org/docs/handbook/compiler-options.html|tsc CLI Options]] {{ :lang:angular:projet:typescript_documentation_-_tsc_cli_options_10_01_2024_05_36_09_.html |Archive du 04/01/2024 le 10/01/2024}} Pour utiliser les dernières fonctionnalités javascript, utiliser ''esnext''. { "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 [[https://www.samarpaninfotech.com/blog/how-to-create-library-in-angular-tutorial/|How to Create Library in Angular Tutorial]] {{ :lang:angular:projet:how_to_create_library_using_angular_9_step-by-step_tutorial_2021-07-25_15_08_45_.html |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 [[https://angular.io/guide/file-structure|Workspace and project file structure]] {{ :lang:angular:projet:angular_-_workspace_and_project_file_structure_2021-07-31_09_06_47_.html |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. [[https://stackoverflow.com/questions/60121962/this-class-is-visible-to-consumers-via-somemodule-somecomponent-but-is-not-e|This class is visible to consumers via SomeModule -> SomeComponent, but is not exported from the top-level library entrypoint]] {{ :lang:angular:projet:angular_-_this_class_is_visible_to_consumers_via_somemodule_-_somecomponent_but_is_not_exported_from_the_top-level_library_entrypoint_-_stack_overflow_2021-08-03_18_38_59_.html |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 ''()''.



* Code Angular 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(); } } [[https://angular.io/guide/rx-library|The RxJS library]] {{ :lang:angular:projet:angular_-_the_rxjs_library_2021-07-28_21_31_12_.html |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 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é=== [[https://stackoverflow.com/questions/43270564/dividing-a-form-into-multiple-components-with-validation|Dividing a form into multiple components with validation]] (model driven) {{ :lang:angular:projet:angular_-_dividing_a_form_into_multiple_components_with_validation_-_stack_overflow_2021-08-01_14_32_37_.html |Archive du 07/04/2017 le 01/08/2021}} {{ :lang:angular:projet:angular-hzq5vy.zip |Archive du projet}} [[https://coryrylan.com/blog/angular-custom-form-controls-with-reactive-forms-and-ngmodel|Angular Custom Form Controls with Reactive Forms and NgModel]] {{ :lang:angular:projet:angular_custom_form_controls_with_reactive_forms_and_ngmodel_-_angular_12_11_2021-08-01_14_37_40_.html |Archive du 19/10/2016 le 01/08/2021}} {{ :lang:angular:projet:angular-szsw3k.zip |Archive du projet}} [[https://angular.io/guide/reactive-forms|Angular - Reactive forms]] {{ :lang:angular:projet:angular_-_reactive_forms_2021-08-01_14_50_54_.html |Archive du 12.1.5 le 01/08/2021}} {{ :lang:angular:projet:anovqbvjjgv.angular.zip |Archive du projet}} [[https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/|Angular Forms Guide - Template Driven and Reactive Forms]] {{ :lang:angular:projet:angular_forms_guide_template_driven_and_reactive_forms_2021-08-01_14_52_40_.html |Archive du 26/01/2021 le 01/08/2021}} [[https://angular.io/guide/form-validation|Validating form input]] {{ :lang:angular:projet:angular_-_validating_form_input_2021-08-01_14_54_43_.html |Archive du 12.1.5 le 01/08/2021}} [[https://indepth.dev/posts/1245/angular-nested-reactive-forms-using-controlvalueaccessors-cvas|Angular: Nested Reactive Forms Using ControlValueAccessors(CVAs)]] {{ :lang:angular:projet:angular_nested_reactive_forms_using_controlvalueaccessors_cvas_-_angular_indepth_2021-08-01_14_55_22_.html |Archive du 09/01/2019 le 01/08/2021}} {{ :lang:angular:projet:angular-nested-forms-cva.zip |Archive du projet}} [[https://indepth.dev/posts/1055/never-again-be-confused-when-implementing-controlvalueaccessor-in-angular-forms|Never again be confused when implementing ControlValueAccessor in Angular forms]] {{ :lang:angular:projet:never_again_be_confused_when_implementing_controlvalueaccessor_in_angular_forms_-_angular_indepth_2021-08-01_23_38_14_.html |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
L'interface sera pratique pour exploiter les données depuis la form parent. export interface FoundationStrip { width: number; } /* 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

Nested FormGroup

Form Values: {{ form.valid }} {{ form.value | json }}
/* 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; } 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=== [[https://angular.io/guide/inputs-outputs|Sharing data between child and parent directives and components]] {{ :lang:angular:projet:angular_-_sharing_data_between_child_and_parent_directives_and_components_2021-08-01_20_45_06_.html |Archive du 12.1.5 le 01/08/2021}} {{ :lang:angular:projet:xerqnqgmgdm.angular.zip |Archive du projet}} [[https://stackoverflow.com/questions/58883905/angular-8-send-form-data-from-child-output-to-parent-with-reactive-forms|Angular 8: Send Form Data from Child Output to Parent with Reactive Forms]] {{ :lang:angular:projet:typescript_-_angular_8_send_form_data_from_child_output_to_parent_with_reactive_forms_-_stack_overflow_2021-08-01_20_47_59_.html |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''. export class MeyerhofFormComponent implements OnInit { @Output() changeEvent = new EventEmitter(); 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); }); } } 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'' : import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [..., ReactiveFormsModule] }) export class ... [[https://stackoverflow.com/questions/39152071/cant-bind-to-formgroup-since-it-isnt-a-known-property-of-form/39152110|Can't bind to 'formGroup' since it isn't a known property of 'form']] {{ :lang:angular:projet:angular_-_can_t_bind_to_formgroup_since_it_isn_t_a_known_property_of_form_-_stack_overflow_2021-08-03_18_52_09_.html |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''. import { XXXComponent } from '...'; @NgModule({ declarations: [XXXComponent], exports: [XXXComponent] }) export class ... Source : [[https://github.com/angular/angular/blob/f0c5ba08f63c60f7542dfd3592c4cfd42bd579bc/packages/compiler-cli/src/ngtsc/scope/src/local.ts#L604|Produce a `ts.Diagnostic` for an exported directive or pipe which was not declared or imported by the NgModule in question.]] {{ :lang:angular:projet:angular_local.ts_at_f0c5ba08f63c60f7542dfd3592c4cfd42bd579bc_angular_angular_2021-08-03_19_10_25_.html |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''. export * from '...'; * ''TypeError: control.registerOnChange is not a function'' Dans le code HTML, les balises contenant ''formControlName'' doivent être encapsulées dans des balises ''
''. Par exemple:
[[https://stackoverflow.com/questions/41474011/what-causes-the-control-registeronchange-is-not-a-function-error|What causes the "control.registerOnChange is not a function" error]] {{ :lang:angular:projet:forms_-_what_causes_the_control.registeronchange_is_not_a_function_error_-_stack_overflow_2021-08-03_19_36_47_.html |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(); }); [[https://stackoverflow.com/questions/54976493/nullinjectorerror-no-provider-for-formbuilder-im-importing-reactiveformsmodul|Nullinjectorerror: no provider for formbuilder (I'm importing ReactiveFormsModule)]] {{ :lang:angular:projet:angular_-_nullinjectorerror_no_provider_for_formbuilder_i_m_importing_reactiveformsmodule_-_stack_overflow_2021-08-05_08_25_13_.html |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). [[https://stackoverflow.com/questions/46465891/what-is-ngdefaultcontrol-in-angular|What is ngDefaultControl in Angular?]] {{ :lang:angular:projet:javascript_-_what_is_ngdefaultcontrol_in_angular_-_stack_overflow_2021-08-06_10_40_06_.html |Archive du 28/09/2017 le 06/08/2021}} =====Compilation===== ====Options==== Les options sont dans les fichiers ''tsconfig.json'', dans le champ ''compilerOptions''. { "compilerOptions": { ... }, } [[https://www.typescriptlang.org/tsconfig|Intro to the TSConfig Reference]] {{ :lang:angular:projet:typescript_tsconfig_reference_-_docs_on_every_tsconfig_option_2021-10-09_18_50_50_.html |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' is not assignable to parameter of type 'OperatorFunction'. Types of parameters 'source' and 'source' are incompatible. Type 'import("G:/Github/jessica/src/angular/node_modules/rxjs/internal/Observable").Observable' is not assignable to type 'import("G:/Github/jessica/src/angular/projects/jessica/node_modules/rxjs/dist/types/internal/Observable").Observable'. The types of 'operator.call' are incompatible between these types. Type '(subscriber: import("G:/Github/jessica/src/angular/node_modules/rxjs/internal/Subscriber").Subscriber, 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, 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' is missing the following properties from type 'Subscriber': 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''. { "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" ]