This is Conduit — a blogging platform similar to Medium. It is the official RealWorld demo app, built with Angular.
Users can:
- Browse a global feed of articles
- Filter articles by tag
- Register and log in
- Follow other users and see a personalized feed
- Write, edit, and delete articles (with tag support)
- Favorite/unfavorite articles
- Comment on articles
- View user profiles
- Update their own profile settings (avatar, bio, email, password)
AngularTest/
├── src/
│ └── app/
│ ├── app.component.ts # Root shell (header + router + footer)
│ ├── app.config.ts # App bootstrap config (HTTP, routing, auth init)
│ ├── app.routes.ts # Top-level route definitions
│ ├── core/ # App-wide infrastructure
│ │ ├── auth/ # Login/register page, auth directive, user model
│ │ │ └── services/ # JWT storage, user state management
│ │ ├── interceptors/ # HTTP pipeline (add base URL, token, error handling)
│ │ ├── layout/ # Header and footer components
│ │ └── models/ # Shared interfaces (errors, loading state)
│ ├── features/ # Feature modules
│ │ ├── article/ # Everything article-related
│ │ │ ├── components/ # Reusable article UI pieces
│ │ │ ├── models/ # Article, comment, list-config interfaces
│ │ │ ├── pages/ # Full-page views (home, article detail, editor)
│ │ │ └── services/ # API calls for articles, comments, tags
│ │ ├── profile/ # User profile feature
│ │ │ ├── components/ # Profile article/favorites lists, follow button
│ │ │ ├── models/ # Profile interface
│ │ │ ├── pages/ # Profile page
│ │ │ ├── profile.routes.ts # Profile-specific child routes
│ │ │ └── services/ # API calls for profiles (follow/unfollow)
│ │ └── settings/ # Settings page (single component)
│ └── shared/
│ ├── components/ # ListErrors (form validation display)
│ └── pipes/ # DefaultImage, Markdown rendering
├── realworld/ # Git submodule — shared CSS theme + SVG assets
├── e2e/ # Playwright end-to-end tests
├── angular.json # Angular CLI build config
├── package.json # Dependencies
└── tsconfig.json # TypeScript config
| Folder | Purpose |
|---|---|
core/ |
Things every part of the app needs: auth, HTTP setup, layout |
features/ |
Self-contained feature areas (articles, profiles, settings) |
shared/ |
Generic UI utilities reused across features |
realworld/ |
External CSS/assets submodule (not Angular code) |
- AppComponent — The outermost wrapper. Renders the header, the current page (via router), and the footer.
- HeaderComponent — Top navigation bar. Shows different links depending on whether the user is logged in.
- FooterComponent — Static footer with the app name and current year.
- AuthComponent — Handles both login and register on a single smart form. Detects which mode it's in from the URL (
/loginvs/register).
- HomeComponent — The main landing page. Shows tabs for "Global Feed", "Your Feed" (logged-in only), and tag-filtered feeds.
- ArticleListComponent — Reusable paginated list of articles. Accepts configuration to know which articles to fetch.
- ArticlePreviewComponent — A single article card (title, description, author, date, favorite button).
- ArticleComponent — Displays the full article body (rendered as Markdown), author info, and the comments section.
- ArticleMetaComponent — Shows author avatar, name, and date. Used in both the article card and detail page.
- ArticleCommentComponent — A single comment. Shows a delete button only if the comment belongs to the current user.
- FavoriteButtonComponent — A reusable heart button to like/unlike an article.
- EditorComponent — Create or edit an article. When a URL slug is present (
/editor/:slug), it loads the existing article for editing.
- ProfileComponent — A user's profile page. Shows their avatar, bio, and a follow button.
- ProfileArticlesComponent — Tab showing articles written by the user.
- ProfileFavoritesComponent — Tab showing articles the user has favorited.
- FollowButtonComponent — A reusable follow/unfollow button.
- SettingsComponent — Form for updating your own profile (avatar URL, username, bio, email, password). Also contains the logout button.
| URL | Page | Login Required? |
|---|---|---|
/ |
Home feed (global) | No |
/tag/:tag |
Home feed filtered by tag | No |
/login |
Login page | No (redirect away if already logged in) |
/register |
Register page | No (redirect away if already logged in) |
/settings |
User settings | Yes → redirects to /login |
/editor |
Create new article | Yes → redirects to /login |
/editor/:slug |
Edit existing article | Yes → redirects to /login |
/article/:slug |
Full article + comments | No |
/profile/:username |
User profile (their articles) | No |
/profile/:username/favorites |
User profile (their favorites) | No |
All pages are lazy-loaded — the browser only downloads the code for a page when the user navigates to it.
User Action (click/form submit)
↓
Component (signals for local state)
↓
Service (API call via HttpClient)
↓
HTTP Interceptors (add base URL → add auth token → catch errors)
↓
Backend API (https://api.realworld.show/api)
↓
Response flows back through Observable/Promise
↓
Component updates its signal state → UI re-renders
- apiInterceptor — Automatically prepends
https://api.realworld.show/apito every request. You never write the full URL in a service — just the path like/articles. - tokenInterceptor — Reads the saved JWT from
localStorageand adds it as anAuthorization: Token <jwt>header on every request. - errorInterceptor — If a 401 (unauthorized) response comes back, it automatically logs the user out. Normalizes all error responses into a consistent format.
App starts
↓
AppInitializer runs
↓
JWT found in localStorage?
├── Yes → GET /user to validate → set currentUser + authState = 'authenticated'
└── No → authState = 'unauthenticated'
Components subscribe to UserService.authState$ to react to login/logout events
UserService is the single source of truth for who is logged in. It uses a BehaviorSubject so any component can subscribe and always get the current value immediately.
The app uses no external state library (no Redux/NgRx). Instead:
- Angular Signals handle local component state (loading flags, form values, current article, etc.)
- RxJS BehaviorSubjects in
UserServicehandle the globally shared auth state - HTTP Observables from services carry data into components
- The combination of signals + zoneless change detection makes the app very efficient — Angular only re-renders exactly what changed
Backend: RealWorld API spec
Base URL: https://api.realworld.show/api
Auth: JWT token in Authorization: Token <jwt> header
JWT Storage: localStorage under the key jwtToken
| Feature | Method | Endpoint |
|---|---|---|
| Auth | POST | /users/login |
| POST | /users (register) |
|
| GET | /user (get current) |
|
| PUT | /user (update) |
|
| Articles | GET | /articles (global feed) |
| GET | /articles/feed (following feed) |
|
| GET | /articles/:slug |
|
| POST | /articles/ |
|
| PUT | /articles/:slug |
|
| DELETE | /articles/:slug |
|
| POST | /articles/:slug/favorite |
|
| DELETE | /articles/:slug/favorite |
|
| Comments | GET | /articles/:slug/comments |
| POST | /articles/:slug/comments |
|
| DELETE | /articles/:slug/comments/:commentId |
|
| Tags | GET | /tags |
| Profiles | GET | /profiles/:username |
| POST | /profiles/:username/follow |
|
| DELETE | /profiles/:username/follow |