Skip to content

Instantly share code, notes, and snippets.

@rifayetuxbd
Last active July 16, 2024 03:37
Show Gist options
  • Save rifayetuxbd/b62b83193453a2634a8e96c5577de343 to your computer and use it in GitHub Desktop.
Save rifayetuxbd/b62b83193453a2634a8e96c5577de343 to your computer and use it in GitHub Desktop.
Create angular applicaitonwith tailwind css based spartan ui, ngxtension, tanstack

Angular Project setup with spartan

  • pnpm dlx @angular/cli@latest new APP_NAME -s -t --routing --package-manager=pnpm --ssr
  • pnpm add -D @spartan-ng/cli
  • pnpm add @angular/cdk @spartan-ng/ui-core
  • pnpm add -D tailwindcss postcss autoprefixer
  • pnpm dlx tailwindcss init

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
 presets: [require('@spartan-ng/ui-core/hlm-tailwind-preset')],
 content: [
   './src/**/*.{html,ts}',
   './REPLACE_WITH_PATH_TO_YOUR_COMPONENTS_DIRECTORY/**/*.{html,ts}',
 ],
 theme: {
   extend: {},
 },
 plugins: [],
};

REPLACE_WITH_PATH_TO_YOUR_COMPONENTS_DIRECTORY => libs/ui

styles.css

@tailwind base;
@tailwind components;
@tailwind utilities;
  • pnpm ng g @spartan-ng/cli:ui-theme @spartan-ng/cli:ui
  • pnpm ng add ngxtension
  • pnpm add @tanstack/angular-table @tanstack/angular-query-experimental
  • pnpm add @tanstack/angular-form @tanstack/zod-form-adapter zod

Final style.css

@import "@angular/cdk/overlay-prebuilt.css";

@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --font-sans: "";
}

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;

    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;

    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;

    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;

    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;

    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;

    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;

    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;

    --destructive: 0, 91%, 71%;
    --destructive-foreground: 210 40% 98%;

    --ring: 212.7 26.8% 83.9;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

angular.json Add necessary settings

"schematics": {
  "@schematics/angular:component": {
  "inlineTemplate": true,
  "inlineStyle": true,
  "standalone": true,
  "changeDetection": "OnPush"
 }
}

Configuring eslint and prettier

  • pnpm ng add @angular-eslint/schematics

package.json

scripts: {
    "lint": "ng lint",
    "lint:fix": "ng lint --fix"
    "prettier:check": "prettier --check ./src",
    "prettier:write": "prettier --write ./src"
}

eslint.config.json

// @ts-check
const eslint = require('@eslint/js');
const tseslint = require('typescript-eslint');
const angular = require('angular-eslint');

module.exports = tseslint.config(
  {
    files: ['**/*.ts'],
    extends: [
      eslint.configs.recommended,
      ...tseslint.configs.recommended,
      ...tseslint.configs.stylistic,
      ...angular.configs.tsRecommended,
    ],
    processor: angular.processInlineTemplates,
    rules: {
      '@angular-eslint/directive-selector': [
        'error',
        {
          type: 'attribute',
          prefix: 'app',
          style: 'camelCase',
        },
      ],
      '@angular-eslint/component-selector': [
        'error',
        {
          type: 'element',
          prefix: 'app',
          style: 'kebab-case',
        },
      ],
      '@typescript-eslint/no-unused-vars': ['warn'],
    },
  },
  {
    files: ['**/*.html'],
    extends: [
      ...angular.configs.templateRecommended,
      ...angular.configs.templateAccessibility,
    ],
    rules: {
      'prettier/prettier': [
        'error',
        {
          parser: 'angular',
        },
      ],
    },
  }
);
  • pnpm add -D prettier@latest

  • touch .prettierignore

# See http://help.github.com/ignore-files/ for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db
libs/ui
  • touch .prettierrc.json
{
  "tabWidth": 2,
  "useTabs": false,
  "singleQuote": true,
  "semi": true,
  "bracketSpacing": true,
  "arrowParens": "avoid",
  "trailingComma": "es5",
  "bracketSameLine": true,
  "printWidth": 80,
  "endOfLine": "lf"
}

app.component.ts - and change detection strategy

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ RouterOutlet ],
  host: {
    class: ''
  },
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <router-outlet />
  `,
  styles: [],
})

index.html - add script to choose theme in <head> tag

<script>
      if (
        // check if user had saved dark as their
        // theme when accessing page before
        localStorage.theme === "dark" ||
        // or user's requesting dark color
        // scheme through operating system
        (!("theme" in localStorage) &&
          window.matchMedia("(prefers-color-scheme: dark)").matches)
      ) {
        // then if we have access to the document and the element
        // we add the dark class to the html element and
        // store the dark value in the localStorage
        if (document && document.documentElement) {
          document.documentElement.classList.add("dark");
          localStorage.setItem("theme", "dark");
        }
      } else {
        // else if we have access to the document and the element
        // we remove the dark class to the html element and
        // store the value light in the localStorage
        if (document && document.documentElement) {
          document.documentElement.classList.remove("dark");
          localStorage.setItem("theme", "light");
        }
      }
</script>

theme.type.ts Type for the theme

export type Theme = 'light' | 'dark';

theme.service.ts - Theme service

import { DOCUMENT, isPlatformBrowser } from '@angular/common';
import { injectDestroy } from 'ngxtension/inject-destroy';
import {
  Injectable,
  PLATFORM_ID,
  RendererFactory2,
  inject,
} from '@angular/core';
import { ReplaySubject, takeUntil } from 'rxjs';
import { Theme } from '@/types/theme';

@Injectable({
  providedIn: 'root',
})
export class ThemeService {
  private THEME = 'theme';
  private _platformId = inject(PLATFORM_ID);
  private _renderer = inject(RendererFactory2).createRenderer(null, null);
  private _document = inject(DOCUMENT);
  private _theme = new ReplaySubject<Theme>(1);

  public theme$ = this._theme.asObservable();
  private _destroy$ = injectDestroy();

  constructor() {
    this._syncThemeFromLocalStorage();
    this._toggleClassOnThemeChanges();
  }

  private _syncThemeFromLocalStorage(): void {
    if (isPlatformBrowser(this._platformId)) {
      this._theme.next(
        localStorage.getItem(this.THEME) === 'dark' ? 'dark' : 'light'
      );
    }
  }

  private _toggleClassOnThemeChanges(): void {
    this.theme$.pipe(takeUntil(this._destroy$)).subscribe((theme) => {
      if (theme === 'dark') {
        this._renderer.addClass(this._document.documentElement, 'dark');
      } else {
        if (this._document.documentElement.className.includes('dark')) {
          this._renderer.removeClass(this._document.documentElement, 'dark');
        }
      }
    });
  }

  public setTheme(theme: Theme) {
    localStorage.setItem(this.THEME, theme);
    this._theme.next(theme);
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment