« Angular » : différence entre les versions
(→Format) |
|||
(44 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 20 : | Ligne 20 : | ||
= [https://angular.io/guide/styleguide#style-04-06 Hierarchie des dossiers et fichiers] = | = [https://angular.io/guide/styleguide#style-04-06 Hierarchie des dossiers et fichiers] = | ||
src | {{boxx|src}} | ||
* main.ts | * {{boxx|main.ts}} is where the app starts running | ||
* app | * {{boxx|favicon.ico}} is the app's icon, just as you would find in any web site | ||
* {{boxx|index.html}} is the app's top level HTML template | |||
* {{boxx|style.css}} is the app's top level style sheet | |||
* {{boxx|app}} | |||
** {{boxx|app.component.ts}} app-root component, top-level Angular component in the app | |||
** {{boxx|app.component.html}} component HTML template | |||
** {{boxx|app.component.css}} component styles | |||
** shared | ** shared | ||
** app.module.ts | ** app.module.ts | ||
Ligne 34 : | Ligne 40 : | ||
*** component2 | *** component2 | ||
**** component2.component.* | **** component2.component.* | ||
* asset | * {{boxx|asset}} contains site images | ||
* environments | * environments | ||
= gitignore = | |||
<filebox fn='.gitignore' lang='bash' collapsed> | |||
# See http://help.github.com/ignore-files/ for more about ignoring files. | |||
# Compiled output | |||
/dist | |||
/tmp | |||
/out-tsc | |||
/bazel-out | |||
# Node | |||
/node_modules | |||
npm-debug.log | |||
yarn-error.log | |||
# IDEs and editors | |||
.idea/ | |||
.project | |||
.classpath | |||
.c9/ | |||
*.launch | |||
.settings/ | |||
*.sublime-workspace | |||
# Visual Studio Code | |||
.vscode/* | |||
!.vscode/settings.json | |||
!.vscode/tasks.json | |||
!.vscode/launch.json | |||
!.vscode/extensions.json | |||
.history/* | |||
# Miscellaneous | |||
/.angular/cache | |||
.sass-cache/ | |||
/connect.lock | |||
/coverage | |||
/libpeerconnection.log | |||
testem.log | |||
/typings | |||
# System files | |||
.DS_Store | |||
Thumbs.db | |||
</filebox> | |||
= Astuces = | = Astuces = | ||
Ligne 73 : | Ligne 125 : | ||
= Component = | = Component = | ||
A component represents a view. [https://angular.io/cli/generate generate] | |||
<kode lang='bash'> | <kode lang='bash'> | ||
ng generate component ' | # from src/app | ||
# CREATE src/app/ | ng generate component '[component-name]' | ||
# CREATE src/app/ | # CREATE src/app/[component-name]/[component-name].component.html | ||
# CREATE src/app/ | # CREATE src/app/[component-name]/[component-name].component.spec.ts | ||
# CREATE src/app/ | # CREATE src/app/[component-name]/[component-name].component.ts | ||
# CREATE src/app/[component-name]/[component-name].component.css | |||
# | # --dry-run | ||
# | # --inline-template use inline template instead of creating an html file | ||
# --inline-style use inline style instead of creating a css file | |||
# --skip-tests do not create "spec.ts" test files | |||
# - | # Error could not find an NgModule | ||
# --skip-import do not import this component into the owning NgModule | |||
</kode> | </kode> | ||
<filebox fn=' | * {{boxx|src/app}} | ||
** {{boxx|[component-name]}} | |||
*** {{boxx|[component-name].component.css}} | |||
*** {{boxx|[component-name].component.html}} | |||
*** {{boxx|[component-name].component.ts}} | |||
<filebox fn='src/app/[component-name]/[component-name].component.ts'> | |||
import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||
@Component({ | @Component({ | ||
// nom de la directive pour l’utilisation comme tag HTML | // nom de la directive pour l’utilisation comme tag HTML | ||
selector: ' | selector: '[component-name]', | ||
templateUrl: './ | templateUrl: './[component-name].component.html', | ||
styleUrls: ['./ | // inline template | ||
template: `<div>Hello World</div>`, | |||
styleUrls: ['./[component-name].component.css'], | |||
// inline style | |||
styles: `div { background: pink; }` | |||
}) | }) | ||
export class | export class ComponentName { | ||
// propriété bindée dans le template | // propriété bindée dans le template | ||
pageTitle: string = 'My Title'; | pageTitle: string = 'My Title'; | ||
Ligne 115 : | Ligne 175 : | ||
</filebox> | </filebox> | ||
<filebox fn=' | <filebox fn='src/app/[component-name]/[component-name].component.html'> | ||
<!-- si persons est null ou vide, table n'est pas affichée --> | <!-- si persons est null ou vide, table n'est pas affichée --> | ||
<table *ngIf='persons && persons.length'> | <table *ngIf='persons && persons.length'> | ||
Ligne 127 : | Ligne 187 : | ||
<body> | <body> | ||
<!-- utilisation de la directive my-new-component --> | <!-- utilisation de la directive my-new-component --> | ||
< | <[component-name]></[component-name]> | ||
</kode> | </kode> | ||
{{info | À la création d'un nouveau {{boxx|component}}, penser à le déclarer dans le {{boxx|module}}.}} | {{info | À la création d'un nouveau {{boxx|component}}, penser à le déclarer dans le {{boxx|module}}.}} | ||
= Module = | <filebox fn='src/app/app.module.ts'> | ||
import { ComponentName } from './[component-name]/[component-name].component'; | |||
@NgModule({ | |||
declarations: [ | |||
ComponentName | |||
] | |||
}) | |||
export class AppModule {} | |||
</filebox> | |||
= [https://angular.io/guide/feature-modules Feature Module] = | |||
Permet d'organiser les components par groupes. Un module par fonctionnalité. | Permet d'organiser les components par groupes. Un module par fonctionnalité. | ||
<kode lang='powershell'> | <kode lang='powershell'> | ||
ng generate module | # from src/app | ||
ng generate module [module-name] | |||
# --dry-run / -d | # --dry-run / -d | ||
# create a folder [module-name] and a file [module-name]/[module-name].module.ts | |||
</kode> | </kode> | ||
<filebox fn=' | <filebox fn='src/[module-name]/[module-name].module.ts'> | ||
import { | import { ComponentName } from './[component-name]/[component-name].component'; | ||
// | // un module pour la gestion des items | ||
@NgModule({ | |||
// declare the components used in this module | |||
declarations: [ ], | |||
// | // declare the components to be used outside of this module | ||
import { | exports: [ ComponentName ] | ||
}) | |||
export class ModuleName {} | |||
</filebox> | |||
Importing the feature module into the app module. | |||
<filebox fn='src/app/app.module.ts'> | |||
import { ModuleName } from '../[module-name]/[module-name].module'; | |||
@NgModule({ | @NgModule({ | ||
// déclare les modules externes qui seront mis à disposition des components du module | // déclare les modules externes qui seront mis à disposition des components du module | ||
imports: [ | imports: [ ModuleName ] | ||
}) | }) | ||
export class AppModule {} | export class AppModule {} | ||
</filebox> | </filebox> | ||
<filebox fn='src/app/app.component.html'> | |||
<filebox fn=' | <[component-name]></[component-name]> | ||
</filebox> | |||
// | = [https://angular.io/guide/inputs-outputs#sending-data-to-a-child-component Sending data to a child component] = | ||
<filebox fn='src/app/child.component.ts'> | |||
import { Component, Input } from '@angular/core'; // import Input | |||
export class ChildComponent { | |||
@Input() name = ''; // decorate the property with @Input() | |||
} | |||
</filebox> | |||
<filebox fn='src/app/parent.component.html'> | |||
<child [name]="childName"></child> <!-- use property bindingm --> | |||
</filebox> | |||
<filebox fn='src/app/parent.component.ts'> | |||
export class ParentComponent { | |||
childName = 'Child Name'; | |||
} | |||
export class | |||
</filebox> | </filebox> | ||
Ligne 314 : | Ligne 378 : | ||
</kode> | </kode> | ||
= [https://angular.io/api/common/ | = Format = | ||
== [https://angular.io/api/common/DecimalPipe Decimal] == | |||
<kode lang='html'> | <kode lang='html'> | ||
< | {{ 123.456 | number:'4.0-2':'en' }} <!-- 0,123.46 --> | ||
<!-- | {{ 123.456 | number:'4.0-2':'fr' }} <!-- 0 123,46 --> | ||
</kode> | |||
== [https://angular.io/api/common/PercentPipe Percentage] == | |||
<kode lang='html'> | |||
<!-- 5% --> | |||
{{ 0.05 | percent }} | |||
</kode> | </kode> | ||
= [https://angular.io/api/common/CurrencyPipe Currency | == [https://angular.io/api/common/CurrencyPipe Currency] == | ||
<kode lang='html'> | <kode lang='html'> | ||
<!-- 1000,00 € --> | <!-- 1000,00 € --> | ||
{{ | {{ 1000 | currency:'EUR':'symbol':'1.2-2'}} | ||
</kode> | |||
== [https://angular.io/api/common/DatePipe Date] == | |||
<kode lang='html'> | |||
<div>{{ today | date: 'EEEE dd MMMM yyyy' }}</div> | |||
<!-- Mardi 18 Septembre 2018 --> | |||
</kode> | </kode> | ||
Ligne 332 : | Ligne 409 : | ||
@NgModule({ | @NgModule({ | ||
imports: [ | imports: [ | ||
// import HttpClientModule after BrowserModule. | // import HttpClientModule after BrowserModule. | ||
HttpClientModule | BrowserModule, HttpClientModule | ||
], | ], | ||
</filebox> | </filebox> | ||
<filebox fn=' | <filebox fn='item/item-list/item-list.component.ts'> | ||
import { Component, OnInit } from '@angular/core'; | import { Component, OnInit } from '@angular/core'; | ||
import { HttpClient } from '@angular/common/http'; | import { HttpClient } from '@angular/common/http'; | ||
import { Item } from '../ | import { Item } from '../item/item.module'; | ||
export class | export class ItemListComponent implements OnInit { | ||
public items: Item[] = []; | public items: Item[] = []; | ||
url: string = 'http://localhost:5235'; // useful only if the service is hosted on another url | |||
constructor(private http: HttpClient) {} | constructor(private http: HttpClient) {} | ||
ngOnInit() | ngOnInit() { | ||
this.http.get<Item[]>( | |||
this.http.get<Item[]>(`${this.url}/item`).subscribe({ | |||
next: result => this.items = result, | |||
error: error => this.handleError(error) | |||
}); | |||
this.http.post<Item>('/api/items', newItemToAdd).subscribe(newItemFromServer => ...); | this.http.post<Item>('/api/items', newItemToAdd).subscribe(newItemFromServer => ...); | ||
this.http.delete('/api/items/42').subscribe(); | this.http.delete('/api/items/42').subscribe(); | ||
this.http.put<Item>('/api/items', updatedItem).subscribe(); | this.http.put<Item>('/api/items', updatedItem).subscribe(); | ||
} | } | ||
Ligne 373 : | Ligne 451 : | ||
</filebox> | </filebox> | ||
<filebox fn=' | <filebox fn='item/item.module.ts'> | ||
export interface Item { | export interface Item { | ||
id: number; | |||
name: string; | name: string; | ||
} | } | ||
</filebox> | </filebox> | ||
{{warn | Les objets récupérés sont de type {{boxx|Object}} et pas vraiment du type du cast.<br> | {{warn | Les objets récupérés sont de type {{boxx|Object}} et pas vraiment du type du cast.<br> | ||
Les attributs sont accessibles mais pas les méthodes. ([https://stackoverflow.com/questions/34031448/typescript-typeerror-myclass-myfunction-is-not-a-function lien])}} | Les attributs sont accessibles mais pas les méthodes. ([https://stackoverflow.com/questions/34031448/typescript-typeerror-myclass-myfunction-is-not-a-function lien])}} |
Dernière version du 22 janvier 2024 à 18:56
Liens
CLI
# build l'application angular ng build # --prod pour la production # build l'application angular et la rebuild uniquement ce qui est nécessaire à chaque modification de fichiers ng build --watch # lancer le service et ouvrir http://localhost:4200/ ng serve --open |
Hierarchie des dossiers et fichiers
src
- main.ts is where the app starts running
- favicon.ico is the app's icon, just as you would find in any web site
- index.html is the app's top level HTML template
- style.css is the app's top level style sheet
- app
- app.component.ts app-root component, top-level Angular component in the app
- app.component.html component HTML template
- app.component.css component styles
- shared
- app.module.ts
- app.component.*
- component1
- component1.component.*
- module2
- shared
- module2.module.ts
- module2-routing.module.ts
- component2
- component2.component.*
- asset contains site images
- environments
gitignore
.gitignore |
# See http://help.github.com/ignore-files/ for more about ignoring files. # Compiled output /dist /tmp /out-tsc /bazel-out # Node /node_modules npm-debug.log yarn-error.log # IDEs and editors .idea/ .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # Visual Studio Code .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .history/* # Miscellaneous /.angular/cache .sass-cache/ /connect.lock /coverage /libpeerconnection.log testem.log /typings # System files .DS_Store Thumbs.db |
Astuces
Titre de l'onglet et favicon
src/index.html |
<head> <title>MonTitre</title> <link rel="icon" type="image/x-icon" href="favicon.ico"> |
String et substitution de variable
let myVar = 'texte'; let otherVar = `Contenu de myVar: ${myVar}`; |
Object to string
console.log(JSON.stringify(myObject)); |
Timeout
setTimeout(() => { // code }, 1000); // delay en ms |
Propriété get
date: Date; get formattedDate() { return moment(this.date).format('MMMM YYYY'); } |
Component
A component represents a view. generate
# from src/app ng generate component '[component-name]' # CREATE src/app/[component-name]/[component-name].component.html # CREATE src/app/[component-name]/[component-name].component.spec.ts # CREATE src/app/[component-name]/[component-name].component.ts # CREATE src/app/[component-name]/[component-name].component.css # --dry-run # --inline-template use inline template instead of creating an html file # --inline-style use inline style instead of creating a css file # --skip-tests do not create "spec.ts" test files # Error could not find an NgModule # --skip-import do not import this component into the owning NgModule |
- src/app
- [component-name]
- [component-name].component.css
- [component-name].component.html
- [component-name].component.ts
- [component-name]
src/app/[component-name]/[component-name].component.ts |
import { Component } from '@angular/core'; @Component({ // nom de la directive pour l’utilisation comme tag HTML selector: '[component-name]', templateUrl: './[component-name].component.html', // inline template template: `<div>Hello World</div>`, styleUrls: ['./[component-name].component.css'], // inline style styles: `div { background: pink; }` }) export class ComponentName { // propriété bindée dans le template pageTitle: string = 'My Title'; persons: any[] = [ { "Name": "Billy" }, { "Name": "John" } ]; add() { alert("add"); } |
src/app/[component-name]/[component-name].component.html |
<!-- si persons est null ou vide, table n'est pas affichée --> <table *ngIf='persons && persons.length'> <tr *ngFor='let person of persons'> <td>{{ person.Name }}</td> <a (click)="addItem()"><i class="fa fa-plus-circle"></i></a> |
<body> <!-- utilisation de la directive my-new-component --> <[component-name]></[component-name]> |
À la création d'un nouveau component, penser à le déclarer dans le module. |
src/app/app.module.ts |
import { ComponentName } from './[component-name]/[component-name].component'; @NgModule({ declarations: [ ComponentName ] }) export class AppModule {} |
Feature Module
Permet d'organiser les components par groupes. Un module par fonctionnalité.
# from src/app ng generate module [module-name] # --dry-run / -d # create a folder [module-name] and a file [module-name]/[module-name].module.ts |
src/[module-name]/[module-name].module.ts |
import { ComponentName } from './[component-name]/[component-name].component'; // un module pour la gestion des items @NgModule({ // declare the components used in this module declarations: [ ], // declare the components to be used outside of this module exports: [ ComponentName ] }) export class ModuleName {} |
Importing the feature module into the app module.
src/app/app.module.ts |
import { ModuleName } from '../[module-name]/[module-name].module'; @NgModule({ // déclare les modules externes qui seront mis à disposition des components du module imports: [ ModuleName ] }) export class AppModule {} |
src/app/app.component.html |
<[component-name]></[component-name]> |
Sending data to a child component
src/app/child.component.ts |
import { Component, Input } from '@angular/core'; // import Input export class ChildComponent { @Input() name = ''; // decorate the property with @Input() } |
src/app/parent.component.html |
<child [name]="childName"></child> <!-- use property bindingm --> |
src/app/parent.component.ts |
export class ParentComponent { childName = 'Child Name'; } |
ngFor
<ul> <li *ngFor="let item of items; let i = index"> {{ item.name }} - {{ i }} </li> </ul> |
Filtre / Pipe
items.component.html |
<input type="text" [(ngModel)]="filterText" autofocus /> <!-- champs filtre avec bouton clear et un watermark --> <div class="btn-group"> <input type="search" placeholder="Filtre" [(ngModel)]="filterText" autofocus> <span id="filterclear" class="glyphicon glyphicon-remove-circle" (click)="clearFilter()"></span> </div> <ul> <li *ngFor="let item of items | itemsFilter: filterText"> {{ item.name }} </li> </ul> |
items.component.css |
#filterclear { position: absolute; right: 5px; top: 0; bottom: 0; height: 14px; margin: auto; font-size: 14px; cursor: pointer; } |
items.component.ts |
export class ItemsComponent { filterText: string = ""; clearFilter() { this.filterText = ""; } } |
items-filter.pipe.ts |
import { Pipe, PipeTransform } from '@angular/core'; import { Item } from 'ClientApp/app/shared/item'; @Pipe({ name: 'itemsFilter', pure: false }) export class ItemsFilterPipe implements PipeTransform { transform(items: Item[], filterText: string): Item[] { if (!items || !filterText) { return items; } filterText = filterText.toLowerCase(); // test si item.name contient filterText return items.filter(item => item.name.toLowerCase().indexOf(filterText) !== -1); } } |
app.module.ts |
/* erreur Can't bind to 'ngModel' since it isn't a known property of 'input' */ import { FormsModule } from '@angular/forms'; import { ItemsFilterPipe } from './components/items/items-filter.pipe'; @NgModule({ imports: [ FormsModule ], declarations: [ ItemsFilterPipe ], |
OrderBy - Sort
yarn add ngx-order-pipe # pour angular < 5 utiliser la version 1.1.3 yarn add ngx-order-pipe@1.1.3 |
<ul> <!-- orderBy: expression : reverse : caseInsensitive : comparator --> <li *ngFor="let item of items | orderBy: 'name' : true"> {{ item.name }} </li> </ul> |
app.module.ts |
import { OrderModule } from 'ngx-order-pipe'; @NgModule({ imports: [ OrderModule ], |
ngIf
<div *ngIf="user">{{ user.name }}</div> <div *ngIf="myObject.MyProperty === 10; else autreTexte">Texte</div> <ng-template #autreTexte> <div>Autre texte</div> </ng-template> |
Format
Decimal
{{ 123.456 | number:'4.0-2':'en' }} <!-- 0,123.46 --> {{ 123.456 | number:'4.0-2':'fr' }} <!-- 0 123,46 --> |
Percentage
<!-- 5% --> {{ 0.05 | percent }} |
Currency
<!-- 1000,00 € --> {{ 1000 | currency:'EUR':'symbol':'1.2-2'}} |
Date
<div>{{ today | date: 'EEEE dd MMMM yyyy' }}</div> <!-- Mardi 18 Septembre 2018 --> |
HttpClient
app/app.module.ts |
import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ // import HttpClientModule after BrowserModule. BrowserModule, HttpClientModule ], |
item/item-list/item-list.component.ts |
import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Item } from '../item/item.module'; export class ItemListComponent implements OnInit { public items: Item[] = []; url: string = 'http://localhost:5235'; // useful only if the service is hosted on another url constructor(private http: HttpClient) {} ngOnInit() { this.http.get<Item[]>(`${this.url}/item`).subscribe({ next: result => this.items = result, error: error => this.handleError(error) }); this.http.post<Item>('/api/items', newItemToAdd).subscribe(newItemFromServer => ...); this.http.delete('/api/items/42').subscribe(); this.http.put<Item>('/api/items', updatedItem).subscribe(); } private handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { // A client-side or network error occurred. Handle it accordingly. console.error('An error occurred:', error.error.message); } else { // The backend returned an unsuccessful response code. // The response body may contain clues as to what went wrong, console.error(`Backend returned code ${error.status}, body was: ${error.error}`); } } } |
item/item.module.ts |
export interface Item { id: number; name: string; } |
Les objets récupérés sont de type Object et pas vraiment du type du cast. Les attributs sont accessibles mais pas les méthodes. (lien) |
map
Projection d'un tableau dans un nouveau tableau.
import { map } from 'rxjs/operators'; // pour l'utilisation comme délégué let array1: number[] = [ 1, 2, 3 ]; let array2: string[] = array1.map((item, index, array) => { // item: T = 1 // index: number = 0 // array: T[] = array1 return item.toString(); }); // [ '1', '2', '3' ] const numToString = map((val: number) => val.toString()); let array2: string[] = numToString(array1); |
Bouton
item.component.html |
<button (click)="myAction()">My Action</button> |
item.component.ts |
myAction() { alert('action'); } |
ngClass et ngStyle
Affections conditionnelles de styles et classes CSS.
<div [ngStyle]="{'color':myVar > 0 ? 'green' : 'red' }" [ngClass]="{'myCssClass':myVar > 0}"> </<div> <div [style.color]="getColor(myVar)" [class.myCssClass]="myVar > 0"> </<div> |
Template reference
Choix d'un template en fonction d'une variable:
<ng-container [ngTemplateOutlet]='templateChoice === 1 ? template1 : template2' [ngTemplateOutletContext]="{arg1:arg1}"> </ng-container> <ng-template #template1 let-arg1="arg1"> </ng-template> |
Understanding Dynamic Scoping and TemplateRef
Tooltip
<div data-toggle="tooltip" data-placement="top" title="texte"></div> <!-- binding du tooltip avec myVar --> <div data-toggle="tooltip" data-placement="top" [title]="myVar"></div> |
Form
app/app.module.ts |
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ FormsModule ] }) export class AppModule { } |
Binding
myform.component.html |
<form (submit)="onSubmit()" #myClassForm="ngForm"> <div class="form-group"> <label for="field1">Item name</label> <input type="text" class="form-control" id="field1" name="field1" [(ngModel)]="model.field1" required /> <!-- [()] : 2-ways binding --> <select [(ngModel)]="model.choice"> <option [value]="choice" *ngFor="let choice of choices">{{choice}}</option> </select> <!-- checkbox, nécessite un name ou standalone --> <input type="checkbox" id="checkbox1" name="checkbox1" [(ngModel)]="model.checkbox1" [ngModelOptions]="{standalone: true}" (change)="onChange(model.checkbox1)"> </div> <button type="submit" class="btn btn-success">Submit</button> |
myform.component.ts |
model = { field1: "", choice: "un" }; choices = [ "un", "deux", "trois" ]; onSubmit() { } |
model undefined dans le template
Si l'initialisation de model se fait dans ngOnInit, model est undefined dans le constructeur ainsi que dans le template.
<!-- utiliser ?. pour éviter d'avoir une erreur --> {{ model?.name }} <!-- ?. ne peut être utiliser avec les binding 2-ways --> <input [(ngModel)]="model && model.name" /> <!-- utiliser ngIf --> <input *ngIf='model' [(ngModel)]="model.name" /> |
// autre solution: initialiser à la déclaration avec un objet vide model: Item = <Item>{}; |
Validation
myform.component.html |
<form (submit)="onSubmit()" #myform="ngForm" novalidate> <div class="form-group"> <input type="text" class="form-control" name="field1" [(ngModel)]="mydata.field1" #field1="ngModel" required /> <div class="text-danger" *ngIf="field1.touched && field1.invalid && field1.errors.required">Field1 is required</div> </div> <div class="form-group"> <input type="submit" class="btn btn-success" value="submit" [disabled]="myform.invalid" /> </div> |
Autofocus
autofocus ne fonctionne pas nativement avec Angular.
Pour Angular 4.4.7, utiliser angular-autofocus-fix
app/app.shared.module.ts |
import { AutofocusModule } from 'angular-autofocus-fix' @NgModule({ imports: [ AutofocusModule |
<input type="text" autofocus /> |
input date
La date ne s'affiche pas correctement dans le champs input.
<input type="date" [ngModel] ="myDate | date:'yyyy-MM-dd'" (ngModelChange)="myDate = $event"> |
FormArray
Événements
Clavier
<input type="text" (keydown.enter)="validate()"> |
Focus
<input type="text" (focus)="focusFunction()" (focusout)="validate()"> |
$event
<input type="text" (keydown.enter)="validate($event)"> |
validate(event: Event) { let sourceElement = event.srcElement; } |
Menu contextuel
ngx-contextmenu: npm, git, demo, custom css
<div [contextMenu]="myContextMenu" [contextMenuSubject]="myObject">Texte</div> <context-menu #myContextMenu> <ng-template contextMenuItem (execute)="myFunction($event.item)">Context Menu 1</ng-template> </context-menu> |
import { ViewEncapsulation } from '@angular/core'; @Component({ encapsulation: ViewEncapsulation.None // for custom css in context menu myFunction(myObject: MyClass) { /* */ } |
.dropdown-menu > li > a:hover { text-decoration: none; } .dropdown-menu > li > a { display: block; /* prend toute la largeur */ padding: 0 10px; color: white; } .dropdown-menu { min-width: 0; /* set to 10rem */ } |
yarn add ngx-contextmenu @angular/cdk # @angular/cdk@^6.0.0 |
app.module.ts |
import { ContextMenuModule } from 'ngx-contextmenu'; @NgModule({ imports: [ ContextMenuModule.forRoot() |
Langue
src/app/app.module.ts |
import { LOCALE_ID } from '@angular/core'; import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; @NgModule({ providers: [ {provide: LOCALE_ID, useValue: 'fr' } ] }) export class AppModule { constructor() { registerLocaleData(localeFr, 'fr'); } } |
ng-bootstrap
yarn add @ng-bootstrap/ng-bootstrap |
app.module.ts |
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ imports: [ NgbModule ], }) |
Tooltip
Bootstrap
Bootstrap 4
yarn add bootstrap jquery popper.js |
src/styles.css |
/* modifier ce fichier plutôt que angular.json pour être sur de l'ordre de concaténation du css */ @import url("~bootstrap/dist/css/bootstrap.min.css"); |
angular.json |
"styles": [ "src/styles.css", "node_modules/bootstrap/dist/css/bootstrap.min.css" ], "scripts": [ "node_modules/jquery/dist/jquery.min.js", "node_modules/bootstrap/dist/js/bootstrap.min.js" ] |
Bootstrap 3
yarn add bootstrap3 |
angular.json |
"styles": [ "src/styles.css", "node_modules/bootstrap3/dist/css/bootstrap.min.css" ], |
navmenu.component.html |
<div class='main-nav'> <div class='navbar navbar-inverse'> <div class='navbar-header'> <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'> <span class='sr-only'>Toggle navigation</span> <span class='icon-bar'></span> <span class='icon-bar'></span> <span class='icon-bar'></span> </button> <a class='navbar-brand' [routerLink]="['/home']">Home</a> </div> <div class='clearfix'></div> <div class='navbar-collapse collapse'> <ul class='nav navbar-nav'> <li [routerLinkActive]="['link-active']"> <a [routerLink]="['/home']"> <span class='glyphicon glyphicon-home'></span> Home </a> </li> <li [routerLinkActive]="['link-active']"> <a [routerLink]="['/menu1']"> <span class='glyphicon glyphicon-picture'></span> Menu1 </a> </li> </ul> </div> </div> </div> |
navmenu.component.css |
li .glyphicon { margin-right: 10px; } /* Highlighting rules for nav menu items */ li.link-active a, li.link-active a:hover, li.link-active a:focus { background-color: #4189C7; color: white; } /* Keep the nav menu independent of scrolling and on top of other items */ .main-nav { position: fixed; top: 0; left: 0; right: 0; z-index: 1; } @media (min-width: 768px) { /* On small screens, convert the nav menu to a vertical sidebar */ .main-nav { height: 100%; width: calc(16.66666667% - 20px); } .navbar { border-radius: 0px; border-width: 0px; height: 100%; } .navbar-header { float: none; } .navbar-collapse { border-top: 1px solid #444; padding: 0px; } .navbar ul { float: none; } .navbar li { float: none; font-size: 15px; margin: 6px; } .navbar li a { padding: 10px 16px; border-radius: 4px; } .navbar a { /* If a menu item's text is too long, truncate it */ width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } } |
Font Awesome
Font Awesome 5
Font Awesome 4
yarn add font-awesome |
src/styles.css |
/* modifier ce fichier plutôt que angular.json pour être sur de l'ordre de concaténation du css */ @import url("~font-awesome/css/font-awesome.css"); |
angular.json |
"styles": [ "src/styles.css", "node_modules/font-awesome/css/font-awesome.min.css" ], |
<i class="fa fa-edit"></i> |
Global style
angular.json |
{ "projects": { "ClientApp": { "architect": { "build": { "options": { "styles": [ "src/styles.css", "src/other-styles.css" ], |
La modification du fichier angular.json nécessite de recompiler le projet à la main. |
Tous les styles sont concaténés au sein d'un seul fichier lors du build. L'ordre des fichiers listés dans style n'est pas respecté lors de la concaténation (bug). |
src/styles.css |
@import url("src/other-styles.css"); |
Dark style
src/styles.css |
html, body { height: 100%; } |
src/app/app.component.css |
/* dark theme */ .container-fluid { background-color: #1f1f1f; color: #cccccc; min-height: 100%; /* take the full height */ } |
Animation
app.module.ts |
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @NgModule({ imports: [ BrowserAnimationsModule ], |
test/test.component.ts |
import {style, state, animate, transition, trigger} from '@angular/animations'; @Component({ animations: [ trigger('fadeInOut', [ transition(':enter', [ // :enter is alias to 'void => *' style({ opacity: 0 }), animate(500, style({ opacity: 1 })) ]), transition(':leave', [ // :leave is alias to '* => void' animate(500, style({ opacity: 0 })) ]) ]) ] }) |
test/test.component.html |
<div [@fadeInOut]> |
Accèder aux éléments du DOM
let element = document.getElementById('myId'); (element as HTMLInputElement).value = "My Value"; document.querySelector(':focus').blur(); |
Versions
Version | Date | Commentaire |
---|---|---|
Angular 7 | Octobre 2018 | |
Angular 6 | Mars 2018 | |
Angular 5 | Octobre 2017 | |
Angular 4 | Mars 2017 | Pas de version 3. |
Angular 2 | Septembre 2016 | TypeScript. Réécriture complète d'AngularJS. |
AngularJS 1 | Octobre 2010 | Javascript |
Langages
Langage | Description |
---|---|
EcmaScript 5 | Sortie Décembre 2009. |
EcmaScript 2015 / 6 | Orienté objet. Sortie Juin 2015. fonctionnalités. |
TypeScript | Superset de JS. Stong types. Orienté objet. |
Dart |
AngularJS vs Angular 2+
AngularJS
- Javascript
- Design basé sur MVC
Angular 2+
- Typescript
- Design basé sur Service/Controller
moment.js
yarn add moment ngx-moment |
app.module.ts |
import { MomentModule } from 'ngx-moment'; @NgModule({ imports: [ MomentModule |
test/test.component.html |
{{myDate | amDateFormat:'LL'}} |
test/test.component.ts |
import * as moment from 'moment'; const formattedDate = moment(myDate).format('LL'); |
jquery / jquery-ui
yarn add jquery jquery-ui-dist |
angular.json |
"scripts": [ "node_modules/jquery/dist/jquery.min.js", "node_modules/jquery-ui-dist/jquery-ui.min.js" ] |
test/test.component.ts |
import * as $ from 'jquery'; // pour jquery seulement declare var $: any; // pour jquery et jquery-ui, à placer après les imports avant la classe ngOnInit() { $(document).ready(function() { // pas forcément nécessaire $('#id').MonthPicker({ Button: false }); }); |
angular-modal-gallery
Galerie d'images pour Angular 4,5,6.
# installation yarn add @ks89/angular-modal-gallery hammerjs mousetrap yarn add @types/hammerjs @types/mousetrap # pour angular 6 yarn add rxjs-compat |
app.module.ts |
import 'hammerjs'; import 'mousetrap'; import { ModalGalleryModule } from '@ks89/angular-modal-gallery'; @NgModule({ imports: [ ModalGalleryModule.forRoot() |
*.component.ts |
import { AccessibilityConfig, Action, AdvancedLayout, ButtonEvent, ButtonsConfig, ButtonsStrategy, ButtonType, Description, DescriptionStrategy, DotsConfig, GalleryService, GridLayout, Image, ImageModalEvent, LineLayout, PlainGalleryConfig, PlainGalleryStrategy, PreviewConfig } from '@ks89/angular-modal-gallery'; images: Image[] = [ new Image(0, { img: '../assets/images/1.png' }), new Image(1, { img: '../assets/images/2.png' }) ]; |
*.component.html |
<ks-modal-gallery [id]="1" [modalImages]="images"></ks-modal-gallery> <ks-modal-gallery *ngFor="let album of albums; let i = index" [id]="i" [modalImages]="album.images" </ks-modal-gallery> |
close outside | [enableCloseOutside]="false" |
infinite sliding but NO side previews | [slideConfig]="{infinite: true, sidePreviews: {show: false}}" |
bouton open / ouverture automatique
import { GalleryService } from '@ks89/angular-modal-gallery'; constructor(private galleryService: GalleryService) {} openGallery() { this.galleryService.openGallery(1, 0); // id-gallery, img } // ouvrir la galerie au chargement de la page ngOnInit() { this.delay(1).then(() => { this.galleryService.openGallery(1, 0); } ); } delay(ms: number) { return new Promise( resolve => setTimeout(resolve, ms) ); } // avec await async ngOnInit() { await this.delay(1); // on attend 1ms avant d’exécuter la suite this.galleryService.openGallery(1, 0); } |
Afficher une seule image
<ks-modal-gallery [id]="1" [modalImages]="images" [plainGalleryConfig]="plainGalleryRow"></ks-modal-gallery> |
plainGalleryRow: PlainGalleryConfig = { strategy: PlainGalleryStrategy.ROW, layout: new LineLayout({ width: '80px', height: 'auto' }, { length: 1, wrap: true }, 'flex-start') }; |
bouton delete
<ks-modal-gallery [buttonsConfig]="customButtonsConfig" (buttonBeforeHook)="onButtonBeforeHook($event)"></ks-modal-gallery> |
customButtonsConfig: ButtonsConfig = { visible: true, strategy: ButtonsStrategy.CUSTOM, // les modèles des boutons par défaut se trouvent dans libs/angular-modal-gallery/src/components/upper-buttons buttons: [ { className: 'delete-image', type: ButtonType.DELETE, ariaLabel: 'Delete the current image', title: 'Delete the current image', fontSize: '20px' }, { className: 'close-image', type: ButtonType.CLOSE, ariaLabel: 'Close this modal image gallery', title: 'Close this modal image gallery', fontSize: '20px' } ] }; // possibilité de passer le tableau d'images en argument onButtonBeforeHook(event: ButtonEvent) { console.log('onButtonBeforeHook ', event); if (!event || !event.button) { return; } // Invoked after a click on a button, but before that the related // action is applied. // For instance: this method will be invoked after a click // of 'close' button, but before that the modal gallery // will be really closed. if (event.button.type === ButtonType.DELETE) { console.log('image to delete: id ' + event.image.id + 'path ' + event.image.modal.img); // path = /images/<photoFileName>.jpg // delete the image file this.http.delete('/api' + event.image.modal.img).subscribe(); // remove the current image from the array of images and reassign it this.images = this.images.filter((img: Image) => event.image && img.id !== event.image.id); } } |
Preview Config (liste des images en bas)
<!-- afficher 5 images --> <ks-modal-gallery [id]="1" [modalImages]="images" [previewConfig]="{ number: 5 }"> </ks-modal-gallery> |
Valeur par défaut: { visible: true, number: 3, arrows: true, clickable: true, size: { height: '50px', width: 'auto' }}
ngx-chart
yarn add @swimlane/ngx-charts |
app.module.ts |
import { NgxChartsModule } from '@swimlane/ngx-charts'; imports: [ NgxChartsModule ] |
test/test.component.html |
<ngx-charts-bar-vertical-2d [results]="barVertical2dData" xAxis="true" yAxis="true"> </ngx-charts-bar-vertical-2d> <ngx-charts-line-chart [results]="lineChartData" xAxis="true" yAxis="true" showGridLines roundDomains="true" [xAxisTickFormatting]="xAxisTickFormatting"> </ngx-charts-line-chart> |
test/test.component.ts |
import { Component, OnInit, ViewEncapsulation } from '@angular/core'; @Component({ encapsulation: ViewEncapsulation.None }) barVertical2dData = [ { 'name': 'Groupe1', 'series': [ { 'name': 'Texte1', 'value': 100 }, { 'name': 'Texte2', 'value': 200 } ] }, { 'name': 'Groupe2', 'series': [ { 'name': 'Texte1', 'value': 300 }, { 'name': 'Texte2', 'value': 400 } ] }, ]; lineChartData = [{ name: 'line1', series: [ { 'name': new Date(2018, 7, 1, 0, 0, 0, 0), 'value': 23.56 }, { 'name': new Date(2018, 8, 1, 0, 0, 0, 0), 'value': 57.45 }, { 'name': new Date(2018, 9, 1, 0, 0, 0, 0), 'value': 199.25 }, { 'name': new Date(2018, 10, 1, 0, 0, 0, 0), 'value': 154.32 } ] }]; xAxisTickFormatting(value: Date) { if (value.getDate() < 9) { return moment(value).format('MMM YYYY'); } else { return ''; } } |
Debug
Firefox
Firefox → Inspect Element → Debugger → Webpack → src → app → *.ts
Ajouter des point d'arrêts
Visual Studio Code
- Installer Debugger for Firefox
- Ajouter un nouvel élément de lancement
.vscode\launch.json |
{ "name": "Debug Angular", "type": "firefox", "request": "launch", "reAttach": true, "url": "http://localhost:4200", "webRoot": "${workspaceFolder}" } |
- Lancer le service Angular :
ng serve
- Lancer le debugage depuis VS code: Debug → Debug Angular → Run
Publication / Déploiement / Publish
ng build --prod # créé le dossier dist/myproject |
/etc/nginx/nginx.conf |
try_files $uri $uri/ /index.html; |
Variables d'environnement
src/environment.ts |
export const environment = { production: false, myvar: 'dev' }; |
src/environment.prod.ts |
export const environment = { production: true, myvar: 'prod' }; |
src/app/app.component.ts |
import { environment } from '../environments/environment'; environment.myvar; |
# configuration dans angular.json → configurations ng serve -c production ng build --prod |
Installation
# installer angular cli global npm install -g @angular/cli # global avec yarn yarn global add @angular/cli # local avec yarn yarn add @angular/cli # créer un projet angular dans le dossier MyAngularApp ng new MyAngularApp # --dry-run # --minimal : moins de fichiers # lancer le service et l'ouvrir dans un navigateur cd MyAngularApp ng serve --open # build ng build # --watch recompile ce qu'il faut lors de modifications |
Windows
# check if node.js is installed node --version # install node.js LTS winget install OpenJS.NodeJS.LTS |
Erreurs
Can't resolve 'rxjs/add/operator/map'
Vérifier que rxjs et rxjs-compat sont installés.
Got interpolation ({{}}) where expression was expected
Ne pas utiliser {{ }} dans un événement (click)
<a (click)="delete({{ item.id }})"><i class="fa fa-trash"></i></a> <a (click)="delete(item.id)"><i class="fa fa-trash"></i></a> |
UI pas mise à jour après la modification d'une valeur bindée
import { NgZone } from '@angular/core'; this.http.get<Class[]>(`/api/class`).subscribe(data => { this.ngZone.run(() => { this.bindedValue = data; }); }); |