Bonjour

Je suis le Tutoriel sur le développement full stack d'une application Web avec Angular 7 et Spring Boot 2 https://gkemayo.developpez.com/tutor...s-du-front-end et j'ai pu télécharger et tester les deux code source backend et frontend, et ça marche.

Maintenant j'ai téléchargé un template angular pour gérer le coté frontend et remplacer celui du code source, mais je rencontre des messages d'erreur lors de l'insertion des données dans la base, c'est-à-dire lorsque je clique sur le bouton save, j'ai ce message d'erreur:

CreerCompteComponent.html:5 ERROR TypeError: Cannot read property 'saveCompte' of undefined
at CreerCompteComponent.push../src/app/demo/pages/compte/creer-compte/creer-compte.component.ts.CreerCompteComponent.saveNewCompte (creer-compte.component.ts:78)
at CreerCompteComponent.push../src/app/demo/pages/compte/creer-compte/creer-compte.component.ts.CreerCompteComponent.save (creer-compte.component.ts:63)
at Object.eval [as handleEvent] (CreerCompteComponent.html:5)
at handleEvent (core.js:23009)
at callWithDebugContext (core.js:24079)
at Object.debugHandleEvent [as handleEvent] (core.js:23806)
at dispatchEvent (core.js:20458)
at core.js:21948
at SafeSubscriber.schedulerFn [as _next] (core.js:13516)
at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:196)
View_CreerCompteComponent_0 @ CreerCompteComponent.html:5
proxyClass @ compiler.js:18234
push../node_modules/@angular/core/fesm5/core.js.DebugContext_.logError @ core.js:24041
push../node_modules/@angular/core/fesm5/core.js.ErrorHandler.handleError @ core.js:15762
dispatchEvent @ core.js:20462
(anonymous) @ core.js:21948
schedulerFn @ core.js:13516
push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub @ Subscriber.js:196
push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next @ Subscriber.js:134
push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next @ Subscriber.js:77
push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next @ Subscriber.js:54
push../node_modules/rxjs/_esm5/internal/Subject.js.Subject.next @ Subject.js:47
push../node_modules/@angular/core/fesm5/core.js.EventEmitter.emit @ core.js:13488
push../node_modules/@angular/forms/fesm5/forms.js.NgForm.onSubmit @ forms.js:4433
eval @ CreerCompteComponent.html:5
handleEvent @ core.js:23009
callWithDebugContext @ core.js:24079
debugHandleEvent @ core.js:23806
dispatchEvent @ core.js:20458
(anonymous) @ core.js:20905
(anonymous) @ platform-browser.js:993
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask @ zone.js:423
onInvokeTask @ core.js:17280
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask @ zone.js:422
push../node_modules/zone.js/dist/zone.js.Zone.runTask @ zone.js:195
push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask @ zone.js:498
invokeTask @ zone.js:1744
globalZoneAwareCallback @ zone.js:1770
Voici le fichier creer-compte.component.html:

Code html : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="row">
  <div class="col-sm-12">
      <fieldset>
        <form #addCompteForm="ngForm" (ngSubmit)="save(addCompteForm)">
           <span><label for="First Name" class="compte-label">First Name:</label><input name="firstName" class="compte-input" type="text" required [(ngModel)]="compte.firstName"></span>
           <span><label for="Last Name" class="compte-label">Last Name:</label><input name="lastName" class="compte-input" type="text" required [(ngModel)]="compte.lastName"></span>
           <span><label for="Job" class="compte-label">Job:</label><input name="job" class="compte-input" type="text" required [(ngModel)]="compte.job"></span>
           <span><label for="Address" class="compte-label">Address:</label><input name="address" class="compte-input" type="text" required [(ngModel)]="compte.address"></span>
           <span><label for="Email" class="compte-label">Email:</label><input name="email" class="compte-input" type="text" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" required [(ngModel)]="compte.email"><input [value]="actionButton" class="compte-submit-button" type=submit [disabled]="!addCompteForm.form.valid"/></span>
        </form>
        <button class="clear-form-button" (click)="clearForm(addCompteForm)">Clear Form</button>
    </fieldset>
  </div>
</div>

Voici le fichier creer-compte.component.ts

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
 
import { NgForm } from "@angular/forms";
import { NgxSpinnerService } from 'ngx-spinner';
 
import { CompteService } from '../compte.service';
import { Compte } from '../compte';
 
@Component({
  selector: 'app-creer-compte',
  templateUrl: './creer-compte.component.html',
  styleUrls: ['./creer-compte.component.scss']
})
export class CreerCompteComponent implements OnInit {
 
 
   public types = [ 'Email', 'Last Name'];
    public email: string;
    public lastName: string;
    public displayType: string = 'Email'
    public headsTab = ['FIRST NAME', 'LAST NAME', 'EMAIL', 'JOB', 'ADDRESS'];
    public isNoResult: boolean = true;
    public isFormSubmitted: boolean = false;
    public actionButton: string = 'Save';
    public titleSaveOrUpdate: string = 'Add New Compte Form';
    public messageModal: string;
    public displayMessageModal: boolean = false;
   public compte = new Compte();
 
  constructor(
			  private compteService: CompteService, private router: Router
			 ) {	
  }
 
ngOnInit() {
  }
 
gotoMoncompte() {
    this.router.navigate(['/compte/mon-compte']);
  }
 
  gotoListe() {
    this.router.navigate(['/compte/liste-compte']);
  }
 
  save(addCompteForm: NgForm) {	
 // this.displayMessageModal = false;
    if (!addCompteForm.valid) {
		this.buildMessageModal('Error in the form');
    //  this.isSubmit = true;
     // return;
    }
    else
   { 
 
if(this.actionButton && this.actionButton === 'Save'){		   
		    this.saveNewCompte(this.compte);
			this.gotoListe(); 
      }      
    }
  }
 
  /**
  * Save new customer
  * @param customer
  */
  saveNewCompte(compte: Compte){
     // this.spinner.show();
      this.compteService.saveCompte(compte).subscribe(
              (result: Compte) => {
                 if(result.id){
					 this.gotoListe();
                     //this.spinner.hide();
                     //this.buildMessageModal('Save operation correctly done');
                 }
              },
              error => {
				   this.gotoMoncompte();
                 // this.spinner.hide();
                 // this.buildMessageModal('An error occurs when saving the customer data');
              }
      );
  }
 
  /**
   * Construit le message à afficher suite à une action utilisateur.
   * @param msg 
   */
    buildMessageModal(msg: string){
    this.messageModal = msg;
    this.displayMessageModal = true;
  }
 
 clearForm(addCompteForm: NgForm){
      addCompteForm.form.reset(); 
      this.displayMessageModal = false;
  }
 
}
Voici le fichier compte.service.ts:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Compte } from './compte';
 
@Injectable({
  providedIn: 'root'
})
export class CompteService {
 
  constructor(private http: HttpClient) { }
 
    /**
     * Save a new Compte object in the Backend server data base.
     * @param book
     */
     saveCompte(compte: Compte): Observable<Compte>{
         return this.http.post<Compte>('/library/rest/compte/api/addCustomer', compte);
     }
 
     /**
      * Update an existing Compte object in the Backend server data base.
      * @param compte
      */
      updateCompte(compte: Compte): Observable<Compte>{
          return this.http.put<Compte>('/library/rest/compte/api/updateCompte', compte);
      }
 
      /**
       * Delete an existing Compte object in the Backend server data base.
       * @param compte
       */
       deleteCompte(compte: Compte): Observable<string>{
           return this.http.delete<string>('/library/rest/compte/api/deleteCompte/'+compte.id);
       }
 
     /**
      * Search compte by email
      * @param email
      */
     searchCompteByEmail(email: string): Observable<Compte>{
         return  this.http.get<Compte>('/library/rest/compte/api/searchByEmail?email='+email);
     }
 
    /**
     * Search books by pagination
     * @param beginPage
     * @param endPage, 
     */
     searchCompteByLastName(lastName: string): Observable<Compte[]>{
             return this.http.get<Compte[]>('/library/rest/compte/api/searchByLastName?lastName='+lastName);
     }
}
Voici le fichier CompteRestController.java, coté backend:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package com.gkemayo.library.compte;
 
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
 
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
 
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
 
@RestController
@RequestMapping("/rest/compte/api")
@Api(value = "Compte Rest Controller: contains all operations for managing comptes")
public class CompteRestController {
 
	public static final Logger LOGGER = LoggerFactory.getLogger(CompteRestController.class);
 
	@Autowired
	private CompteServiceImpl compteService;
 
	@Autowired
	private JavaMailSender javaMailSender;
 
	/**
	 * Ajoute un nouveau client dans la base de donnée H2. Si le client existe déjà, on retourne un code indiquant que la création n'a pas abouti.
	 * @param compteDTORequest
	 * @return
	 */
	@PostMapping("/addCompte")
	@ApiOperation(value = "Add a new Compte in the Library", response = CompteDTO.class)
	@ApiResponses(value = { @ApiResponse(code = 409, message = "Conflict: the compte already exist"),
			@ApiResponse(code = 201, message = "Created: the compte is successfully inserted"),
			@ApiResponse(code = 304, message = "Not Modified: the compte is unsuccessfully inserted") })
	public ResponseEntity<CompteDTO> createNewCompte(@RequestBody CompteDTO compteDTORequest) {
		//, UriComponentsBuilder uriComponentBuilder
		Compte existingCompte = compteService.findCompteByEmail(compteDTORequest.getEmail());
		if (existingCompte != null) {
			return new ResponseEntity<CompteDTO>(HttpStatus.CONFLICT);
		}
		Compte compteRequest = mapCompteDTOToCompte(compteDTORequest);
		compteRequest.setCreationDate(LocalDate.now());
		Compte compteResponse = compteService.saveCompte(compteRequest);
		if (compteResponse != null) {
			CompteDTO compteDTO = mapCompteToCompteDTO(compteResponse);
			return new ResponseEntity<CompteDTO>(compteDTO, HttpStatus.CREATED);
		}
		return new ResponseEntity<CompteDTO>(HttpStatus.NOT_MODIFIED);
 
	}
 
	/**
	 * Met à jour les données d'un client dans la base de donnée H2. Si le client n'est pas retrouvé, on retourne un code indiquant que la mise à jour n'a pas abouti.
	 * @param compteDTORequest
	 * @return
	 */
	@PutMapping("/updateCompte")
	@ApiOperation(value = "Update/Modify an existing compte in the Library", response = CompteDTO.class)
	@ApiResponses(value = { @ApiResponse(code = 404, message = "Not Found : the compte does not exist"),
			@ApiResponse(code = 200, message = "Ok: the compte is successfully updated"),
			@ApiResponse(code = 304, message = "Not Modified: the compte is unsuccessfully updated") })
	public ResponseEntity<CompteDTO> updateCompte(@RequestBody CompteDTO compteDTORequest) {
		//, UriComponentsBuilder uriComponentBuilder
		if (!compteService.checkIfIdexists(compteDTORequest.getId())) {
			return new ResponseEntity<CompteDTO>(HttpStatus.NOT_FOUND);
		}
		Compte compteRequest = mapCompteDTOToCompte(compteDTORequest);
		Compte compteResponse = compteService.updateCompte(compteRequest);
		if (compteResponse != null) {
			CompteDTO compteDTO = mapCompteToCompteDTO(compteResponse);
			return new ResponseEntity<CompteDTO>(compteDTO, HttpStatus.OK);
		}
		return new ResponseEntity<CompteDTO>(HttpStatus.NOT_MODIFIED);
	}
 
	/**
	 * Supprime un client dans la base de donnée H2. Si le client n'est pas retrouvé, on retourne le Statut HTTP NO_CONTENT.
	 * @param compteId
	 * @return
	 */
	@DeleteMapping("/deleteCompte/{compteId}")
	@ApiOperation(value = "Delete a compte in the Library, if the compte does not exist, nothing is done", response = String.class)
	@ApiResponse(code = 204, message = "No Content: compte sucessfully deleted")
	public ResponseEntity<String> deleteCompte(@PathVariable Integer compteId) {
		compteService.deleteCompte(compteId);
		return new ResponseEntity<String>(HttpStatus.NO_CONTENT);
	}
 
	@GetMapping("/paginatedSearch")
	@ApiOperation(value="List comptes of the Library in a paginated way", response = List.class)
	@ApiResponses(value = {
			@ApiResponse(code = 200, message = "Ok: successfully listed"),
			@ApiResponse(code = 204, message = "No Content: no result founded"),
	})
	public ResponseEntity<List<CompteDTO>> searchComptes(@RequestParam("beginPage") int beginPage,
			@RequestParam("endPage") int endPage) {
		//, UriComponentsBuilder uriComponentBuilder
		Page<Compte> comptes = compteService.getPaginatedComptesList(beginPage, endPage);
		if (comptes != null) {
			List<CompteDTO> compteDTOs = comptes.stream().map(compte -> {
				return mapCompteToCompteDTO(compte);
			}).collect(Collectors.toList());
			return new ResponseEntity<List<CompteDTO>>(compteDTOs, HttpStatus.OK);
		}
		return new ResponseEntity<List<CompteDTO>>(HttpStatus.NO_CONTENT);
	}
 
	/**
	 * Retourne le client ayant l'adresse email passé en paramètre.
	 * @param email
	 * @return
	 */
	@GetMapping("/searchByEmail")
	@ApiOperation(value="Search a compte in the Library by its email", response = CompteDTO.class)
	@ApiResponses(value = {
			@ApiResponse(code = 200, message = "Ok: successfull research"),
			@ApiResponse(code = 204, message = "No Content: no result founded"),
	})
	public ResponseEntity<CompteDTO> searchCompteByEmail(@RequestParam("email") String email) {
		//, UriComponentsBuilder uriComponentBuilder
		Compte compte = compteService.findCompteByEmail(email);
		if (compte != null) {
			CompteDTO compteDTO = mapCompteToCompteDTO(compte);
			return new ResponseEntity<CompteDTO>(compteDTO, HttpStatus.OK);
		}
		return new ResponseEntity<CompteDTO>(HttpStatus.NO_CONTENT);
	}
 
	/**
	 * Retourne la liste des clients ayant le nom passé en paramètre.
	 * @param lastName
	 * @return
	 */
	@GetMapping("/searchByLastName")
	@ApiOperation(value="Search a compte in the Library by its Last name", response = List.class)
	@ApiResponses(value = {
			@ApiResponse(code = 200, message = "Ok: successfull research"),
			@ApiResponse(code = 204, message = "No Content: no result founded"),
	})
	public ResponseEntity<List<CompteDTO>> searchBookByLastName(@RequestParam("lastName") String lastName) {
		//,	UriComponentsBuilder uriComponentBuilder
		List<Compte> comptes = compteService.findCompteByLastName(lastName);
		if (comptes != null && !CollectionUtils.isEmpty(comptes)) {
			List<CompteDTO> compteDTOs = comptes.stream().map(compte -> {
				return mapCompteToCompteDTO(compte);
			}).collect(Collectors.toList());
			return new ResponseEntity<List<CompteDTO>>(compteDTOs, HttpStatus.OK);
		}
		return new ResponseEntity<List<CompteDTO>>(HttpStatus.NO_CONTENT);
	}
 
	/**
	 * Envoi un mail à un client. L'objet MailDTO contient l'identifiant et l'email du client concerné, l'objet du mail et le contenu du message.
	 * @param loanMailDto
	 * @param uriComponentBuilder
	 * @return
	 */
	@PutMapping("/sendEmailToCompte")
	@ApiOperation(value="Send an email to compte of the Library", response = String.class)
	@ApiResponses(value = {
			@ApiResponse(code = 200, message = "Ok: Email successfully sent"),
			@ApiResponse(code = 404, message = "Not Found: no compte found, or wrong email"),
			@ApiResponse(code = 403, message = "Forbidden: Email cannot be sent")
	})
	public ResponseEntity<Boolean> sendMailToCompte(@RequestBody MailDTO loanMailDto, UriComponentsBuilder uriComponentBuilder) {
 
		Compte compte = compteService.findCompteById(loanMailDto.getCompteId());
		if (compte == null) {
			String errorMessage = "The selected Compte for sending email is not found in the database";
			LOGGER.info(errorMessage);
			return new ResponseEntity<Boolean>(false, HttpStatus.NOT_FOUND);
		} else if (compte != null && StringUtils.isEmpty(compte.getEmail())) {
			String errorMessage = "No existing email for the selected Compte for sending email to";
			LOGGER.info(errorMessage);
			return new ResponseEntity<Boolean>(false, HttpStatus.NOT_FOUND);
		}
 
		SimpleMailMessage mail = new SimpleMailMessage();
		mail.setFrom(loanMailDto.MAIL_FROM);
		mail.setTo(compte.getEmail());
		mail.setSentDate(new Date());
		mail.setSubject(loanMailDto.getEmailSubject());
		mail.setText(loanMailDto.getEmailContent());
 
		try {
			javaMailSender.send(mail);
		} catch (MailException e) {
			return new ResponseEntity<Boolean>(false, HttpStatus.FORBIDDEN);
		}
 
		return new ResponseEntity<Boolean>(true, HttpStatus.OK);
	}
 
	/**
	 * Transforme un entity Compte en un POJO CompteDTO
	 * 
	 * @param compte
	 * @return
	 */
	private CompteDTO mapCompteToCompteDTO(Compte compte) {
		ModelMapper mapper = new ModelMapper();
		CompteDTO compteDTO = mapper.map(compte, CompteDTO.class);
		return compteDTO;
	}
 
	/**
	 * Transforme un POJO CompteDTO en en entity Compte
	 * 
	 * @param compteDTO
	 * @return
	 */
	private Compte mapCompteDTOToCompte(CompteDTO compteDTO) {
		ModelMapper mapper = new ModelMapper();
		Compte compte = mapper.map(compteDTO, Compte.class);
		return compte;
	}
 
}
Et voici le fichier proxy.conf.json:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
{
  "/library/": {
    "target": "http://localhost:8082",
    "secure": false,
    "changeOrigin": "true"
  }
}


Merci