Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[angular-xmcloud] Introduce SXA layout component for angular #1873

Merged
merged 15 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ Our versioning strategy is as follows:
* `[create-sitecore-jss]` Rework Angular initializer to support XMCloud and SXP journeys ([#1845](https://github.com/Sitecore/jss/pull/1845))([#1858](https://github.com/Sitecore/jss/pull/1858))([#1868](https://github.com/Sitecore/jss/pull/1868))
* `[create-sitecore-jss]` Allow node-xmcloud-proxy app to be installed alongside Angular SPA application
* `nodeAppDestination` arg can be passed into `create-sitecore-jss` command to define path for proxy to be installed in
* `[create-sitecore-jss]``[template/angular-xmcloud]` Angular SXA components ([#1864](https://github.com/Sitecore/jss/pull/1864))([#1872](https://github.com/Sitecore/jss/pull/1872))
* `[sitecore-jss-angular]` Angular placeholder now supports SXA components ([#1870](https://github.com/Sitecore/jss/pull/1870))([#1872](https://github.com/Sitecore/jss/pull/1872))
* `[create-sitecore-jss]``[template/angular-xmcloud]` Angular SXA components ([#1864](https://github.com/Sitecore/jss/pull/1864))
* `[sitecore-jss-angular]` Angular placeholder now supports SXA components ([#1870](https://github.com/Sitecore/jss/pull/1870))
* `[template/angular-xmcloud]` Angular SXA layout ([#1873](https://github.com/Sitecore/jss/pull/1873))

### 🛠 Breaking Change

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Inject, Injectable } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { HTMLLink } from '@sitecore-jss/sitecore-jss-angular';

@Injectable()
export class JssLinkService {
document: Document;

constructor() {
this.document = Inject(DOCUMENT);
}

/**
* Adds link element in the document head.
* @param headLinks - An array of HTMLLink objects to add to the head.
*/
addHeadLinks(headLink: HTMLLink) {
if (!headLink) {
return;
}

this.createLink(headLink);
}

/**
* Creates a new link element and appends it to the head.
* @param headLink - An HTMLLink object to be added.
*/
private createLink(headLink: HTMLLink) {
if (!headLink.rel || !headLink.href) {
console.log('Invalid link object:', headLink);
return;
}

const link: HTMLLinkElement = this.document.createElement('link');
link.setAttribute('rel', headLink.rel);
link.setAttribute('href', headLink.href);
this.document.head.appendChild(link);
}

/**
* Removes all link elements that match the specified rel attribute.
* @param rel - The rel attribute of the links to be removed.
*/
removeLinksByRel(rel: string) {
const links = this.document.head.querySelectorAll(`link[rel="${rel}"]`);
links.forEach((link) => {
this.document.head.removeChild(link);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<app-navigation></app-navigation>
<div class="{{ mainClassPageEditing }}">
<ng-container *ngIf="state === LayoutState.Layout">
<app-scripts></app-scripts>
<header>
<div id="header">
<sc-placeholder
name="headless-header"
[rendering]="route"
addy-pathania marked this conversation as resolved.
Show resolved Hide resolved
(loaded)="onPlaceholderLoaded($event)"
></sc-placeholder>
</div>
</header>
<main>
<div id="content">
<sc-placeholder
name="headless-main"
[rendering]="route"
(loaded)="onPlaceholderLoaded($event)"
></sc-placeholder>
</div>
</main>
<footer>
<div id="footer">
<sc-placeholder
name="headless-footer"
[rendering]="route"
addy-pathania marked this conversation as resolved.
Show resolved Hide resolved
(loaded)="onMainPlaceholderLoaded($event)"
></sc-placeholder>
</div>
</footer>
</ng-container>

<app-not-found
*ngIf="state === LayoutState.NotFound"
[errorContextData]="errorContextData"
></app-not-found>
<app-server-error *ngIf="state === LayoutState.Error"></app-server-error>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* eslint-disable no-shadow, no-console */
import { Component, OnInit, OnDestroy } from '@angular/core';
import {
RouteData,
Field,
LayoutServiceContextData,
getContentStylesheetLink,
} from '@sitecore-jss/sitecore-jss-angular';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { JssState } from '../../JssState';
import { JssMetaService } from '../../jss-meta.service';
import { JssLinkService } from '../../jss-link.service';

enum LayoutState {
Layout,
NotFound,
Error,
}

interface RouteFields {
[name: string]: unknown;
pageTitle: Field<string>;
}

@Component({
selector: 'app-layout',
templateUrl: './layout.component.html',
})
export class LayoutComponent implements OnInit, OnDestroy {
route: RouteData<RouteFields>;
state: LayoutState;
LayoutState = LayoutState;
subscription: Subscription;
errorContextData: LayoutServiceContextData;
mainClassPageEditing: string;

constructor(
private activatedRoute: ActivatedRoute,
private readonly meta: JssMetaService,
private linkService: JssLinkService
) {}

ngOnInit() {
// route data is populated by the JssRouteResolver
this.subscription = this.activatedRoute.data.subscribe(
(data: { jssState: JssState<RouteFields> }) => {
if (!data.jssState) {
this.state = LayoutState.NotFound;
return;
}

if (data.jssState.sitecore && data.jssState.sitecore.route) {
this.route = data.jssState.sitecore.route;
this.setMetadata(this.route.fields);
this.state = LayoutState.Layout;
this.mainClassPageEditing = data.jssState.sitecore.context.pageEditing
? 'editing-mode'
: 'prod-mode';

/** TODO: get contextId and edgeUrl properly **/
const sitecoreEdgeContextId = '';
const sitecoreEdgeUrl = '';
addy-pathania marked this conversation as resolved.
Show resolved Hide resolved
const contentStyles = getContentStylesheetLink(
data.jssState.sitecore,
addy-pathania marked this conversation as resolved.
Show resolved Hide resolved
sitecoreEdgeContextId,
sitecoreEdgeUrl
);

// Clear existing stylesheets
this.linkService.removeLinksByRel('stylesheet');

if (contentStyles) {
this.linkService.addHeadLinks(contentStyles);
}
}

if (data.jssState.routeFetchError) {
if (
data.jssState.routeFetchError.status >= 400 &&
data.jssState.routeFetchError.status < 500
) {
this.state = LayoutState.NotFound;
} else {
this.state = LayoutState.Error;
}

this.errorContextData =
data.jssState.routeFetchError.data && data.jssState.routeFetchError.data.sitecore;
}
}
);
}

ngOnDestroy() {
// important to unsubscribe when the component is destroyed
this.subscription.unsubscribe();
}

setMetadata(routeFields: RouteFields) {
// set page title, if it exists
if (routeFields && routeFields.pageTitle) {
this.meta.setTitle(routeFields.pageTitle.value || 'Page');
}
}

onMainPlaceholderLoaded(_placeholderName: string) {
// you may optionally hook to the loaded event for a placeholder,
// which can be useful for analytics and other DOM-based things that need to know when a placeholder's content is available.
}
}
addy-pathania marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
RouteData,
LayoutServiceContextData,
} from '@sitecore-jss/sitecore-jss-angular';
import { RouteData, LayoutServiceContextData } from '@sitecore-jss/sitecore-jss-angular';
import { LayoutServiceError } from './layout/jss-layout.service';

export class JssState<Fields = Record<string, unknown>> {
Expand Down
2 changes: 2 additions & 0 deletions packages/sitecore-jss-angular/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export {
ComponentRendering,
ComponentFields,
ComponentParams,
getContentStylesheetLink,
} from '@sitecore-jss/sitecore-jss/layout';
export {
RetryStrategy,
Expand All @@ -69,6 +70,7 @@ export {
HttpResponse,
enableDebug,
ClientError,
HTMLLink,
} from '@sitecore-jss/sitecore-jss';
export { isServer } from '@sitecore-jss/sitecore-jss/utils';
export {
Expand Down