Chat about this codebase

AI-powered code exploration

Online

Project Overview

MohamedPortfolio is a server-side rendered (SSR) Angular application designed as a ready-to-deploy personal portfolio. It combines Angular CLI v17.3.11 scaffolding, SEO-friendly templates, and a full testing setup to help developers launch or fork a modern, high-performance showcase site.

Problems Solved

  • Ensures crawlable, SEO-optimized pages via Angular Universal SSR
  • Provides out-of-the-box meta tags for social sharing (OpenGraph, Twitter Cards)
  • Reduces setup time with preconfigured build/test scripts and folder structure
  • Supports component-level code generation, unit tests, and end-to-end tests

Key Features

  • Angular CLI v17.3.11 project structure
  • Server-Side Rendering with Angular Universal
  • SEO & Social Media Meta Tags in src/index.html
  • Scaffold Commands for components, services, modules
  • Local Dev Server with live reload (npm run dev)
  • Production & SSR Builds (npm run build:prod, npm run build:ssr)
  • Unit Testing with Karma/Jasmine (npm test)
  • End-to-End Testing with Protractor/Cypress (npm run e2e)
  • Favicon & Asset Management

When to Use or Fork

  • You need a SEO-focused Angular portfolio or personal site
  • You want a turnkey SSR setup with minimal configuration
  • You plan to extend with additional sections or custom themes
  • You require a full testing pipeline (unit + e2e) out of the box

Quick Start Commands

Install dependencies

npm install

Run local dev server (hot reload)

npm run dev

Generate a new component

npm run ng generate component components/testimonial

Run unit tests

npm test

Build for production

npm run build:prod

Build and serve SSR

npm run build:ssr
npm run serve:ssr

Core Metadata Snippet

src/index.html includes comprehensive SEO and social tags:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Mohamed Samy Hossebo | Portfolio</title>
  <meta name="description" content="Full-stack developer portfolio showcasing Angular SSR projects.">
  <meta property="og:title" content="Mohamed Samy Hossebo ‒ Portfolio">
  <meta property="og:description" content="Explore Angular, Node.js, and UI/UX case studies.">
  <meta property="og:image" content="assets/og-image.png">
  <meta name="twitter:card" content="summary_large_image">
  <link rel="icon" href="assets/favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Leverage this template and scripts to deploy or customize your own Angular SSR portfolio in minutes.

Getting Started

Follow these steps to clone the portfolio repo, install dependencies, and run the app in development (CSR) or server-side rendering (SSR) modes.

Prerequisites

  • Node.js v18+
  • npm v9+
  • (Optional) Angular CLI v17.3.11

1. Clone the repository

git clone https://github.com/MohamedSamyHossebo/MohamedPortfolio.git
cd MohamedPortfolio

2. Install dependencies

npm install

3. Development Mode (Client-Side Rendering)

Launch a live-reload dev server on http://localhost:4200:

npm run start
  • Changes to components, templates or styles rebuild instantly.

  • To rebuild on file changes without serving, use:

    npm run watch
    

4. Production Build

Compile optimized assets into dist/portfolio/browser:

npm run build -- --configuration production
  • Output path and build options live in angular.json under projects.portfolio.architect.build.options.
  • To switch configurations, append -- --configuration=<name>.

5. Server-Side Rendering (SSR)

This portfolio uses Angular Universal with an Express server (server.ts). After building, serve the SSR bundle:

  1. Build browser and server targets, then prerender routes (if configured):

    npm run build:ssr
    

    or, if build:ssr isn’t defined:

    npm run build && npm run build -- --configuration=server
    
  2. Start the SSR server (default port 4000):

    npm run serve:ssr
    

    or, if your script is named differently:

    npm run serve:ssr:portfolio
    
  3. Open http://localhost:4000 to view server-rendered pages.

Customizing SSR

  • Change server port:

    PORT=3000 npm run serve:ssr
    
  • Modify Express behavior in server.ts (static asset cache, routes, providers).

  • Adjust Angular CLI’s ssr.entry and server options in angular.json under the build target.


You now have the app running in CSR or SSR modes. For testing, running npm run test launches Karma/Jasmine; end-to-end tests run via npm run e2e.

Architecture & Core Concepts

This application combines standalone Angular components, a client-hydrated routing strategy, an Express bridge for Angular Universal SSR, and a Tailwind CSS pipeline for utility-first styling.

Standalone Component Architecture

Angular leverages standalone components to minimize NgModule boilerplate. The root component bootstraps the entire app.

src/app/app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <header class="p-4 bg-theme text-white">
      <h1 class="text-2xl font-bold">My Portfolio</h1>
      <nav>
        <a routerLink="/home" class="mr-4">Home</a>
        <a routerLink="/work" class="mr-4">Work</a>
        <a routerLink="/clients" class="mr-4">Clients</a>
        <a routerLink="/hire">Hire Me</a>
      </nav>
    </header>
    <main class="p-6">
      <router-outlet></router-outlet>
    </main>
  `,
  styles: [`
    header { position: sticky; top: 0; z-index: 50; }
  `]
})
export class AppComponent {}

Key points:

  • standalone: true eliminates the need for NgModule.
  • Imports RouterOutlet directly.
  • Defines global layout and navigation.

Routing Strategy & Client Hydration

Routing lives in app.routes.ts, and hydration is enabled in app.config.ts to replay server-rendered state on the client.

src/app/app.routes.ts

import { Routes } from '@angular/router';
import { WorkComponent } from './components/work/work.component';
import { ClientsComponent } from './components/clients/clients.component';
import { HireComponent } from './components/hire/hire.component';

export const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  {
    path: 'home',
    loadComponent: () =>
      import('./components/home/home.component').then(m => m.HomeComponent)
  },
  { path: 'work',    component: WorkComponent },
  { path: 'clients', component: ClientsComponent },
  { path: 'hire',    component: HireComponent },
  { path: '**',      redirectTo: 'home' }
];

src/app/app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideClientHydration } from '@angular/platform-browser';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, {
      scrollPositionRestoration: 'enabled',
      initialNavigation: 'enabledBlocking'
    }),
    provideClientHydration()
  ]
};

Practical tips:

  • Use loadComponent for lazy loading standalone components.
  • Enable scrollPositionRestoration to preserve scroll on back/forward.
  • provideClientHydration() rehydrates server-generated HTML seamlessly.

Express SSR Bridge

Express serves static assets and uses Angular’s CommonEngine for server-side rendering.

src/main.server.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app/app.config';

const serverConfig: ApplicationConfig = {
  providers: [provideServerRendering()]
};

const config = mergeApplicationConfig(appConfig, serverConfig);

export default () => bootstrapApplication(AppComponent, config);

server.ts

import express from 'express';
import { CommonEngine } from '@angular/ssr';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';

export function app(): express.Express {
  const server = express();
  const serverDist  = dirname(fileURLToPath(import.meta.url));
  const browserDist = resolve(serverDist, '../browser');
  const indexHtml   = join(serverDist, 'index.server.html');
  const engine      = new CommonEngine();

  server.set('views', browserDist);
  server.set('view engine', 'html');

  // Serve static assets
  server.get('*.*', express.static(browserDist, { maxAge: '1y' }));

  // SSR for all other routes
  server.get('*', (req, res, next) => {
    engine.render({
      bootstrap,
      documentFilePath: indexHtml,
      url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
      publicPath: browserDist,
      providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }]
    })
    .then(html => res.send(html))
    .catch(next);
  });

  return server;
}

// Start server
if (import.meta.url.endsWith('server.ts')) {
  const port = process.env.PORT || 4000;
  app().listen(port, () => console.log(`Listening on http://localhost:${port}`));
}

Workflow:

  1. npm run build:ssr → produces /browser, index.server.html, and server/main.js.
  2. npm run serve:ssr → launches Express SSR on port 4000.

Tailwind Styling Pipeline

Tailwind integrates via styles.css and customizes via tailwind.config.js.

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{html,ts}'],
  theme: {
    extend: {
      colors: {
        body: '#17171F',
        theme: '#3F3FFF',
        'selected-text': '#A3A3FF',
        secondary: '#9191A4',
        badge: '#3F3F51'
      },
      fontFamily: {
        poppins: ['Poppins', 'sans-serif']
      },
      keyframes: {
        colorChange: {
          '0%,100%': { color: '#FFFFFF' },
          '50%':     { color: '#3F3FFF' }
        }
      },
      animation: {
        colorPulse: 'colorChange 2s infinite'
      }
    }
  },
  plugins: []
};

src/styles.css

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

/* Global resets */
* { box-sizing: border-box; }
html, body { scroll-behavior: smooth; }

/* Custom scrollbar (utilities layer) */
@layer utilities {
  ::-webkit-scrollbar {
    width: 10px; height: 10px; background: transparent;
  }
  ::-webkit-scrollbar-thumb {
    background: #fff; border-radius: 8px;
  }
}

/* Reusable classes */
.secondary-title {
  @apply text-3xl font-bold mb-2;
}
.secondary-title::before {
  content: "";
  @apply block w-12 h-2 bg-theme rounded-full mb-2;
}
.section-paragraph {
  @apply text-secondary my-6 max-w-3xl;
}
.badge {
  @apply px-4 py-2 text-sm rounded bg-badge;
}

Usage tips:

  • Reference styles.css in angular.json under "styles".
  • Use classes like bg-theme, text-selected-text, and animate-colorPulse in templates.
  • Tailwind auto-purges unused CSS based on the content globs.

Customization Guide

Tailor your portfolio by editing component templates, assets, copy and theme settings. This guide covers projects, clients/experience lists, hero section, global styles and theming.


1. Editing Portfolio Projects

Projects live in work.component.ts as a typed array. The template in work.component.html dynamically renders each entry.

work.component.ts

import { Component } from '@angular/core';

interface Project {
  name: string;
  url: string;
  image: string;
}

@Component({
  selector: 'app-work',
  templateUrl: './work.component.html',
  styleUrls: ['./work.component.css']
})
export class WorkComponent {
  projects: Project[] = [
    { name: 'My Blog', url: 'https://blog.example.com', image: 'assets/blog.png' },
    { name: 'Shop App', url: 'https://shop.example.com', image: 'assets/shop.png' },
    // Add your project below
    { name: 'Portfolio Redesign', url: 'https://you.example.com', image: 'assets/portfolio.png' }
  ];
}

work.component.html

<section id="work" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
  <div *ngFor="let proj of projects" class="group relative overflow-hidden rounded-lg">
    <a [href]="proj.url" target="_blank" rel="noopener">
      <img [src]="proj.image" alt="{{ proj.name }}" class="w-full h-48 object-cover transition-transform duration-200 group-hover:scale-105">
      <div class="absolute bottom-0 left-0 w-full bg-black bg-opacity-60 text-white p-4">
        <h4 class="text-lg font-semibold">{{ proj.name }}</h4>
      </div>
    </a>
  </div>
</section>

Steps:

  1. Place new project screenshots in src/assets/.
  2. Add a { name, url, image } entry in projects.
  3. Verify image paths and run ng serve.

2. Updating Clients & Experience Lists

Both clients.component.html and experience.component.html use similar “card” markup. Copy, modify and repeat <article> blocks.

<div class="space-y-12 my-16">
  <!-- Existing entry -->
  <article class="flex flex-wrap lg:flex-nowrap border border-gray-700 p-8 lg:p-12">
    <figure class="mb-4 lg:mb-0 lg:mr-8">
      <img src="assets/bidsline.jpeg" alt="Bidsline Technology Logo" class="w-20 h-20">
      <figcaption class="sr-only">Bidsline Technology</figcaption>
    </figure>
    <div class="text-center lg:text-left">
      <h3 class="text-2xl text-white font-semibold">Bidsline Technology</h3>
      <div class="flex flex-wrap gap-3 mt-4">
        <span class="badge">Front-End Developer</span>
        <span class="badge">2025</span>
        <span class="badge">Current</span>
      </div>
    </div>
  </article>

  <!-- New entry -->
  <article class="flex flex-wrap lg:flex-nowrap border border-gray-700 p-8 lg:p-12">
    <figure class="mb-4 lg:mb-0 lg:mr-8">
      <img src="assets/newCompanyLogo.png" alt="New Company Inc. Logo" class="w-20 h-20">
      <figcaption class="sr-only">New Company Inc.</figcaption>
    </figure>
    <div class="text-center lg:text-left">
      <h3 class="text-2xl text-white font-semibold">New Company Inc.</h3>
      <div class="flex flex-wrap gap-3 mt-4">
        <span class="badge">UI/UX Designer</span>
        <span class="badge">2024–2025</span>
      </div>
    </div>
  </article>
</div>

Fields to update:

  • <img src> → your logo in src/assets/
  • <h3> → entity name
  • <span class="badge"> → role, dates, status

3. Customizing the Hero Section

Edit hero.component.html and optionally adjust scroll/download logic in hero.component.ts.

hero.component.html

<section id="hero" class="h-screen flex flex-col justify-center items-center bg-gradient-to-br from-blue-900 to-purple-800">
  <h1 class="text-5xl font-bold text-white mb-6">Welcome, I’m Jane Doe</h1>
  <p class="text-lg text-gray-200 mb-8">Front-End Developer & UI/UX Enthusiast</p>
  <div class="space-x-4">
    <button (click)="scrollTo('work')" class="btn-primary">View Work</button>
    <button (click)="downloadCV()" class="btn-secondary">Download CV</button>
  </div>
  <img src="assets/profile.jpg" alt="Jane Doe" class="w-48 h-48 rounded-full mt-12 border-4 border-white">
</section>

hero.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-hero',
  templateUrl: './hero.component.html'
})
export class HeroComponent {
  scrollTo(id: string) {
    document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
  }

  downloadCV() {
    const link = document.createElement('a');
    link.href = 'assets/Jane_Doe_CV.pdf';
    link.download = 'Jane_Doe_CV.pdf';
    link.click();
  }
}

Tips:

  • Change headline, subtitle, button labels.
  • Replace assets/profile.jpg and assets/Jane_Doe_CV.pdf.

4. Theming & Global Styles

tailwind.config.js

Adjust colors, fonts and animations:

module.exports = {
  content: ['./src/**/*.{html,ts}'],
  theme: {
    extend: {
      colors: {
        primary: '#3F3FFF',
        accent: '#FF4081'
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif']
      },
      animation: {
        pulseText: 'pulse 2s infinite'
      }
    }
  },
  plugins: []
};

src/styles.css

Global utilities and custom scrollbar:

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

@layer utilities {
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb { background: theme('colors.primary'); border-radius: 8px; }
}

/* Fallback for Firefox */
* {
  scrollbar-width: thin;
  scrollbar-color: theme('colors.primary') transparent;
}

Steps:

  1. Edit tailwind.config.js and rerun npm run build or ng build.
  2. Update colors or typography and rebuild CSS.

5. Asset Management

Store all images (logos, screenshots, profile, CV) in src/assets/. Reference them in your templates:

<img src="assets/your-image.png" alt="Description">

Ensure:

  • File names match exactly (case‐sensitive).
  • Routes in components point to assets/....

6. Responsive Design Tips

Use Tailwind breakpoints:

  • sm: for ≥640px
  • md: for ≥768px
  • lg: for ≥1024px
  • xl: for ≥1280px

Example: change grid columns on larger screens:

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  <!-- items -->
</div>

Follow these steps to fully customize text, images, layout and theme across your Angular portfolio.

Build & Deployment

This section covers producing optimized production bundles with SSR and prerendering, then deploying your Angular Universal app to Render, Vercel, or any Node host.

Configuring SSR and Prerender in angular.json

Enable Angular Universal Server-Side Rendering (SSR) and static prerendering via the Angular CLI.

angular.json excerpt

"projects": {
  "portfolio": {
    "architect": {
      "build": {
        "builder": "@angular-devkit/build-angular:application",
        "options": {
          "outputPath": "dist/portfolio",
          "index": "src/index.html",
          "browser": "src/main.ts",             // browser bundle entry
          "server": "src/main.server.ts",       // server bundle entry
          "polyfills": ["zone.js"],
          "tsConfig": "tsconfig.app.json",
          "assets": ["src/favicon.ico","src/assets"],
          "styles": ["src/styles.css"],
          "scripts": [],
          "prerender": true,                    // enable static prerender
          "ssr": {
            "entry": "server.ts"                // custom SSR launcher
          }
        },
        "configurations": {
          "production": { /* budgets, hashing… */ },
          "development": { /* sourcemaps, optimization… */ }
        },
        "defaultConfiguration": "production"
      }
    }
  }
}

How it works

  1. Browser & Server bundles
    ng build emits both browser static files and a server bundle (main.server.ts).
  2. Prerender
    With prerender: true, the builder imports server.ts, renders routes in its Routes array, and outputs static HTML under dist/portfolio/browser/.
  3. SSR entry
    server.ts boots your Express server or standalone SSR launcher.

Quickstart Commands

# Build & prerender (production)
npm run build

# Serve prerendered + SSR pages
npm run serve:ssr:portfolio

# Development watch mode (no SSR prerender)
npm run watch

Setting up the Express SSR Server (server.ts)

Provide a minimal Express server that renders Angular pages, serves static assets, and supports serverless wrappers.

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';

export function app(): express.Express {
  const server = express();

  const serverDist = dirname(fileURLToPath(import.meta.url));
  const browserDist = resolve(serverDist, '../browser');
  const indexHtml = join(serverDist, 'index.server.html');

  const engine = new CommonEngine();
  server.set('view engine', 'html');
  server.set('views', browserDist);

  // Static assets
  server.get('*.*', express.static(browserDist, { maxAge: '1y' }));

  // SSR render handler
  server.get('*', (req, res, next) => {
    engine.render({
      bootstrap,
      documentFilePath: indexHtml,
      url: `${req.protocol}://${req.headers.host}${req.originalUrl}`,
      publicPath: browserDist,
      providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }]
    })
    .then(html => res.send(html))
    .catch(next);
  });

  return server;
}

function run(): void {
  const port = process.env.PORT || 4000;
  app().listen(port, () => {
    console.log(`Server listening at http://localhost:${port}`);
  });
}

run();

Practical Tips

  • Custom APIs: Insert before the static-assets block:
    server.get('/api/users', (req, res) => {
      res.json([{ id: 1, name: 'Alice' }]);
    });
    
  • Caching: Adjust maxAge in express.static for fingerprinted assets.
  • Serverless: Import app() in your Function-as-a-Service handler without changes.

Deployment Guides

Render

Create render.yaml in your repo root:

services:
  - type: web
    name: portfolio-ssr
    buildCommand: npm ci && npm run build
    startCommand: npm run serve:ssr:portfolio
    env:
      - key: NODE_ENV
        value: production
    plan: free

Push to Render; it installs, builds, and starts your SSR server automatically.

Vercel

Add vercel.json to root:

{
  "version": 2,
  "builds": [
    { "src": "package.json", "use": "@vercel/static-build", "config": { "distDir": "dist/portfolio/browser" } },
    { "src": "dist/portfolio/server/server.mjs", "use": "@vercel/node" }
  ],
  "routes": [
    { "src": "/(.*\\..*)", "headers": { "cache-control": "public,max-age=31536000,immutable" }, "dest": "/$1" },
    { "src": "/(.*)", "dest": "dist/portfolio/server/server.mjs" }
  ]
}

In Vercel Dashboard, set Build Command to npm run build, Output Directory to dist/portfolio/browser.

Custom Node Host

  1. SSH into server
  2. Clone repo and install:
    git clone <repo-url>
    cd MohamedPortfolio
    npm ci
    
  3. Build and start:
    npm run build
    npm run serve:ssr:portfolio
    
  4. (Optional) Use PM2 for process management:
    npm install -g pm2
    pm2 start npm --name portfolio -- run serve:ssr:portfolio
    pm2 save
    

Development Guide

This guide covers contributing code, running unit tests, maintaining consistent code style, and extending the component set in the MohamedPortfolio Angular project.

1. Contributing Code

Keep your changes focused, reviewed, and easy to merge.

Setup and Workflow

  1. Fork the repository and clone your fork:
    git clone git@github.com:<your-user>/MohamedPortfolio.git
    cd MohamedPortfolio
    npm install
    
  2. Create a feature branch:
    git checkout -b feat/<short-description>
    
  3. Implement changes under src/app/:
    • Components: src/app/components
    • Services: src/app/services
    • Models: src/app/models
  4. Follow Angular commit conventions:
    feat(header): add responsive nav menu
    fix(contact): correct form validation logic
    docs(readme): update build instructions
    
  5. Push and open a Pull Request against main.

2. Running Unit Tests with Jasmine & Karma

Angular CLI wires up Jasmine/Karma via npm test and tsconfig.spec.json.

Key Configuration

package.json

"scripts": {
  "test": "ng test",
  // other scripts…
}

tsconfig.spec.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/spec",
    "types": ["jasmine"]
  },
  "include": [
    "src/**/*.spec.ts",
    "src/**/*.d.ts"
  ]
}

Common Commands

# Run all tests once (Chrome)
npm test

# Watch for changes
ng test --watch=true

# Generate a new spec file alongside a component
ng generate component components/example --skip-tests=false

Adding a New Test

Create src/app/components/my-widget/my-widget.component.spec.ts:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyWidgetComponent }     from './my-widget.component';

describe('MyWidgetComponent', () => {
  let fixture: ComponentFixture<MyWidgetComponent>;
  let component: MyWidgetComponent;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [MyWidgetComponent],
      // imports, providers, mocks…
    }).compileComponents();

    fixture   = TestBed.createComponent(MyWidgetComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Troubleshooting & Tips

  • Debug a single suite:
    ng test --include="**/my-widget.component.spec.ts"
    
  • Generate coverage report:
    1. Enable coverage reporter in karma.conf.js.
    2. Run npm test -- --code-coverage.
  • For CI, set headless mode in karma.conf.js:
    module.exports = {
      browsers: ['ChromeHeadless'],
      singleRun: true,
      // …
    };
    

3. Maintaining Code Style

Use the project’s .editorconfig to enforce consistent formatting across editors.

Excerpt from .editorconfig

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true

[*.ts]
quote_type = single

[*.md]
trim_trailing_whitespace = false
max_line_length = 120

Recommendations

  • Enable EditorConfig plugin in VS Code, WebStorm, etc.
  • Respect single quotes in .ts/.tsx files.
  • Do not trim trailing whitespace in Markdown to preserve formatting.
  • Run a linter or Prettier (if configured) before committing.

4. Extending the Component Set

Leverage Angular CLI scaffolding and module organization to add new features.

Generating a New Component

# Generates component in src/app/components/feedback/
ng generate component components/feedback \
  --flat=false \
  --style=scss \
  --skipTests=false

This creates:

src/app/components/feedback/
  feedback.component.ts
  feedback.component.html
  feedback.component.scss
  feedback.component.spec.ts

Registering in a Module

By default, the CLI adds the new component to AppModule. For feature modules:

  1. Generate a module if needed:
    ng generate module components/feedback --flat=false
    
  2. Move the component folder under the new module folder.
  3. In feedback.module.ts:
    import { NgModule }             from '@angular/core';
    import { CommonModule }         from '@angular/common';
    import { FeedbackComponent }    from './feedback.component';
    
    @NgModule({
      declarations: [FeedbackComponent],
      imports:      [CommonModule],
      exports:      [FeedbackComponent]
    })
    export class FeedbackModule { }
    
  4. Import FeedbackModule in app.module.ts or a parent feature module.

With these practices, contributions stay organized, tests remain reliable, code style stays uniform, and new components integrate smoothly into the MohamedPortfolio project.