Skip to content

Angular Frontend Added to Stack #310

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const dependencyVersionMap = {
mongoose: "^8.14.0",

"vite-plugin-pwa": "^0.21.2",
"@angular/service-worker": "^20.0.2",
"@vite-pwa/assets-generator": "^0.2.6",

"@tauri-apps/cli": "^2.4.0",
Expand Down Expand Up @@ -92,6 +93,7 @@ export const dependencyVersionMap = {
"@orpc/tanstack-query": "^1.5.0",

"@trpc/tanstack-react-query": "^11.0.0",
"@tanstack/angular-query-experimental": "5.80.2",
"@trpc/server": "^11.0.0",
"@trpc/client": "^11.0.0",

Expand Down
76 changes: 72 additions & 4 deletions apps/cli/src/helpers/project-generation/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from "fs-extra";
import { globby } from "globby";
import { PKG_ROOT } from "../../constants";
import type { ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";
import { processTemplate } from "../../utils/template-processor";

async function processAndCopyFiles(
Expand Down Expand Up @@ -70,12 +71,19 @@ export async function setupFrontendTemplates(
const hasNuxtWeb = context.frontend.includes("nuxt");
const hasSvelteWeb = context.frontend.includes("svelte");
const hasSolidWeb = context.frontend.includes("solid");
const hasAngularWeb = context.frontend.includes("angular");
const hasNativeWind = context.frontend.includes("native-nativewind");
const hasUnistyles = context.frontend.includes("native-unistyles");
const _hasNative = hasNativeWind || hasUnistyles;
const isConvex = context.backend === "convex";

if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) {
if (
hasReactWeb ||
hasNuxtWeb ||
hasSvelteWeb ||
hasSolidWeb ||
hasAngularWeb
) {
const webAppDir = path.join(projectDir, "apps/web");
await fs.ensureDir(webAppDir);

Expand Down Expand Up @@ -180,6 +188,28 @@ export async function setupFrontendTemplates(
} else {
}
}
} else if (hasAngularWeb) {
const angularBaseDir = path.join(PKG_ROOT, "templates/frontend/angular");
if (await fs.pathExists(angularBaseDir)) {
await processAndCopyFiles("**/*", angularBaseDir, webAppDir, context);
} else {
}

if (!isConvex && (context.api === "orpc" || context.api === "trpc")) {
const apiWebAngularDir = path.join(
PKG_ROOT,
`templates/api/${context.api}/web/angular`,
);
if (await fs.pathExists(apiWebAngularDir)) {
await processAndCopyFiles(
"**/*",
apiWebAngularDir,
webAppDir,
context,
);
} else {
}
}
}
}

Expand Down Expand Up @@ -378,7 +408,7 @@ export async function setupAuthTemplate(
const hasNativeWind = context.frontend.includes("native-nativewind");
const hasUnistyles = context.frontend.includes("native-unistyles");
const hasNative = hasNativeWind || hasUnistyles;

const hasAngularWeb = context.frontend.includes("angular");
if (serverAppDirExists) {
const authServerBaseSrc = path.join(PKG_ROOT, "templates/auth/server/base");
if (await fs.pathExists(authServerBaseSrc)) {
Expand Down Expand Up @@ -435,7 +465,11 @@ export async function setupAuthTemplate(
}

if (
(hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) &&
(hasReactWeb ||
hasNuxtWeb ||
hasSvelteWeb ||
hasSolidWeb ||
hasAngularWeb) &&
webAppDirExists
) {
if (hasReactWeb) {
Expand Down Expand Up @@ -503,6 +537,20 @@ export async function setupAuthTemplate(
} else {
}
}
} else if (hasAngularWeb) {
const authWebAngularSrc = path.join(
PKG_ROOT,
"templates/auth/web/angular",
);
if (await fs.pathExists(authWebAngularSrc)) {
await processAndCopyFiles(
"**/*",
authWebAngularSrc,
webAppDir,
context,
);
} else {
}
}
}

Expand Down Expand Up @@ -570,6 +618,15 @@ export async function setupAddonsTemplate(
)
) {
addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/vite");
} else if (context.frontend.includes("angular")) {
addonSrcDir = path.join(
PKG_ROOT,
"templates/addons/pwa/apps/web/angular",
);
await addPackageDependency({
dependencies: ["@angular/service-worker"],
projectDir: webAppDir,
});
} else {
continue;
}
Expand Down Expand Up @@ -608,7 +665,7 @@ export async function setupExamplesTemplate(
const hasNuxtWeb = context.frontend.includes("nuxt");
const hasSvelteWeb = context.frontend.includes("svelte");
const hasSolidWeb = context.frontend.includes("solid");

const hasAngularWeb = context.frontend.includes("angular");
for (const example of context.examples) {
if (example === "none") continue;

Expand Down Expand Up @@ -758,6 +815,17 @@ export async function setupExamplesTemplate(
);
} else {
}
} else if (hasAngularWeb) {
const exampleWebAngularSrc = path.join(exampleBaseDir, "web/angular");
if (await fs.pathExists(exampleWebAngularSrc)) {
await processAndCopyFiles(
"**/*",
exampleWebAngularSrc,
webAppDir,
context,
);
} else {
}
}
}

Expand Down
32 changes: 29 additions & 3 deletions apps/cli/src/helpers/setup/api-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
const hasNuxtWeb = frontend.includes("nuxt");
const hasSvelteWeb = frontend.includes("svelte");
const hasSolidWeb = frontend.includes("solid");

const hasAngularWeb = frontend.includes("angular");
if (!isConvex && api !== "none") {
const serverDir = path.join(projectDir, "apps/server");
const serverDirExists = await fs.pathExists(serverDir);
Expand Down Expand Up @@ -106,6 +106,22 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
projectDir: webDir,
});
}
} else if (hasAngularWeb) {
if (api === "trpc") {
await addPackageDependency({
dependencies: ["@trpc/client", "@trpc/server"],
projectDir: webDir,
});
} else if (api === "orpc") {
await addPackageDependency({
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server",
],
projectDir: webDir,
});
}
}
}

Expand Down Expand Up @@ -142,7 +158,7 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
];
const needsSolidQuery = frontend.includes("solid");
const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));

const needsAngularQuery = frontend.includes("angular");
if (needsReactQuery && !isConvex) {
const reactQueryDeps: AvailableDependencies[] = ["@tanstack/react-query"];
const reactQueryDevDeps: AvailableDependencies[] = [
Expand Down Expand Up @@ -206,7 +222,17 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
}
}
}

if (needsAngularQuery && !isConvex) {
if (webDirExists) {
const webPkgJsonPath = path.join(webDir, "package.json");
if (await fs.pathExists(webPkgJsonPath)) {
await addPackageDependency({
dependencies: ["@tanstack/angular-query-experimental"],
projectDir: webDir,
});
}
}
}
if (isConvex) {
if (webDirExists) {
const webPkgJsonPath = path.join(webDir, "package.json");
Expand Down
6 changes: 4 additions & 2 deletions apps/cli/src/prompts/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ export async function getAddonsChoice(
frontends?.includes("react-router") ||
frontends?.includes("tanstack-router") ||
frontends?.includes("solid") ||
frontends?.includes("next");
frontends?.includes("next") ||
frontends?.includes("angular");

const hasCompatibleTauriFrontend =
frontends?.includes("react-router") ||
frontends?.includes("tanstack-router") ||
frontends?.includes("nuxt") ||
frontends?.includes("svelte") ||
frontends?.includes("solid") ||
frontends?.includes("next");
frontends?.includes("next") ||
frontends?.includes("angular");

const allPossibleOptions: AddonOption[] = [
{
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/src/prompts/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export async function getApiChoice(
const includesNuxt = frontend?.includes("nuxt");
const includesSvelte = frontend?.includes("svelte");
const includesSolid = frontend?.includes("solid");
const includesAngular = frontend?.includes("angular");

let apiOptions = [
{
Expand All @@ -35,7 +36,7 @@ export async function getApiChoice(
},
];

if (includesNuxt || includesSvelte || includesSolid) {
if (includesNuxt || includesSvelte || includesSolid || includesAngular) {
apiOptions = [
{
value: "orpc" as const,
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/prompts/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function getBackendFrameworkChoice(
if (backendFramework !== undefined) return backendFramework;

const hasIncompatibleFrontend = frontends?.some(
(f) => f === "nuxt" || f === "solid",
(f) => f === "nuxt" || f === "solid" || f === "angular",
);

const backendOptions: Array<{
Expand Down
6 changes: 5 additions & 1 deletion apps/cli/src/prompts/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export async function getExamplesChoice(
},
];

if (backend !== "elysia" && !frontends?.includes("solid")) {
if (
backend !== "elysia" &&
!frontends?.includes("solid") &&
!frontends?.includes("angular")
) {
options.push({
value: "ai" as const,
label: "AI Chat",
Expand Down
5 changes: 5 additions & 0 deletions apps/cli/src/prompts/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export async function getFrontendChoice(
label: "TanStack Start (devinxi)",
hint: "SSR, Server Functions, API Routes and more with TanStack Router",
},
{
value: "angular" as const,
label: "Angular",
hint: "The web framework that empowers developers to build fast, reliable applications",
},
];

const webOptions = allWebOptions.filter((option) => {
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const FrontendSchema = z
"native-unistyles",
"svelte",
"solid",
"angular",
"none",
])
.describe("Frontend framework");
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/src/utils/template-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ handlebars.registerHelper("or", (a, b) => a || b);

handlebars.registerHelper("eq", (a, b) => a === b);

handlebars.registerHelper("not", (a) => !a);

handlebars.registerHelper(
"includes",
(array, value) => Array.isArray(array) && array.includes(value),
Expand Down
Loading