Thursday, June 26, 2025

React: Context; Angular: @Injectable

Bootstrapping React
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { ThemeProvider } from './contexts/ThemeContext.tsx';

// Just for demo that context can reach even on children, grandchildren etc
function Main() {
  return <App></App>;
}

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <ThemeProvider>
      <Main />
    </ThemeProvider>
  </React.StrictMode>
);
Bootstrapping Angular
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>My app</title>
    <meta charset="UTF-8" />
  </head>
  <body>
    <main-root>Loading...</main-root>
  </body>
</html>
import 'zone.js';
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AsyncPipe, NgStyle } from '@angular/common';

import { App } from './app/app';

@Component({
  selector: 'main-root',
  standalone: true,
  imports: [AsyncPipe, NgStyle, App],
  template: `<app-root></app-root>`,
})
// Just for demo that service can reach even on children, grandchildren etc
export class Main {
  name = 'Angular';
}

bootstrapApplication(Main);
React's App component
import './App.css';
import { useTheme } from './contexts/ThemeContext';

export default function App() {
  // Destructuring make names lose context
  // const { theme, toggleTheme } = useTheme();

  // I feel explicit is better, so things won't appear as local
  const themeContext = useTheme();

  const name = 'App';

  return (
    <div
      style={{
        background: themeContext.theme === 'dark' ? '#222' : '#eee',
        color: themeContext.theme === 'dark' ? '#eee' : '#222',
        padding: '2rem',
      }}
    >
      <h1>Hello from {name}!</h1>
      <h3 onClick={themeContext.toggleTheme}>
        {themeContext.theme.toUpperCase()} MODE
      </h3>
      <a target="_blank" href="https://react.dev/">
        Learn more about React
      </a>
    </div>
  );
}
Angular's app-root component
import { Component } from '@angular/core';
import { ThemeContext } from './contexts/theme.context';
import { AsyncPipe, NgStyle } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.html',
  imports: [NgStyle, AsyncPipe],
})
export class App {
  name = 'App';

  // This works too, just seems complicated, and also it looks like local properties
  // theme$: Observable<Theme>;
  // toggleTheme: () => void;

  // This works too, but it looks like local properties
  // get theme$() { return this.themeContext.theme$; }
  // toggleTheme() { this.themeContext.toggleTheme(); }

  constructor(public themeContext: ThemeContext) {
    // This works too, just seems complicated, and also it looks like local properties
    // ({theme$: this.theme$, toggleTheme: this.toggleTheme } = themeContext);
    // this.toggleTheme = this.toggleTheme.bind(themeContext);
    //
    // This works too, just seems complicated, and also it looks like local properties
    // this.theme$ = this.themeContext.theme$;
    // this.toggleTheme = this.themeContext.toggleTheme.bind(themeContext);
  }
}
<div
  [ngStyle]="{
  background: (themeContext.theme$ | async) === 'dark' ? '#222' : '#eee',
  color: (themeContext.theme$ | async) === 'dark' ? '#eee' : '#222',
  padding: '2rem'
}"
>
  <h1>Hello from {{ name }}!</h1>
  <h3 (click)="themeContext.toggleTheme()">
    {{(themeContext.theme$ | async)?.toUpperCase()}} MODE
  </h3>
  <a target="_blank" href="https://angular.dev/overview">
    Learn more about Angular
  </a>
</div>
React Context:
import { createContext, ReactNode, useContext, useState } from 'react';

type Theme = 'light' | 'dark';

interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );

  function toggleTheme() {
    setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
  }
}

export function useTheme(): ThemeContextType {
  const context = useContext(ThemeContext);

  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }

  return context;
}
Angular @Injectable
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

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

@Injectable({
  providedIn: 'root',
})
export class ThemeContext {
  private themeSubject = new BehaviorSubject<Theme>('light');
  theme$ = this.themeSubject.asObservable();

  toggleTheme() {
    const newTheme = this.themeSubject.value === 'light' ? 'dark' : 'light';
    this.themeSubject.next(newTheme);
  }
}
React ContextAngular @Injectable

No comments:

Post a Comment