diff --git a/apps/showcase/src/assets/trainings/sdk/program.json b/apps/showcase/src/assets/trainings/sdk/program.json index beb897b52f..cca4767796 100644 --- a/apps/showcase/src/assets/trainings/sdk/program.json +++ b/apps/showcase/src/assets/trainings/sdk/program.json @@ -84,21 +84,34 @@ "htmlContentUrl": "./steps/angular-integration/instructions.md", "filesConfiguration": { "name": "angular-integration", - "startingFile": "apps/tutorial-app/src/app/app.component.ts", + "startingFile": "apps/tutorial-app/src/app/app.config.ts", "urls": [ { "path": ".", "contentUrl": "./shared/monorepo-template.json" }, + { + "path": "./libs/sdk", + "contentUrl": "@o3r-training/showcase-sdk/structure/spec.json" + }, { "path": "./libs/sdk/src", - "contentUrl": "@o3r-training/training-sdk/structure/src.json" + "contentUrl": "@o3r-training/showcase-sdk/structure/src.json" + }, + { + "path": "./apps/tutorial-app/src/app", + "contentUrl": "./steps/angular-integration/exercise.json" + } + ], + "solutionUrls": [ + { + "path": "./apps/tutorial-app/src/app", + "contentUrl": "./steps/angular-integration/solution.json" } ], "mode": "interactive", "commands": [ "npm install --legacy-peer-deps --ignore-scripts --force", - "npm run ng run sdk:build", "npm run ng run tutorial-app:serve" ] } diff --git a/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.html b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.html new file mode 100644 index 0000000000..84a3a3fab3 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.html @@ -0,0 +1,25 @@ +
+ + +
+ + + + + + + + + + + @for (pet of pets(); track pet.id; let index = $index) { + + + + + + } + +
+ Pets with the status available from the Swagger Petstore +
#NameStatus
{{ index + 1 }}{{ pet.name }}{{ pet.status }}
diff --git a/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.ts b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.ts new file mode 100644 index 0000000000..34885cd76a --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.component.ts @@ -0,0 +1,33 @@ +import { Component, inject, signal } from '@angular/core'; +import { ApiFactoryService } from '@o3r/apis-manager'; +import { type Pet, PetApi, StoreApi } from 'sdk'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' +}) +export class AppComponent { + /** Title of the application */ + public title = 'tutorial-app'; + + /* Inject the ApiFactoryService and get the corresponding APIs */ + private petApi; + private storeApi; + + private readonly petsWritable = signal([]); + public readonly pets = this.petsWritable.asReadonly(); + + private readonly petsInventoryWritable = signal<{ [key: string]: number }>({}); + public readonly petsInventory = this.petsInventoryWritable.asReadonly(); + + public async getAvailablePets() { + /* Get the pets whose status is 'available' */ + } + + public async getPetInventory() { + /* Get the pets inventory */ + } +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.config.ts b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.config.ts new file mode 100644 index 0000000000..8c5a84fd88 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/exercise/app.config.ts @@ -0,0 +1,33 @@ +import { ApiFetchClient } from '@ama-sdk/client-fetch'; +import { ApiClient, PluginRunner, RequestOptions, RequestPlugin } from '@ama-sdk/core'; +import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { ApiManager, ApiManagerModule } from '@o3r/apis-manager'; +import { routes } from './app.routes'; + +class RequestLogPlugin implements RequestPlugin { + public load(): PluginRunner { + return { + transform: (data: RequestOptions) => { + alert(JSON.stringify(data)); + return data; + } + }; + } +} + +// Default configuration for all the APIs defined in the ApiManager +const apiConfig: ApiClient = new ApiFetchClient( + // Properties of ApiFetchClient +); +const apiManager = new ApiManager(apiConfig, { + // Configuration override for a specific API +}); + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + importProvidersFrom(ApiManagerModule.forRoot(apiManager)) + ] +}; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/instructions.md b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/instructions.md new file mode 100644 index 0000000000..a9136b70ea --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/instructions.md @@ -0,0 +1,40 @@ +When dealing with an Angular project, you need to ensure that your `ApiClient` will be shared accross +your application. The Otter framework provides the `ApiManager` service to manage your API collection. + +### Objective +- Leverage the `ApiManager` service to access two different clients to retrieve the list of available pets and submit an order for the first pet returned. +- Add a plugin to the `OrderApi` to log each time a call is sent. + +### Prerequisite +- The package `@o3r/apis-manager` needs to be installed in the project with `npm install @o3r/apis-manager` (which has already been done for you). + +### Exercise +As you can see in the `app.config.ts` file, a plugin `RequestLogPlugin` has been created which displays an alert box when the API receives a request. + +Integrate the `ApiManagerModule` in your `ApplicationConfig` and configure it to use the `RequestLogPlugin` in the `StoreApi`. +You can inspire yourself with the following lines: + +```typescript +// Default configuration for all the APIs defined in the ApiManager +const apiConfig: ApiClient = new ApiFetchClient( + { + // Properties of ApiFetchClient + } +); +const apiManager = new ApiManager(apiConfig, { + // Configuration override for a specific API + StoreApi: new ApiFetchClient({ + // Properties of ApiFetchClient + }) +}); + +export const appConfig: ApplicationConfig = { + providers: [importProvidersFrom(ApiManagerModule.forRoot(apiManager))] +}; +``` + +Now, checkout the `app.component.ts` file and inject the `ApiFactoryService` to use your unique instance of the `StoreApi` and `PetApi`. +In the existing functions, update the `pets` signal with the result of a call to `findPetsByStatus` and the `petsInventory` signal with the the result of `getInventory`. + +Now, when clicking the **Get Available Pets** button, your table should be updated with the list of available pets (without the alert box). +When clicking the **Get Inventory** button, you should see the request to the `StoreApi` logged in the alert box. diff --git a/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.component.ts b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.component.ts new file mode 100644 index 0000000000..b8ef620814 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.component.ts @@ -0,0 +1,37 @@ +import { Component, inject, signal } from '@angular/core'; +import { ApiFactoryService } from '@o3r/apis-manager'; +import { type Pet, PetApi, StoreApi } from 'sdk'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' +}) +export class AppComponent { + /** Title of the application */ + public title = 'tutorial-app'; + + /* Inject the ApiFactoryService and get the corresponding APIs */ + private petApi = inject(ApiFactoryService).getApi(PetApi); + private storeApi = inject(ApiFactoryService).getApi(StoreApi); + + private readonly petsWritable = signal([]); + public readonly pets = this.petsWritable.asReadonly(); + + private readonly petsInventoryWritable = signal<{ [key: string]: number }>({}); + public readonly petsInventory = this.petsInventoryWritable.asReadonly(); + + public async getAvailablePets() { + /* Get the pets whose status is 'available' */ + const availablePets = await this.petApi.findPetsByStatus({status: 'available'}); + this.petsWritable.set(availablePets); + } + + public async getPetInventory() { + /* Get the pets inventory */ + const inventory = await this.storeApi.getInventory({}); + this.petsInventoryWritable.set(inventory); + } +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.config.ts b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.config.ts new file mode 100644 index 0000000000..d10d5c3f99 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/angular-integration/solution/app.config.ts @@ -0,0 +1,42 @@ +import { ApiFetchClient } from '@ama-sdk/client-fetch'; +import { ApiClient, PluginRunner, RequestOptions, RequestPlugin } from '@ama-sdk/core'; +import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { ApiManager, ApiManagerModule } from '@o3r/apis-manager'; +import { routes } from './app.routes'; + +class RequestLogPlugin implements RequestPlugin { + public load(): PluginRunner { + return { + transform: (data: RequestOptions) => { + alert(JSON.stringify(data)); + return data; + } + }; + } +} + +// Default configuration for all the APIs defined in the ApiManager +const apiConfig: ApiClient = new ApiFetchClient( + { + basePath: 'https://petstore3.swagger.io/api/v3', + requestPlugins: [], + fetchPlugins: [] + } +); +const apiManager = new ApiManager(apiConfig, { + // Configuration override for a specific API + StoreApi: new ApiFetchClient({ + basePath: 'https://petstore3.swagger.io/api/v3', + requestPlugins: [new RequestLogPlugin()], + fetchPlugins: [] + }) +}); + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + importProvidersFrom(ApiManagerModule.forRoot(apiManager)) + ] +};