Skip to content

Commit c0b0acd

Browse files
authored
chore(examples): Update Angular example to use next version API (dotCMS#32160)
This pull request introduces several updates to improve type safety, modernize API usage, and enhance Angular components across the application. Key changes include the adoption of more specific TypeScript interfaces, the replacement of deprecated methods with updated APIs, and the refactoring of Angular components to use modern features like `computed` properties and required inputs. ### TypeScript Improvements: * Updated the `#getPageFromGraphQL` method in `PageClient` to support generic types, improving flexibility and type safety (`core-web/libs/sdk/client/src/lib/client/page/page-api.ts`). [[1]](diffhunk://#diff-c78881047cc240f2b5bf982d6628d362d8d6809baef85ffadaf8f270d8aebc7dL119-R119) [[2]](diffhunk://#diff-c78881047cc240f2b5bf982d6628d362d8d6809baef85ffadaf8f270d8aebc7dL129-R134) ### Angular Client Updates: * Replaced `DotCmsClient.init` with `createDotCMSClient` in `app.config.ts` to use the latest client initialization API. Updated the client configuration type to `DotCMSClientConfig` for better compatibility (`examples/angular/src/app/app.config.ts`). * Added `DotCMSEditablePageService` to the application providers to enable editable page functionality (`examples/angular/src/app/app.config.ts`). ### Component Refactoring: * Refactored multiple Angular components (`ActivityComponent`, `BannerComponent`, `ImageComponent`, `ProductComponent`, `WebPageContentComponent`) to use specific contentlet interfaces instead of the generic `DotCMSContentlet`. This improves type safety and clarity (`examples/angular/src/app/content-types`). [[1]](diffhunk://#diff-63b433b9d03e786471e7d6108e15f86c5b2a190c018001c7dee7ac281d5195a2L4-R44) [[2]](diffhunk://#diff-c28b5e34652734bbffe79d19692542393955e512f424fbd91f325997b80d4782L4-R49) [[3]](diffhunk://#diff-481074a9594708861b3b3c1c4c2ea89f2a5619542b7d1c4d8758a2190364f765L3-R33) [[4]](diffhunk://#diff-cd1f41fdf8bc1ca6879d6074add62d242d22f269a4e36920376626d69fd2b1eaL2-R33) [[5]](diffhunk://#diff-c3e544da8f3e2fd565610871a05740024af320673950dc09095accc0da3f7fe0L3-R15) * Replaced deprecated property access patterns (e.g., `contentlet()['fieldName']`) with modern, type-safe accessors (e.g., `contentlet().fieldName`) across components. [[1]](diffhunk://#diff-63b433b9d03e786471e7d6108e15f86c5b2a190c018001c7dee7ac281d5195a2L4-R44) [[2]](diffhunk://#diff-c28b5e34652734bbffe79d19692542393955e512f424fbd91f325997b80d4782L4-R49) [[3]](diffhunk://#diff-481074a9594708861b3b3c1c4c2ea89f2a5619542b7d1c4d8758a2190364f765L3-R33) [[4]](diffhunk://#diff-cd1f41fdf8bc1ca6879d6074add62d242d22f269a4e36920376626d69fd2b1eaL2-R33) [[5]](diffhunk://#diff-c3e544da8f3e2fd565610871a05740024af320673950dc09095accc0da3f7fe0L3-R15) ### Blog Page Enhancements: * Updated `BlogPostComponent` to use a `computed` property for parsing `blogContent` dynamically, improving performance and readability (`examples/angular/src/app/pages/blog/blog-post/blog-post.component.ts`). [[1]](diffhunk://#diff-dfb28b2843662915c9de0d47636c9200186e00b6a74bd3121d3f4eb4e9eaa44cL1-R5) [[2]](diffhunk://#diff-dfb28b2843662915c9de0d47636c9200186e00b6a74bd3121d3f4eb4e9eaa44cL14-R28) * Refactored `blog.component.html` to align with the updated API response structure, ensuring compatibility with the new `pageResponse` format (`examples/angular/src/app/pages/blog/blog.component.html`). [[1]](diffhunk://#diff-1fef797720bbe8494bbbacaa14d7fe1ba9262cc42f401b9a55e59509d43f9c16L3-R3) [[2]](diffhunk://#diff-1fef797720bbe8494bbbacaa14d7fe1ba9262cc42f401b9a55e59509d43f9c16L16-L29) These changes collectively improve the maintainability, performance, and type safety of the codebase while aligning with modern Angular and TypeScript best practices.
1 parent 03d7f30 commit c0b0acd

File tree

63 files changed

+1381
-1304
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1381
-1304
lines changed

core-web/libs/sdk/client/src/lib/client/page/page-api.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class PageClient {
116116
url: string,
117117
options?: DotCMSPageRequestParams
118118
): Promise<DotCMSComposedPageResponse<T>> {
119-
return this.#getPageFromGraphQL(url, options) as Promise<DotCMSComposedPageResponse<T>>;
119+
return this.#getPageFromGraphQL(url, options);
120120
}
121121

122122
/**
@@ -126,12 +126,12 @@ export class PageClient {
126126
* @private
127127
* @param {string} url - The URL of the page to retrieve
128128
* @param {DotCMSPageRequestParams} [options] - Options including languageId, mode, and GraphQL parameters
129-
* @returns {Promise<DotCMSPageResponse>} A Promise that resolves to the page data
129+
* @returns {Promise<DotCMSComposedPageResponse<T>>} A Promise that resolves to the page data
130130
*/
131-
async #getPageFromGraphQL(
131+
async #getPageFromGraphQL<T extends DotCMSExtendedPageResponse = DotCMSPageResponse>(
132132
url: string,
133133
options?: DotCMSPageRequestParams
134-
): Promise<DotCMSPageResponse> {
134+
): Promise<DotCMSComposedPageResponse<T>> {
135135
const {
136136
languageId = '1',
137137
mode = 'LIVE',

examples/angular/src/app/app.component.css

Whitespace-only changes.

examples/angular/src/app/app.component.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { Component } from '@angular/core';
22
import { RouterOutlet } from '@angular/router';
33

44
@Component({
5-
selector: 'app-root',
6-
standalone: true,
7-
imports: [RouterOutlet],
8-
template: '<router-outlet />',
9-
styleUrl: './app.component.css',
5+
selector: 'app-root',
6+
standalone: true,
7+
imports: [RouterOutlet],
8+
template: '<router-outlet />'
109
})
1110
export class AppComponent {}
Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,49 @@
11
import { ApplicationConfig, InjectionToken } from '@angular/core';
22
import { provideRouter } from '@angular/router';
3+
import { createDotCMSClient } from '@dotcms/client/next';
34

4-
import { ClientConfig, DotCmsClient } from '@dotcms/client';
55
import { provideDotCMSImageLoader } from '@dotcms/angular';
66

77
import { routes } from './app.routes';
88
import { environment } from '../environments/environment';
9+
import { DotCMSClientConfig } from '@dotcms/types';
10+
import { DotCMSEditablePageService } from '@dotcms/angular/next';
911

10-
export const DOTCMS_CLIENT_TOKEN = new InjectionToken<DotCmsClient>('DOTCMS_CLIENT');
12+
export const DOTCMS_CLIENT_TOKEN = new InjectionToken<ReturnType<typeof createDotCMSClient>>(
13+
'DOTCMS_CLIENT'
14+
);
1115

12-
const DOTCMS_CLIENT_CONFIG: ClientConfig = {
13-
dotcmsUrl: environment.dotcmsUrl,
14-
authToken: environment.authToken,
15-
siteId: environment.siteId,
16+
const DOTCMS_CLIENT_CONFIG: DotCMSClientConfig = {
17+
dotcmsUrl: environment.dotcmsUrl,
18+
authToken: environment.authToken,
19+
siteId: environment.siteId
1620
};
1721

18-
const client = DotCmsClient.init(DOTCMS_CLIENT_CONFIG);
22+
const client = createDotCMSClient(DOTCMS_CLIENT_CONFIG);
1923

2024
export const appConfig: ApplicationConfig = {
21-
providers: [
22-
provideRouter(routes),
23-
/**
24-
* We provide the ⁠DOTCMS_CLIENT_TOKEN with the initialized ⁠DotCmsClient instance, enabling
25-
* its injection throughout the application. This approach ensures a single ⁠DotCmsClient
26-
* instance is used, promoting consistency and centralized management of client configuration.
27-
*/
28-
{
29-
provide: DOTCMS_CLIENT_TOKEN,
30-
useValue: client
31-
},
32-
/**
33-
* This custom image loader, designed for the NgOptimizedImage component, appends the dotCMS URL
34-
* to the image source if it’s not an external URL.
35-
*
36-
* Additionally, it appends the ⁠language_id query parameter if the ⁠loaderParams object contains
37-
* a ⁠languageId key. To use an image from an external URL, set the ⁠isOutsideSRC key to ⁠true in
38-
* the ⁠loaderParams object.
39-
* <img [ngSrc]="https://my-url.com/some.jpg" [loaderParams]="{isOutsideSRC: true}" />
40-
* For further customization, you can provide your own image loader implementation.
41-
*/
42-
provideDotCMSImageLoader(environment.dotcmsUrl),
43-
],
25+
providers: [
26+
provideRouter(routes),
27+
/**
28+
* We provide the ⁠DOTCMS_CLIENT_TOKEN with the initialized ⁠DotCmsClient instance, enabling
29+
* its injection throughout the application. This approach ensures a single ⁠DotCmsClient
30+
* instance is used, promoting consistency and centralized management of client configuration.
31+
*/
32+
{
33+
provide: DOTCMS_CLIENT_TOKEN,
34+
useValue: client
35+
},
36+
/**
37+
* This custom image loader, designed for the NgOptimizedImage component, appends the dotCMS URL
38+
* to the image source if it’s not an external URL.
39+
*
40+
* Additionally, it appends the ⁠language_id query parameter if the ⁠loaderParams object contains
41+
* a ⁠languageId key. To use an image from an external URL, set the ⁠isOutsideSRC key to ⁠true in
42+
* the ⁠loaderParams object.
43+
* <img [ngSrc]="https://my-url.com/some.jpg" [loaderParams]="{isOutsideSRC: true}" />
44+
* For further customization, you can provide your own image loader implementation.
45+
*/
46+
provideDotCMSImageLoader(environment.dotcmsUrl),
47+
DotCMSEditablePageService
48+
]
4449
};
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { Routes } from '@angular/router';
2-
import { DotCMSPagesComponent } from './pages/pages.component';
2+
import { DotCMSPageComponent } from './pages/dot-cms-page/dot-cms-page.component';
33
import { BlogComponent } from './pages/blog/blog.component';
44

55
export const routes: Routes = [
6-
7-
{
8-
path: 'blog/post/:slug',
9-
component: BlogComponent
10-
},
11-
{
12-
path: '**',
13-
component: DotCMSPagesComponent
14-
},
6+
{
7+
path: 'blog/post/:slug',
8+
component: BlogComponent
9+
},
10+
{
11+
path: '**',
12+
component: DotCMSPageComponent
13+
}
1514
];
Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
11
import { NgOptimizedImage } from '@angular/common';
22
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
33
import { RouterLink } from '@angular/router';
4-
import { DotCMSContentlet } from '@dotcms/angular';
4+
import { DotCMSBasicContentlet } from '@dotcms/types';
5+
6+
interface ActivityContentlet extends DotCMSBasicContentlet {
7+
image?: {
8+
identifier: string;
9+
};
10+
title: string;
11+
description: string;
12+
urlTitle: string;
13+
}
514

615
@Component({
7-
selector: 'app-activity',
8-
standalone: true,
9-
imports: [RouterLink, NgOptimizedImage],
10-
template: ` <article class="overflow-hidden p-4 bg-white rounded shadow-lg">
11-
@if (contentlet().image; as image) {
12-
<img
13-
class="w-full"
14-
[ngSrc]="image"
15-
width="100"
16-
height="100"
17-
alt="Activity Image"
18-
/>
19-
}
20-
<div class="px-6 py-4">
21-
<p class="mb-2 text-xl font-bold">{{ contentlet().title }}</p>
22-
<p class="text-base line-clamp-3">{{ contentlet()['description'] }}</p>
23-
</div>
24-
<div class="px-6 pt-4 pb-2">
25-
<a
26-
[routerLink]="'/activities/' + contentlet()['urlTitle'] || '#'"
27-
class="inline-block px-4 py-2 font-bold text-white bg-red-400 rounded-full hover:bg-red-500"
28-
>
29-
Link to detail →
30-
</a>
31-
</div>
32-
</article>`,
33-
styleUrl: './activity.component.css',
34-
changeDetection: ChangeDetectionStrategy.OnPush,
16+
selector: 'app-activity',
17+
standalone: true,
18+
imports: [RouterLink, NgOptimizedImage],
19+
template: ` <article class="overflow-hidden p-4 bg-white rounded-sm shadow-lg my-2">
20+
@if (contentlet().inode; as inode) {
21+
<div class="relative w-full h-56 overflow-hidden">
22+
<img class="object-cover w-full h-full" [ngSrc]="inode" fill alt="Activity Image" />
23+
</div>
24+
}
25+
<div class="px-6 py-4">
26+
<p class="mb-2 text-xl font-bold">{{ contentlet().title }}</p>
27+
<p class="text-base line-clamp-3">{{ contentlet().description }}</p>
28+
</div>
29+
<div class="px-6 pt-4 pb-2">
30+
<a
31+
[routerLink]="'/activities/' + contentlet().urlTitle || '#'"
32+
class="inline-block px-4 py-2 font-bold text-white bg-red-400 rounded-full hover:bg-red-500">
33+
Link to detail →
34+
</a>
35+
</div>
36+
</article>`,
37+
styleUrl: './activity.component.css',
38+
changeDetection: ChangeDetectionStrategy.OnPush
3539
})
3640
export class ActivityComponent {
37-
contentlet = input.required<DotCMSContentlet>();
41+
contentlet = input.required<ActivityContentlet>();
3842
}
Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,50 @@
11
import { NgOptimizedImage } from '@angular/common';
22
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
33
import { RouterLink } from '@angular/router';
4-
import { DotCMSContentlet, DotEditableTextComponent } from '@dotcms/angular';
4+
5+
import { DotCMSEditableTextComponent } from '@dotcms/angular/next';
6+
import { DotCMSBasicContentlet } from '@dotcms/types';
7+
8+
interface BannerContentlet extends DotCMSBasicContentlet {
9+
image?: {
10+
identifier: string;
11+
};
12+
title: string;
13+
caption: string;
14+
link: string;
15+
buttonText: string;
16+
}
517

618
@Component({
7-
selector: 'app-banner',
8-
standalone: true,
9-
imports: [RouterLink, NgOptimizedImage, DotEditableTextComponent],
10-
template: `<div
11-
class="flex overflow-hidden relative justify-center items-center w-full h-96 bg-gray-200"
12-
>
13-
@if (contentlet().image; as image) {
14-
<img
15-
class="object-cover w-full"
16-
[ngSrc]="image"
17-
[alt]="contentlet().title"
18-
fill
19-
priority
20-
/>
21-
}
22-
<div
23-
class="flex absolute inset-0 flex-col justify-center items-center p-4 text-center text-white"
24-
>
25-
<h2 class="mb-2 text-6xl font-bold text-shadow">
26-
<dot-editable-text fieldName="title" [contentlet]="contentlet()" />
27-
</h2>
28-
<p class="mb-4 text-xl text-shadow">{{ contentlet()['caption'] }}</p>
29-
<a
30-
class="p-4 text-xl bg-red-400 rounded transition duration-300 hover:bg-red-500"
31-
[routerLink]="contentlet()['link']"
32-
>
33-
{{ contentlet()['buttonText'] }}
34-
</a>
35-
</div>
36-
</div>`,
37-
styleUrl: './banner.component.css',
38-
changeDetection: ChangeDetectionStrategy.OnPush,
19+
selector: 'app-banner',
20+
standalone: true,
21+
imports: [RouterLink, NgOptimizedImage, DotCMSEditableTextComponent],
22+
template: `<div
23+
class="flex overflow-hidden relative justify-center items-center w-full h-96 bg-gray-200">
24+
@if (contentlet().image?.identifier; as imageIdentifier) {
25+
<img
26+
class="object-cover w-full"
27+
[ngSrc]="imageIdentifier"
28+
[alt]="contentlet().title"
29+
fill
30+
priority />
31+
}
32+
<div
33+
class="flex absolute inset-0 flex-col justify-center items-center p-4 text-center text-white">
34+
<h2 class="mb-2 text-6xl font-bold text-shadow">
35+
<dotcms-editable-text fieldName="title" [contentlet]="contentlet()" />
36+
</h2>
37+
<p class="mb-4 text-xl text-shadow">{{ contentlet().caption }}</p>
38+
<a
39+
class="p-4 text-xl bg-red-400 rounded-sm transition duration-300 hover:bg-red-500"
40+
[routerLink]="contentlet().link">
41+
{{ contentlet().buttonText }}
42+
</a>
43+
</div>
44+
</div>`,
45+
styleUrl: './banner.component.css',
46+
changeDetection: ChangeDetectionStrategy.OnPush
3947
})
4048
export class BannerComponent {
41-
contentlet = input.required<DotCMSContentlet>();
49+
contentlet = input.required<BannerContentlet>();
4250
}
Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
1-
import { CommonModule, NgOptimizedImage } from '@angular/common';
1+
import { NgOptimizedImage } from '@angular/common';
22
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
3-
import { DotCMSContentlet } from '@dotcms/angular';
3+
import { DotCMSBasicContentlet } from '@dotcms/types';
4+
5+
interface ImageContentlet extends DotCMSBasicContentlet {
6+
fileAsset: string;
7+
title: string;
8+
description: string;
9+
}
410

511
@Component({
6-
selector: 'app-image',
7-
standalone: true,
8-
imports: [CommonModule, NgOptimizedImage],
9-
template: `<div
10-
class="overflow-hidden relative mb-4 bg-white rounded shadow-lg group"
11-
>
12-
<div class="relative w-full h-96 bg-gray-200">
13-
<img
14-
class="object-cover"
15-
[ngSrc]="contentlet()['fileAsset']"
16-
[alt]="contentlet().title"
17-
fill
18-
/>
19-
</div>
20-
<div
21-
class="absolute bottom-0 px-6 py-8 w-full text-white bg-orange-500 bg-opacity-80 transition-transform duration-300 translate-y-full w-100 group-hover:translate-y-0"
22-
>
23-
<div class="mb-2 text-2xl font-bold">{{ contentlet().title }}</div>
24-
<p class="text-base">{{ contentlet()['description'] }}</p>
25-
</div>
26-
</div>`,
27-
styleUrl: './image.component.css',
28-
changeDetection: ChangeDetectionStrategy.OnPush,
12+
selector: 'app-image',
13+
standalone: true,
14+
imports: [NgOptimizedImage],
15+
template: `<div class="overflow-hidden relative mb-4 bg-white rounded shadow-lg group">
16+
<div class="relative w-full h-96 bg-gray-200">
17+
<img
18+
class="object-cover"
19+
[ngSrc]="contentlet().fileAsset"
20+
[alt]="contentlet().title"
21+
fill />
22+
</div>
23+
<div
24+
class="absolute bottom-0 px-6 py-8 w-full text-white bg-orange-500 bg-opacity-80 transition-transform duration-300 translate-y-full w-100 group-hover:translate-y-0">
25+
<div class="mb-2 text-2xl font-bold">{{ contentlet().title }}</div>
26+
<p class="text-base">{{ contentlet().description }}</p>
27+
</div>
28+
</div>`,
29+
styleUrl: './image.component.css',
30+
changeDetection: ChangeDetectionStrategy.OnPush
2931
})
3032
export class ImageComponent {
31-
contentlet = input.required<DotCMSContentlet>();
33+
contentlet = input.required<ImageContentlet>();
3234
}

0 commit comments

Comments
 (0)