This project was generated with Angular CLI version 14.2.6.
Run ng serve
for a dev server. Navigate to http://localhost:4200/de
. The application will automatically reload if you change any of the source files.
Run ng build --configuration production --localize --base-href / --deploy-url ''
to build the project.
The build artifacts will be stored in the target/classes/META-INF/resources/frontend
directory.
To get more help on the Angular CLI use ng help
or go check out the Angular CLI Overview and Command Reference page.
Beside the aspect, that this Application is an internationalized Application, there are some Changes within angular.json:
First of all the Output Path has been changed so that it will be written to target/classes/META-INF/resources/frontend
:
...
"projects": {
"calcmaster": {
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "target/classes/META-INF/resources/frontend",
...
}
}
}
}
}
The Directory target/classes
is the Directory where Maven will compile all Java-Classes and copy all Resource Files to.
META-INF/resources
is the Base-Directory for a Webjar.
frontend
is the Identifier of the Webjar within the Spring Boot Application.
To work correctly with the internationalized Version of the Page, the baseHref
, deployUrl
and servePath
Properties have to
be set to the Default-Language (specified by i18n/sourceLocale
).
...
"projects": {
"calcmaster": {
...
"i18n": {
"sourceLocale": "de"
},
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "target/classes/META-INF/resources/frontend",
"baseHref": "/de/",
"deployUrl": "/de/"
}
},
...
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"servePath": "/de/"
}
}
}
}
}
Also add a Proxy-Configuration to the serve
Section so that all Calls to /api
and /webjars
would be redirected to the Spring-Boot Application.
...
"projects": {
"calcmaster": {
"architect": {
...
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"servePath": "/de/"
},
"configurations": {
"development": {
"browserTarget": "calcmaster:build:development",
"proxyConfig": "src/proxy.conf.mjs"
}
},
"defaultConfiguration": "development"
}
}
}
While building the Angular Application it should ignore the baseHref
and deployUrl
Properties set especially for
the Development. Therefor the build
Task would disable this by Options:
"scripts": {
"build": "ng build --configuration production --localize --base-href / --deploy-url \"\""
}
One should also specify, that ALL Locales should be generated by specifying the Option --localize
.
Also note that the Option --deploy-url
has been set to an empty String by using 2 Quotation Marks.
The File index.html
will be manipulated to be a Thymeleaf Template:
<!doctype html>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head>
...
<base href="/" th:href="@{/}">
</head>
...
This is necessary for a used reverse Proxy Configuration. By using the Proxy-Header X-Forwarded-Prefix
one can deploy
the Application under any Context-Path and the relative URL's will work out of the Box.
A multilingual Angular Application will be built for every supported Language. Therefor it will be written to different
Destination Directory (one for every supported Language).
But most of the Time the Application will call the same REST-APIs of the Backend WITHOUT the prefixed Language.
To work correctly an HTTP-Interceptor will be used to extract the Language out of the Path and put it as a Header-Information:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { Location } from "@angular/common";
@Injectable({ providedIn: 'root' })
export class ApiPathInterceptor implements HttpInterceptor {
constructor(private _location: Location) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!req.url.startsWith("api/")) {
return next.handle(req);
}
const url = this._location.prepareExternalUrl(`../${req.url}`);
const language = Location.stripTrailingSlash(this._location.prepareExternalUrl('/')).split('/').pop();
if (language) {
return next.handle(req.clone({ url: url, setHeaders: { 'Accept-Language': language } }));
}
return next.handle(req.clone({ url: url }));
}
}
The File pom.xml is the Definition of the Frontend Build. With the Frontend Maven Plugin one can compile the Angular Application using Apache Maven:
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<configuration>
<installDirectory>target</installDirectory>
</configuration>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>install -d</arguments>
</configuration>
</execution>
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
After creating the Angular Application the baseHref
must be adapted so that the index.html
would behave correctly
as a multilingual Page by using the maven-replacer-plugin.
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>maven-replacer-plugin</artifactId>
<configuration>
<regex>false</regex>
</configuration>
<executions>
<execution>
<id>replace /de</id>
<phase>process-resources</phase>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<basedir>${project.build.outputDirectory}/META-INF/resources/frontend/de</basedir>
<filesToInclude>index.html</filesToInclude>
<replacements>
<replacement>
<token><![CDATA[<base href="/de/" th:href="@{/}">]]></token>
<value><![CDATA[<base href="/de/" th:href="@{/de/}">]]></value>
</replacement>
</replacements>
</configuration>
</execution>
<execution>
<id>replace /en</id>
<phase>process-resources</phase>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<basedir>${project.build.outputDirectory}/META-INF/resources/frontend/en</basedir>
<filesToInclude>index.html</filesToInclude>
<replacements>
<replacement>
<token><![CDATA[<base href="/en/" th:href="@{/}">]]></token>
<value><![CDATA[<base href="/en/" th:href="@{/en/}">]]></value>
</replacement>
</replacements>
</configuration>
</execution>
</executions>
</plugin>
The Replacement of the baseHref
must be executed for every supported Language (in this case for de
and en
).
Be aware that the trailing Slash (/de/
or /en/
) is necessary so that the Browser can append the Base HREF to any relative
URL of the Angular Application.
Be aware that you have to use relative URLs when you communicate with the Backend via httpClient
.
Using absolute URLs will not use the baseHref
and won't work when deployed with a Context-Path others than /
.
It is also a good common Practice to use APP_BASE_HREF
and the
LocationService of Angular.
With the LocationService one can prepare external
URLs which uses the correct baseHref
.