├── .all-contributorsrc
├── .devcontainer
└── devcontainer.json
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ └── feature_request.md
├── .gitignore
├── .nvmrc
├── .prettierrc.cjs
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── CODE_OF_CONDUCT.md
├── README.md
├── astro.config.ts
├── license
├── package.json
├── pnpm-lock.yaml
├── public
├── favicon.svg
└── robots.txt
├── src
├── components
│ ├── BlogCard.astro
│ ├── BlogImage.astro
│ ├── Footer.astro
│ ├── Header.astro
│ └── TagsLine.astro
├── env.d.ts
├── icons
│ └── angular.svg
├── images
│ ├── angular.jpg
│ ├── minimal-angular-project.png
│ └── source-map.jpg
├── layouts
│ ├── BlogLayout.astro
│ └── Layout.astro
├── pages
│ ├── 404.astro
│ ├── index.astro
│ ├── models.ts
│ ├── rss.xml.ts
│ ├── snippets.astro
│ └── snippets
│ │ ├── component-store-with-global-state.mdx
│ │ ├── create-minimal-angular-project-for-research-purpose.mdx
│ │ ├── disable-components-using-directive.mdx
│ │ ├── enable-tracing.mdx
│ │ ├── guard-class-to-function.mdx
│ │ ├── observables-testing.mdx
│ │ ├── overriding-npm-dependencies.mdx
│ │ ├── preloading-strategy.mdx
│ │ ├── put-constraint-on-selector.mdx
│ │ ├── server-sent-event-with-component-store.mdx
│ │ ├── signal-reactivity-cheatsheet.mdx
│ │ ├── source-map.mdx
│ │ ├── stateful-directive-with-output.mdx
│ │ ├── stateful-standalone-component.mdx
│ │ ├── v15-new-image-directive.mdx
│ │ └── v15-standalone.mdx
└── scripts
│ └── main.js
├── tailwind.config.ts
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "commitConvention": "angular",
8 | "contributors": [
9 | {
10 | "login": "olierxleben",
11 | "name": "Oliver Erxleben",
12 | "avatar_url": "https://avatars.githubusercontent.com/u/1403225?v=4",
13 | "profile": "https://github.com/olierxleben",
14 | "contributions": [
15 | "code"
16 | ]
17 | },
18 | {
19 | "login": "9kubczas4",
20 | "name": "Paweł Kubiak",
21 | "avatar_url": "https://avatars.githubusercontent.com/u/43759569?v=4",
22 | "profile": "https://github.com/9kubczas4",
23 | "contributions": [
24 | "code"
25 | ]
26 | },
27 | {
28 | "login": "SantoshYadavDev",
29 | "name": "Santosh Yadav",
30 | "avatar_url": "https://avatars.githubusercontent.com/u/11923975?v=4",
31 | "profile": "https://github.com/santoshyadavdev",
32 | "contributions": [
33 | "code"
34 | ]
35 | },
36 | {
37 | "login": "Nicoss54",
38 | "name": "Nicolas Frizzarin",
39 | "avatar_url": "https://avatars.githubusercontent.com/u/24563545?v=4",
40 | "profile": "https://github.com/Nicoss54",
41 | "contributions": [
42 | "doc"
43 | ]
44 | },
45 | {
46 | "login": "yharaskrik",
47 | "name": "Jay Bell",
48 | "avatar_url": "https://avatars.githubusercontent.com/u/9469090?v=4",
49 | "profile": "http://trellis.org",
50 | "contributions": [
51 | "doc"
52 | ]
53 | },
54 | {
55 | "login": "wasylb",
56 | "name": "Bartosz Wasilew",
57 | "avatar_url": "https://avatars.githubusercontent.com/u/43531815?v=4",
58 | "profile": "https://github.com/wasylb",
59 | "contributions": [
60 | "doc"
61 | ]
62 | },
63 | {
64 | "login": "goetzrobin",
65 | "name": "Robin Goetz",
66 | "avatar_url": "https://avatars.githubusercontent.com/u/35136007?v=4",
67 | "profile": "https://goetzrobin.github.io",
68 | "contributions": [
69 | "code"
70 | ]
71 | },
72 | {
73 | "login": "nartc",
74 | "name": "Chau Tran",
75 | "avatar_url": "https://avatars.githubusercontent.com/u/25516557?v=4",
76 | "profile": "https://nartc.me",
77 | "contributions": [
78 | "code",
79 | "doc"
80 | ]
81 | },
82 | {
83 | "login": "phhien203",
84 | "name": "Pham Huu Hien",
85 | "avatar_url": "https://avatars.githubusercontent.com/u/8808535?v=4",
86 | "profile": "https://blog.hien.page",
87 | "contributions": [
88 | "doc"
89 | ]
90 | },
91 | {
92 | "login": "nelsongutidev",
93 | "name": "Nelson Gutierrez",
94 | "avatar_url": "https://avatars.githubusercontent.com/u/62297014?v=4",
95 | "profile": "https://nelsonguti.dev/",
96 | "contributions": [
97 | "doc"
98 | ]
99 | },
100 | {
101 | "login": "lucioaimar",
102 | "name": "Lucio Aimar",
103 | "avatar_url": "https://avatars.githubusercontent.com/u/64326713?v=4",
104 | "profile": "https://github.com/lucioaimar",
105 | "contributions": [
106 | "doc"
107 | ]
108 | },
109 | {
110 | "login": "deepakrudrapaul",
111 | "name": "Deepak Rudra Paul",
112 | "avatar_url": "https://avatars.githubusercontent.com/u/25549935?v=4",
113 | "profile": "https://github.com/deepakrudrapaul",
114 | "contributions": [
115 | "doc"
116 | ]
117 | },
118 | {
119 | "login": "rubenperegrina",
120 | "name": "Rubén Peregrina",
121 | "avatar_url": "https://avatars.githubusercontent.com/u/23550574?v=4",
122 | "profile": "https://github.com/rubenperegrina",
123 | "contributions": [
124 | "doc"
125 | ]
126 | }
127 | ],
128 | "contributorsPerLine": 7,
129 | "skipCi": true,
130 | "repoType": "github",
131 | "repoHost": "https://github.com",
132 | "projectName": "angular-snippets",
133 | "projectOwner": "santoshyadavdev"
134 | }
135 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
3 | {
4 | "name": "Node.js",
5 | "image": "mcr.microsoft.com/devcontainers/javascript-node:16-bullseye"
6 |
7 | // Features to add to the dev container. More info: https://containers.dev/features.
8 | // "features": {},
9 |
10 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
11 | // "forwardPorts": [],
12 |
13 | // Use 'postCreateCommand' to run commands after the container is created.
14 | // "postCreateCommand": "yarn install",
15 |
16 | // Configure tool-specific properties.
17 | // "customizations": {},
18 |
19 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
20 | // "remoteUser": "root"
21 | }
22 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [SantoshYadavDev]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | .output/
4 |
5 | # dependencies
6 | node_modules/
7 |
8 | # logs
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 |
14 | # environment variables
15 | .env
16 | .env.production
17 |
18 | # macOS-specific files
19 | .DS_Store
20 |
21 | # we use pnpm for package management
22 | package-lock.json
23 | yarn.lock
24 |
25 | # other folders
26 | .vercel
27 | .netlify
28 | .idea
29 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
--------------------------------------------------------------------------------
/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require.resolve("prettier-plugin-astro")],
3 | overrides: [
4 | {
5 | files: "*.astro",
6 | options: {
7 | parser: "astro",
8 | // ensure HTML tag breaks properly
9 | htmlWhitespaceSensitivity: "ignore",
10 | },
11 | },
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "astro-build.astro-vscode",
4 | "esbenp.prettier-vscode",
5 | "unifiedjs.vscode-mdx"
6 | ],
7 | "unwantedRecommendations": []
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "deno.enable": true,
3 | "deno.enablePaths": ["netlify/edge-functions"],
4 | "deno.unstable": true,
5 | "deno.importMap": ".netlify/edge-functions-import-map.json",
6 | "deno.path": "/home/codespace/.config/netlify/deno-cli/deno",
7 | "prettier.documentSelectors": ["**/*.astro"],
8 | "[astro]": {
9 | "editor.defaultFormatter": "esbenp.prettier-vscode"
10 | },
11 | "[mdx]": {
12 | "editor.defaultFormatter": "esbenp.prettier-vscode"
13 | },
14 | "editor.formatOnSave": true,
15 | "editor.formatOnSaveMode": "file",
16 | "editor.codeActionsOnSave": {
17 | "source.organizeImports": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | santosh.yadav198613@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Snippets
2 |
3 |
4 | [](#contributors-)
5 |
6 |
7 | A website to find and share code snippets for Angular.
8 |
9 | ## How to add snippets
10 |
11 | Follow the below steps to add a snippet to the website.
12 |
13 | - Go to pages -> snippets folder.
14 | - add a new mdx file with the name of the snippet.
15 | - add the content in the format below.
16 |
17 | ```mdx
18 | ---
19 | title: Tile of the snippet
20 | description: description of the snippet
21 | pubDate: (Dare) Feb 20, 2022
22 | contributedBy: "@TwitterHandle"
23 | ---
24 |
25 | import BlogImage from "@components/BlogImage.astro";
26 | import SourceMap from "@images/source-map.jpg";
27 |
28 | Content of the snippet
29 |
30 | We accept mdx syntax here.
31 |
32 | You can include code snippets like this.
33 |
34 | "sourceMap": {
35 | "hidden": true,
36 | }
37 |
38 | and images like this.
39 |
40 |
41 | ```
42 |
43 | ## Tech Stack 🛠️
44 |
45 | - Static Site Generator: [Astro](https://astro.build/)
46 | - CSS Framework: [Tailwind CSS](https://tailwindcss.com/)
47 | - Markdown: [MDX](https://mdxjs.com/)
48 | - Package Manager: [pnpm](https://pnpm.io/)
49 |
50 | ## Developer Notes 📝
51 |
52 | Here are some commands that you can run to get started with the project. You can also check all the commands in the [`package.json`](https://github.com/lancerossdev/basicblog/blob/main/package.json)
53 |
54 | ```bash
55 | # Install dependencies
56 | pnpm install
57 |
58 | # Run the development server
59 | pnpm dev
60 |
61 | # Build the project
62 | pnpm build
63 |
64 | # Serve the built project
65 | pnpm preview
66 | ```
67 |
68 | ## Contributors
69 |
70 |
71 |
72 |
73 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | ## License ⚖️
108 |
109 | This repository has [MIT License](https://github.com/santoshyadavdev/angular-snipptes/blob/main/license).
110 |
--------------------------------------------------------------------------------
/astro.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "astro/config";
2 | import compress from "astro-compress";
3 | import sitemap from "@astrojs/sitemap";
4 | import tailwind from "@astrojs/tailwind";
5 | import mdx from "@astrojs/mdx";
6 | import image from "@astrojs/image";
7 | import astroLayouts from "astro-layouts";
8 | import codeTitle from "remark-code-title";
9 |
10 | // https://astro.build/config
11 | export default defineConfig({
12 | site: "https://angular-snippets.dev",
13 | markdown: {
14 | extendDefaultPlugins: true,
15 | shikiConfig: {
16 | theme: "dark-plus",
17 | },
18 | remarkPlugins: [
19 | [
20 | astroLayouts,
21 | {
22 | default: "@layouts/Layout.astro",
23 | snippets: "@layouts/BlogLayout.astro",
24 | },
25 | ],
26 | codeTitle,
27 | ],
28 | },
29 |
30 | base: "/",
31 | integrations: [
32 | compress({
33 | css: true,
34 | html: true,
35 | js: true,
36 | img: true,
37 | svg: true,
38 | logger: 0,
39 | }),
40 | tailwind(),
41 | sitemap(),
42 | mdx(),
43 | image({
44 | serviceEntryPoint: "@astrojs/image/sharp",
45 | }),
46 | ],
47 | });
48 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Santosh Yadav
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-snippets",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "astro dev",
7 | "build": "astro build",
8 | "preview": "astro build && astro preview",
9 | "prettier": "prettier --write --plugin-search-dir=. ."
10 | },
11 | "devDependencies": {
12 | "@astrojs/image": "^0.17.3",
13 | "@astrojs/mdx": "~0.19.6",
14 | "@astrojs/rss": "^1.2.1",
15 | "@astrojs/sitemap": "^1.0.0",
16 | "@astrojs/tailwind": "^4.0.0",
17 | "@fontsource/fira-code": "^4.5.12",
18 | "@fontsource/inter": "^4.5.14",
19 | "@tailwindcss/typography": "^0.5.8",
20 | "astro": "^2.10.11",
21 | "astro-compress": "2.0.12",
22 | "astro-icon": "^0.8.0",
23 | "astro-layouts": "^0.0.6",
24 | "prettier": "^2.8.4",
25 | "prettier-plugin-astro": "^0.8.0",
26 | "remark-code-title": "^0.2.2",
27 | "sharp": "^0.31.2",
28 | "tailwind-scrollbar": "^2.0.1",
29 | "tailwindcss": "^3.2.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
4 | Sitemap: https://angular-snippets.dev/sitemap-index.xml
--------------------------------------------------------------------------------
/src/components/BlogCard.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import TagsLine from "./TagsLine.astro";
3 |
4 | export interface Props {
5 | title: string;
6 | description: string;
7 | pubDate: string;
8 | url: string | undefined;
9 | contributedBy: string;
10 | tags: string[];
11 | }
12 | let { title, description, pubDate, url, contributedBy, tags } = Astro.props;
13 | const shortDate = new Date(pubDate).toLocaleDateString("en", {
14 | dateStyle: "short",
15 | });
16 | url = url + "/";
17 | ---
18 |
19 |
22 |
23 |
24 |
25 | {title}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/BlogImage.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Picture } from "@astrojs/image/components";
3 | interface Props {
4 | src: string;
5 | alt: string;
6 | loading?: "lazy" | "eager";
7 | }
8 | const { src, alt, loading = "lazy" } = Astro.props;
9 | ---
10 |
11 |
19 |
--------------------------------------------------------------------------------
/src/components/Footer.astro:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/Header.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { Icon } from "astro-icon";
3 |
4 | const navItems = [
5 | {
6 | name: "Snippets",
7 | href: "/snippets/",
8 | },
9 | {
10 | name: "RSS",
11 | href: "/rss.xml",
12 | },
13 | ];
14 | ---
15 |
16 |
87 |
88 |
106 |
--------------------------------------------------------------------------------
/src/components/TagsLine.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const { tags } = Astro.props;
3 | ---
4 |
5 |
6 | {
7 | tags.map((tag) => (
8 |
9 | {tag}
10 |
11 | ))
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/icons/angular.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
--------------------------------------------------------------------------------
/src/images/angular.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santoshyadavdev/angular-snippets/1b8d2c9509e4395e4b884060346a024250a427b1/src/images/angular.jpg
--------------------------------------------------------------------------------
/src/images/minimal-angular-project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santoshyadavdev/angular-snippets/1b8d2c9509e4395e4b884060346a024250a427b1/src/images/minimal-angular-project.png
--------------------------------------------------------------------------------
/src/images/source-map.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santoshyadavdev/angular-snippets/1b8d2c9509e4395e4b884060346a024250a427b1/src/images/source-map.jpg
--------------------------------------------------------------------------------
/src/layouts/BlogLayout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import "@fontsource/inter/variable.css";
3 | import "@fontsource/fira-code";
4 | import Header from "@components/Header.astro";
5 | import Footer from "@components/Footer.astro";
6 | import TagsLine from "@components/TagsLine.astro";
7 |
8 | let { pubDate } = Astro.props.content;
9 | const { frontmatter } = Astro.props;
10 | export interface Props {
11 | title?: string;
12 | description?: string;
13 | image?: string;
14 | ImageAlt?: string;
15 | }
16 |
17 | const thisDate = new Date(pubDate).toLocaleDateString("en", {
18 | dateStyle: "long",
19 | });
20 | pubDate = pubDate.split("T")[0];
21 |
22 | const ogImageParams = "./banner.png";
23 |
24 | const canonicalURL = new URL(Astro.url).href;
25 | ---
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {frontmatter.title}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
78 |
79 |
80 |
83 |
84 |
85 |
86 |
87 |
88 | {frontmatter.title}
89 |
90 |
100 |
101 |
102 |
103 |
104 |
107 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/src/layouts/Layout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import "@fontsource/inter/variable.css";
3 | import Header from "@components/Header.astro";
4 | import Footer from "@components/Footer.astro";
5 |
6 | export interface Props {
7 | title?: string;
8 | description?: string;
9 | }
10 |
11 | const { title, description } = Astro.props as Props;
12 | const canonicalURL = new URL(Astro.url).href;
13 | ---
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {title}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/pages/404.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "@layouts/Layout.astro";
3 | import { Icon } from "astro-icon";
4 | ---
5 |
6 |
7 |
8 |
9 | 404 Error
10 | Are you lost or looking for some easter eggs?
11 |
15 | Go back to home
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "@layouts/Layout.astro";
3 | import BlogCard from "@components/BlogCard.astro";
4 | import { Icon } from "astro-icon";
5 | import { Frontmatter } from "./models";
6 |
7 | const metadata = {
8 | title: "Angular Snippets",
9 | description: "A collection of Angular snippets",
10 | };
11 |
12 | let blogs = await Astro.glob("./snippets/*.mdx");
13 | blogs = blogs.sort(
14 | (a, b) =>
15 | new Date(b.frontmatter.pubDate).valueOf() -
16 | new Date(a.frontmatter.pubDate).valueOf()
17 | );
18 | ---
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
31 | Welcome to Angular Snippets, add a snippet and help the community
32 |
33 |
34 | There are some code snippets which you may have created and want to
35 | share with the world!
36 |
37 |
38 | This is the place to share your code snippets with the world. You can
39 | add your own snippets and help the community.
40 |
41 |
42 |
43 |
44 | Recent Snippets
45 |
46 | Looking for all snippets? Click the "snippets" or the "Show more snippets"
47 | link down below.
48 |
49 |
50 | {
51 | blogs.length >= 1 &&
52 | blogs
53 | .slice(0, 2)
54 | .map((post) => (
55 |
63 | ))
64 | }
65 |
66 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/pages/models.ts:
--------------------------------------------------------------------------------
1 | export interface Frontmatter {
2 | title: string;
3 | pubDate: string;
4 | description: string;
5 | contributedBy: string;
6 | tags?: string[];
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/rss.xml.ts:
--------------------------------------------------------------------------------
1 | import rss from "@astrojs/rss";
2 |
3 | export const get = () =>
4 | rss({
5 | title: "Angular Snippets",
6 | description: "Find and share Angualr Snippets.",
7 | site: import.meta.env.SITE,
8 | items: import.meta.glob("./snippets/**.mdx"),
9 | });
10 |
--------------------------------------------------------------------------------
/src/pages/snippets.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "@layouts/Layout.astro";
3 | import BlogCard from "@components/BlogCard.astro";
4 | import { Frontmatter } from "./models";
5 |
6 | let blogs = await Astro.glob("./snippets/*.mdx");
7 | blogs = blogs.sort(
8 | (a, b) =>
9 | new Date(b.frontmatter.pubDate).valueOf() -
10 | new Date(a.frontmatter.pubDate).valueOf()
11 | );
12 | ---
13 |
14 |
15 |
16 | Snippets
17 |
18 | Find and share useful code snippets for Angular. Snippets are small,
19 | reusable code that you can use to solve common problems.
20 |
21 |
22 |
23 |
24 | {
25 | blogs.map((post) => {
26 | return (
27 |
35 | );
36 | })
37 | }
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/pages/snippets/component-store-with-global-state.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Global NgRx State and NgRx Component Store
3 | description: "Pull in global state and use in Component Store"
4 | tags: ["angular", "ngrx", "state"]
5 | pubDate: Feb 25, 2023
6 | contributedBy: "@JayCooperBell"
7 | ---
8 |
9 | An example of a simple global NgRx Store that is used in a ComponentStore that reacts to global state changes and manipulates
10 | the rest.
11 |
12 | Set up a global NgRx Store with Effects.
13 |
14 | ```typescript
15 | import { createAction, createFeature, createReducer, on } from "@ngrx/store";
16 | import { createEffect } from "@ngrx/effects";
17 | import { interval, map } from "rxjs";
18 |
19 | interface CounterState {
20 | counter: number;
21 | }
22 |
23 | const initialCounterState: CounterState = {
24 | counter: 0,
25 | };
26 |
27 | export const incrementCounter = createAction(
28 | "[Angular Snippets Example] Increment Counter"
29 | );
30 |
31 | export const counterState = createFeature({
32 | name: "counter",
33 | reducer: createReducer(
34 | initialCounterState,
35 | on(incrementCounter, (state) => ({
36 | ...state,
37 | counter: state.counter + 1,
38 | }))
39 | ),
40 | });
41 |
42 | export const { selectCounter } = counterState;
43 |
44 | export const incrementCounterEffect = createEffect(
45 | () => interval(1000).pipe(map(() => incrementCounter())),
46 | { functional: true }
47 | );
48 | ```
49 |
50 | Add your global state to the application
51 |
52 | ```typescript
53 | import { bootstrapApplication } from "@angular/platform-browser";
54 | import { AppComponent } from "./app/app.component";
55 | import { provideState, provideStore } from "@ngrx/store";
56 | import {
57 | counterState,
58 | incrementCounterEffect,
59 | } from "./app/global-state-in-component-store";
60 | import { provideEffects } from "@ngrx/effects";
61 |
62 | bootstrapApplication(AppComponent, {
63 | providers: [
64 | provideStore({}),
65 | provideState(counterState),
66 | provideEffects({ incrementCounterEffect }),
67 | ],
68 | }).catch((err) => console.error(err));
69 | ```
70 |
71 | Pull the global state into a component store
72 |
73 | ```typescript
74 | import { Component, inject, Injectable } from "@angular/core";
75 | import { ComponentStore } from "@ngrx/component-store";
76 | import { Store } from "@ngrx/store";
77 | import { Observable, tap } from "rxjs";
78 | import { AsyncPipe } from "@angular/common";
79 |
80 | interface MyState {
81 | counter: number;
82 | }
83 |
84 | @Injectable({ providedIn: "root" })
85 | export class ComponentStoreWithGlobalState extends ComponentStore {
86 | readonly counter$ = this.select((state) => state.counter);
87 |
88 | private readonly store = inject(Store);
89 |
90 | private readonly setCounter = this.updater((state, counter: number) => ({
91 | ...state,
92 | counter,
93 | }));
94 |
95 | private readonly multiplyGlobalStateByTwo = this.effect(
96 | (origin$: Observable) =>
97 | origin$.pipe(tap((counter) => this.setCounter(counter * 2)))
98 | );
99 |
100 | constructor() {
101 | super({ counter: 0 });
102 |
103 | this.multiplyGlobalStateByTwo(this.store.select(selectCounter));
104 | }
105 | }
106 |
107 | @Component({
108 | selector: "component-store-with-global-state",
109 | standalone: true,
110 | providers: [ComponentStoreWithGlobalState],
111 | imports: [AsyncPipe],
112 | template: ` {{ counter$ | async }}
`,
113 | })
114 | export class ComponentStoreWithGlobalStateComponent {
115 | readonly store = inject(ComponentStoreWithGlobalState);
116 |
117 | readonly counter$ = this.store.counter$;
118 | }
119 | ```
120 |
121 | Use the component
122 |
123 | ```typescript
124 | import { Component } from "@angular/core";
125 | import { ComponentStoreWithGlobalStateComponent } from "./global-state-in-component-store";
126 |
127 | @Component({
128 | standalone: true,
129 | selector: "angular-snippet-examples-root",
130 | template: `
131 |
132 | `,
133 | styles: [],
134 | imports: [ComponentStoreWithGlobalStateComponent],
135 | })
136 | export class AppComponent {}
137 | ```
138 |
--------------------------------------------------------------------------------
/src/pages/snippets/create-minimal-angular-project-for-research-purpose.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Create minimal Angular project for researching purpose
3 | description: "Simple command line to create an Angular project with bare minimum of files for researching the framework purpose"
4 | tags: ["angular"]
5 | pubDate: Mar 2, 2023
6 | contributedBy: "@HienHuuPham"
7 | ---
8 |
9 | import BlogImage from "@components/BlogImage.astro";
10 | import MinimalProject from "@images/minimal-angular-project.png";
11 |
12 | Do you want to create a new Angular project with bare minimum of files for researching Angular framework?
13 |
14 | Just use this command line and you are good to go.
15 |
16 | ```bash
17 | ng new learn-pipe --minimal --defaults
18 | ```
19 |
20 | - `--minimal` is the short-hand for `--inline-template --inline-style --skip-tests`
21 | - `--defaults` is the short-hand for `--style=css --router=false`
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/pages/snippets/disable-components-using-directive.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Disable components using directive
3 | description: "Example of directive to disable interactive components when a certain condition is met."
4 | tags: ["angular", "directives"]
5 | pubDate: Feb 25, 2023
6 | contributedBy: "@pawelkubiakdev"
7 | ---
8 |
9 | The code snippet below shows how to disable (angular material) components if the shouldDisable$ stream from the ExampleService emits true.
10 | This can be a useful solution when we want to block actions when the user does not have the appropriate permissions resulting from:
11 |
12 | - user role
13 | - a feature that is blocked for the user etc.
14 |
15 | (\*) `DestroyedDirective` was implemented according to the idea of [Kristiyan Kostadinov](https://twitter.com/_crisbeto/status/1582475442715385858).
16 |
17 | ```typescript
18 | @Directive({
19 | selector: "[disableInteractiveElements]",
20 | standalone: true,
21 | hostDirectives: [DestroyedDirective],
22 | })
23 | export class DisableInteractiveElementsDirective implements OnInit {
24 | private readonly destroyed$ = inject(DestroyedDirective).destroyed$;
25 |
26 | constructor(
27 | private readonly service: ExampleService,
28 | private readonly elementRef: ElementRef,
29 | @Optional() @Self() private readonly button: MatButton,
30 | @Optional() @Self() private readonly select: MatSelect
31 | ) {}
32 |
33 | ngOnInit(): void {
34 | this.service.shouldDisable$
35 | .pipe(takeUntil(this.destroyed$))
36 | .subscribe((shouldDisable) => {
37 | if (this.button) {
38 | this.button.disabled = shouldDisable;
39 | } else if (this.select) {
40 | this.select.disabled = shouldDisable;
41 | } else if (
42 | this.elementRef.nativeElement &&
43 | "disabled" in this.elementRef.nativeElement
44 | ) {
45 | this.elementRef.nativeElement.disabled = shouldDisable;
46 | }
47 | });
48 | }
49 | }
50 | ```
51 |
52 | Usage in template:
53 |
54 | ```html
55 |
56 |
59 |
60 | Setting 1
61 | Setting 2
62 |
63 |
64 | ```
65 |
66 | In the case shown above, the first button will always be active, while the other elements, i.e. the second button, select and input, will be active if `service.shouldDisable$` emits false.
67 |
--------------------------------------------------------------------------------
/src/pages/snippets/enable-tracing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Debugging router events
3 | description: "How to add enableTracing and withDebugTracing()"
4 | tags: ["angular", "router"]
5 | pubDate: Mar 3, 2023
6 | contributedBy: "@nelsongutidev"
7 | ---
8 |
9 | For debugging purposes around router events, Angular provides an easy way to log all internal navigation events to the console
10 |
11 | ```typescript
12 | const routes: Routes = [];
13 |
14 | @NgModule({
15 | import: [RouterModule.forRoot(routes, { enableTracing: true })],
16 | exports: [RouterModule],
17 | })
18 | export class AppRoutingModule {}
19 | ```
20 |
21 | If your not using an NgModule to bootstrap your app, you can use the `withDebugTracing()` method
22 |
23 | ```typescript
24 | const appRoutes: Routes = [];
25 | bootstrapApplication(AppComponent, {
26 | providers: [provideRouter(appRoutes, withDebugTracing())],
27 | });
28 | ```
29 |
--------------------------------------------------------------------------------
/src/pages/snippets/guard-class-to-function.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Migration Class Guard to Function Guard
3 | description: "Easy example on how to migrate a class guard in a function guard"
4 | tags: ["angular14", "functional guard"]
5 | pubDate: Feb 24, 2023
6 | contributedBy: "@Nicoss54"
7 | ---
8 |
9 | Here is simple example of how to translate a class guard into a functional guard.
10 | Since version 14 of angular, and especially thanks to the possibility to use the inject function outside the injection context. It is now possible to write our Guards as a simple function.
11 | With the arrival of Angular 15.2, writing guard as a class is now deprecated.
12 |
13 | ```typescript
14 | import { Injectable } from "@angular/core";
15 | import { Router, type CanActivate } from "@angular/router";
16 | import { UserService } from "./userService";
17 |
18 | @Injectable({ provideIn: "root" })
19 | export class AuthenticationGuard implements CanActivate {
20 | constructor(
21 | private readonly userService: UserService,
22 | private readonly router: Router
23 | ) {}
24 |
25 | canActivate(): Observable {
26 | return this.userService.currentUser$.pipe(
27 | map((user) =>
28 | Boolean(user) ? true : this.router.createUrlTree(["sign-in"])
29 | )
30 | );
31 | }
32 | }
33 | ```
34 |
35 | In previous versions of Angular, this syntax was mandatory and particularly useful for injecting our services.
36 |
37 | Thanks to the inject function, this class can be easily transformed into a function.
38 |
39 | ```typescript
40 | import { Router, type CanActivateFn } from "@angular/router";
41 | import { UserService } from "./userService";
42 |
43 | export function AuthenticationGuard(): CanActivateFn {
44 | const userService = inject(UserService);
45 | const router = inject(Router);
46 |
47 | return userService.currentUser$.pipe(
48 | map((user) => (Boolean(user) ? true : router.createUrlTree(["sign-in"])))
49 | );
50 | }
51 | ```
52 |
53 | The way to register your guard on a path has not changed; whether the guard is in the form of a class or a simple function the syntax of registration has not changed.
54 |
55 | ```typescript
56 | import type Routes from '@angular/router';
57 | import { HomeComponent } from './home.component';
58 | import { SignInComponent } from './sign-in.component';
59 | import { AuthenticationGuard } from './authentication-guard'
60 |
61 | const routes: Routes = [
62 | { path: 'sign-in', component: SignInComponent }
63 | { path: 'home', component: HomeComponent, canActivate: [AuthenticationGuard]}
64 | ]
65 | ```
66 |
--------------------------------------------------------------------------------
/src/pages/snippets/observables-testing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Different ways of observables' testing
3 | description: "Unwrap & test your observables in different ways depending on a case"
4 | tags: ["angular", "rxjs", "jest", "testing"]
5 | pubDate: Feb 25, 2023
6 | contributedBy: "@bartosz_wasilew"
7 | ---
8 |
9 | Let's assume we have some `UserListComponent` exposing public stream `users$` (typeof User):
10 |
11 | ```typescript
12 | type User = {
13 | id: number;
14 | name: string;
15 | email: string;
16 | };
17 | ```
18 |
19 | In case we'd like to test it, for this example we'll need the following setup:
20 |
21 | ```typescript
22 | const usersMock: User[] = [
23 | { id: 1, name: "Johny Bravo", email: "johny.bravo@xoxo.com" },
24 | { id: 2, name: "React dev", email: "ihatemyjob@really.io" }, // Heey I'm just kidding, Angular 🤝 React
25 | { id: 3, name: "10x Dev", email: "10xdev@hotmail.com" },
26 | ];
27 |
28 | const userListComponent = {
29 | users$: of(usersMock),
30 | };
31 | ```
32 |
33 | Then you aren't limited only to use `subscribe()` on this Observable, you can pick different ways depending on your case:
34 |
35 | ```typescript
36 | it("done func", (done: DoneCallback) => {
37 | userListComponent.users$.subscribe((expectedUsers: User[]) => {
38 | expect(expectedUsers).toStrictEqual(usersMock);
39 | done();
40 | });
41 | });
42 |
43 | it("first value from", async () => {
44 | const expectedUsers: User[] = await firstValueFrom(userListComponent.users$);
45 | expect(expectedUsers).toStrictEqual(usersMock);
46 | });
47 |
48 | it("Fake async & tick", fakeAsync(() => {
49 | let expectedUsers;
50 | userListComponent.users$.subscribe((users: User[]) =>
51 | setTimeout(() => {
52 | expectedUsers = users;
53 | }, 2000)
54 | );
55 | tick(2100);
56 | expect(expectedUsers).toStrictEqual(usersMock);
57 | }));
58 |
59 | it("marbles", () => {
60 | const testScheduler = new TestScheduler((actual, expected) => {
61 | expect(actual).toStrictEqual(expected);
62 | });
63 | testScheduler.run(({ expectObservable }) => {
64 | expectObservable(userListComponent.users$).toBe("(a|)", {
65 | a: usersMock,
66 | });
67 | });
68 | });
69 | ```
70 |
--------------------------------------------------------------------------------
/src/pages/snippets/overriding-npm-dependencies.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overriding NPM deps
3 | description: "Don't wait/block your work till some NPM package's released, override npm dep & treat it as a temporary solution"
4 | tags: ["angular", "npm"]
5 | pubDate: Mar 03, 2023
6 | contributedBy: "@bartosz_wasilew"
7 | ---
8 |
9 | Let's suppose we have to update our Angular version i.e. for security reasons / new features.
10 | However, some of your libraries can still use outdated Angular version.
11 |
12 | As an example we'll consider the following Angular (already updated) modules' versions:
13 |
14 | my-app => `package.json`
15 | ```json
16 | "dependencies": {
17 | "@angular/common": "^14.2.0",
18 | "@angular/core": "^14.2.0",
19 | ...
20 | ...
21 | }
22 | ```
23 |
24 | While our lib contains constraints to have the following versions:
25 |
26 | my-awesome-lib => `package.json`
27 | ```json
28 | "peerDependencies": {
29 | "@angular/common": "^13.3.12",
30 | "@angular/core": "^13.3.12"
31 | }
32 | ```
33 |
34 | In order to deal with this problem we can:
35 | 1. Wait & block our work till our `my-awesome-lib` supports Angular components in v.`^14.2.0` 👎
36 | 2. `Override` these deps => enforce using versions we provide for the lib 🤩
37 |
38 | In order to override our `my-awesome-lib` we'll need to include `overrides` object, with following possible configurations:
39 |
40 | ```json
41 | "overrides": {
42 | "my-awesome-lib": {
43 | "@angular/common": "14.2.0",
44 | "@angular/core": "14.2.0"
45 | }
46 | }
47 | ```
48 |
49 | ```json
50 | "overrides": {
51 | "my-awesome-lib": {
52 | "@angular/common": "^14.2.0",
53 | "@angular/core": "^14.2.0"
54 | }
55 | }
56 | ```
57 |
58 | ```json
59 | "overrides": {
60 | "my-awesome-lib": {
61 | "@angular/common": "$@angular/common",
62 | "@angular/core": "$@angular/core"
63 | }
64 | }
65 | ```
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/pages/snippets/preloading-strategy.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Preloading strategy
3 | description: "How to implement a module preloading strategy"
4 | tags: ["angular", "modules", "preloading", "router"]
5 | pubDate: Mar 8, 2023
6 | contributedBy: "@rubenperegrina"
7 | ---
8 |
9 | If we have our application separated by modules and we apply lazy loading.
10 | Angular by default will load the modules as the user needs them.
11 | There are several ways to control the loading of modules.
12 |
13 | ### Lazy loading
14 |
15 | Modules will be loaded as the user requires them.
16 |
17 | ```typescript
18 | const routes: Routes = [
19 | {
20 | path: "contact",
21 | loadChildren: () =>
22 | import("./contact/contact.module").then((m) => m.ContactModule),
23 | },
24 | {
25 | path: "about",
26 | loadChildren: () =>
27 | import("./about/about.module").then((m) => m.AboutModule),
28 | },
29 | ];
30 |
31 | @NgModule({
32 | imports: [RouterModule.forRoot(routes)],
33 | exports: [RouterModule],
34 | })
35 | export class AppRoutingModule {}
36 |
37 | //StandAlone API version
38 |
39 | //Lazy loading another routing config
40 | export const routes: Routes = [
41 | {
42 | path: "contact",
43 | loadChildren: () =>
44 | import("./contact/contact.routes").then((m) => m.CONTACT_ROUTES),
45 | },
46 | {
47 | path: "about",
48 | loadChildren: () =>
49 | import("./about/about.routes").then((m) => m.ABOUT_ROUTES),
50 | },
51 | ];
52 |
53 | //Directly lazy loading a standalone component
54 | export const routes: Routes = [
55 | {
56 | path: "contact",
57 | loadComponent: () =>
58 | import("./contact/contact.component").then((m) => m.ContactComponent),
59 | },
60 | {
61 | path: "about",
62 | loadComponent: () =>
63 | import("./about/about.component").then((m) => m.AboutComponent),
64 | },
65 | ];
66 |
67 | //In your main.ts add:
68 | bootstrapApplication(AppComponent, {
69 | providers: [provideRouter(routes)],
70 | });
71 | ```
72 |
73 | ### Preload all the modules
74 |
75 | In this way, all the modules of our application will be loaded at once and ready to use.
76 |
77 | ```typescript
78 | @NgModule({
79 | imports: [
80 | RouterModule.forRoot(routes, {
81 | preloadingStrategy: PreloadAllModules,
82 | }),
83 | ],
84 | exports: [RouterModule],
85 | })
86 | export class AppRoutingModule {}
87 |
88 | //StandAlone API version
89 | //In your main.ts add:
90 | bootstrapApplication(AppComponent, {
91 | providers: [provideRouter(routes, withPreloading(PreloadAllModules))],
92 | });
93 | ```
94 |
95 | ### Customised module preloading
96 |
97 | We can choose which modules to load first, so the most used modules will already be loaded.
98 |
99 | ```typescript
100 | @Injectable({
101 | providedIn:'root'
102 | })
103 | export class PreloadingStrategyService implements PreloadingStrategy {
104 | private preloadedModules: string[] = [];
105 |
106 | preload(route: Route, load: () => Observable): Observable {
107 | if (route.data && route.data['preload'] && route.path) {
108 | this.preloadedModules.push(route.path);
109 | return load();
110 | } else {
111 | return of(null);
112 | }
113 | }
114 | }
115 |
116 | const routes: Routes = [
117 | {
118 | path: "contact",
119 | loadChildren: () =>
120 | import("./contact/contact.module").then((m) => m.ContactModule),
121 | },
122 | {
123 | path: "about",
124 | loadChildren: () =>
125 | import("./about/about.module").then((m) => m.AboutModule),
126 | data: { preload: true },
127 | },
128 | ];
129 |
130 | @NgModule({
131 | imports: [
132 | RouterModule.forRoot(routes, {
133 | preloadingStrategy: PreloadingStrategyService,
134 | }),
135 | ],
136 | exports: [RouterModule],
137 | })
138 | export class AppRoutingModule {}
139 |
140 | //StandAlone API version
141 | //In your main.ts add:
142 | bootstrapApplication(AppComponent, {
143 | providers: [provideRouter(routes, withPreloading(PreloadingStrategyService))],
144 | });
145 | ```
146 |
147 | ### Using ngx-quicklink
148 |
149 | ngx-quicklink is a library that loads modules as their link appears on screen.
150 | To install the library: `npm i ngx-quicklink`
151 |
152 | ```typescript
153 | import { QuicklinkStrategy } from "ngx-quicklink";
154 |
155 | @NgModule({
156 | imports: [
157 | RouterModule.forRoot(routes, {
158 | preloadingStrategy: QuicklinkStrategy,
159 | }),
160 | ],
161 | exports: [RouterModule],
162 | })
163 | export class AppRoutingModule {}
164 |
165 | //StandAlone API version
166 | //In your main.ts add:
167 | import { quicklinkProviders, QuicklinkStrategy } from 'ngx-quicklink';
168 |
169 | bootstrapApplication(AppComponent, {
170 | providers: [
171 | provideRouter(routes, withPreloading(QuicklinkStrategy)),
172 | quicklinkProviders
173 | ]
174 | })
175 |
176 | // Import the QuicklinkDirective in all your standalone components that use preloading:
177 | import { RouterModule } from '@angular/router';
178 | import { QuicklinkDirective } from 'ngx-quicklink';
179 |
180 | @Component({
181 | standalone: true,
182 | imports: [RouterModule, QuicklinkDirective],
183 | template: `
184 | Form
185 | `,
186 | })
187 | ```
188 |
--------------------------------------------------------------------------------
/src/pages/snippets/put-constraint-on-selector.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Putting constraints on selectors
3 | description: "Making our components and directives more robust by putting constraints on their selectors"
4 | tags: ["angular"]
5 | pubDate: Mar 02, 2023
6 | contributedBy: "@Nartc1410"
7 | ---
8 |
9 | `Component#selector` and `Directive#selector` are just like CSS Selector. This means we can utilize some CSS pseudo-class
10 | to put constraints on our `selector`, making them more robust and provide better DX to our consumers
11 |
12 | Suppose we have the following `NgxLilGui` component that wraps the `lil-gui` library.
13 |
14 | > To learn more about `lil-gui`, visit [https://lil-gui.georgealways.com/](https://lil-gui.georgealways.com/)
15 |
16 | ```ts
17 | @Component({
18 | selector: "ngx-lil-gui",
19 | template: ` `,
20 | standalone: true,
21 | })
22 | export class NgxLilGui implements OnInit, OnDestroy {
23 | @Input() config: Config;
24 | @Input() object: Record;
25 |
26 | /* ... */
27 | }
28 | ```
29 |
30 | To make it flexible for the consumers, we allow the consumers to use `config` OR `object` to control the `lil-gui` library.
31 | In addition, the consumers can also use `` without passing in anything. In this case, `` acts as a
32 | group of other ``
33 |
34 | ```html
35 |
36 | This is a span
37 |
38 |
39 |
40 |
41 |
42 |
43 | ```
44 |
45 | However, there's nothing to stop the consumers to use both `[config]` and `[object]` on ``
46 |
47 | ```html
48 |
49 | ```
50 |
51 | To apply this constraint, we can modify our selector as follow:
52 |
53 | ```ts
54 | @Component({
55 | selector: `
56 | ngx-lil-gui:not([config]):not([object]),
57 | ngx-lil-gui[config]:not([object]),
58 | ngx-lil-gui[object]:not([config])
59 | `,
60 | /* ... */
61 | })
62 | export class NgxLilGui implements OnInit, OnDestroy {
63 | /* ... */
64 | }
65 | ```
66 |
67 | Now, the consumers can instantiate `NgxLilGui` component by using ``, ``,
68 | or `` but never ``
69 |
--------------------------------------------------------------------------------
/src/pages/snippets/server-sent-event-with-component-store.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Wrap Server Sent Events (SSE) in Component Store
3 | description: "Set up an SSE listener and store the values in ComponentStore"
4 | tags: ["angular", "ngrx", "state", "sse"]
5 | pubDate: Feb 25, 2023
6 | contributedBy: "@JayCooperBell"
7 | ---
8 |
9 | Demo of using Server-Sent Events (SSE) with Angular
10 |
11 | An SSE is sent every second with the current time to the Angular app that has a persistent EventSource connection to the server.
12 | A NgRx ComponentStore sets up a connection that updates the store with the time from the server.
13 |
14 | This example assumes you have an API setup that sends SSE. For a full stack example see https://github.com/yharaskrik/nest-ng-ngrx-component-store-sse-demo
15 |
16 | Set up a global NgRx Store with Effects.
17 |
18 | ```typescript
19 | import { Injectable, NgZone } from "@angular/core";
20 | import { ComponentStore, tapResponse } from "@ngrx/component-store";
21 | import { Observable, Subject } from "rxjs";
22 |
23 | function eventSourceToObservable(
24 | source: string,
25 | _ngZone: NgZone
26 | ): Observable<{ now: string }> {
27 | /*
28 | * Set up a Subject we will use as a pass through for the EventSource that is listening to SSE to emit
29 | * to the NgRx ComponentStore effect
30 | */
31 | const passThrough = new Subject<{ now: string }>();
32 |
33 | /*
34 | * The EventSource that opens a persistent connection to the server to listen for Server Sent Events (Sse)
35 | */
36 | const eventSource = new EventSource(`/api/${source}`);
37 |
38 | eventSource.onmessage = (e) =>
39 | /*
40 | * Need to run the `passThrough.next` inside of Zone so that Angular knows it has occurred and can make sure that the
41 | * UI is updated.
42 | *
43 | * Reference: https://blog.octo.com/en/angular-2-sse-and-changes-detection/
44 | */
45 | _ngZone.run(() => passThrough.next(JSON.parse(e.data)));
46 |
47 | return passThrough;
48 | }
49 |
50 | export interface TimeSseState {
51 | now: string;
52 | }
53 |
54 | @Injectable()
55 | export class TimeSseStore extends ComponentStore {
56 | readonly now$ = this.select((state) => state.now);
57 |
58 | readonly setNow = this.updater((state, now: string) => ({ ...state, now }));
59 |
60 | constructor(private _ngZone: NgZone) {
61 | super({ now: new Date().toISOString() });
62 | }
63 |
64 | /*
65 | * An NgRx ComponentStore effect that will listen on our PassThrough EventSource and update the state with the
66 | * date sent from the server.
67 | */
68 | readonly getTime$ = this.effect(() =>
69 | eventSourceToObservable("time", this._ngZone).pipe(
70 | tapResponse(
71 | (data) => {
72 | this.setNow(data.now);
73 | },
74 | (error) => console.error(error)
75 | )
76 | )
77 | );
78 | }
79 | ```
80 |
81 | References:
82 |
83 | 1. https://docs.nestjs.com/techniques/server-sent-events
84 | 2. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
85 | 3. https://ngrx.io/guide/component-store/effect
86 |
--------------------------------------------------------------------------------
/src/pages/snippets/signal-reactivity-cheatsheet.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Signal Reactivity Cheat sheet
3 | description: "A cheat sheet about Signal Primary Reactivity"
4 | tags: ["angular16", "signal reactivity"]
5 | pubDate: Feb 27, 2023
6 | contributedBy: "@Nicoss54"
7 | ---
8 |
9 | Until now, Angular used Zone JS to perform change detection and therefore to refresh the view.
10 |
11 | Change detection works by detecting common browser events like mouse clicks, HTTP requests, and other types of events, and deciding if the view of each component needs to be updated or not.
12 | By default, Angular uses the _ChangeDetectionStrategy.Default_ change detection strategy which checks every component in the component tree from the top to the bottom every time a common browser event is emitted.
13 | This way of checking don't take into account the components' dependencies and is called dirty checking. This can negatively influence your application performance specially for large application with many components.
14 |
15 | With the release of Angular version 16, a new system of primitive reactivity will be created: Signal Reactivity which don't need zone js to trigger a refresh of a view.
16 |
17 | The goal of this snippet is to give you a cheat sheet about signal reactivity and give you keys to be ready to work with them.
18 |
19 | ### Create a signal
20 |
21 | Create a signal is very easy, it's just calling a function
22 |
23 | ```typescript
24 | const counter = signal(0); // create a signal with initial value to 0;
25 | ```
26 |
27 | #### Equality Function
28 |
29 | By default, `signal()` uses a default `ValueEqualityFn`.
30 |
31 | - For primitive values, `Object.is()` is utilized
32 | - For reference values (object and array), they are always considered _unequal_
33 |
34 | We can also provide a custom `ValueEqualityFn` to `signal()` for custom compare logic on Signal's update
35 |
36 | ```ts
37 | const users = signal([], (a, b) => userCompareFn(a, b));
38 | ```
39 |
40 | ### Update a signal
41 |
42 | The signal function return a SettableSignal. This gives you some amazing possibilities
43 |
44 | ```typescript
45 | const todos = signal([]);
46 |
47 | todos.set(["Make My dinner"]); // directly set the signal to a new value and notify all the dependents
48 |
49 | todos.update((currentTodos) => [
50 | ...currentTodos,
51 | "Create a new Angular Snippet",
52 | ]); // update the value of the signal based on its current value and notify all dependents
53 |
54 | // NOTE: `mutate` will by-pass equality check and will always notify change
55 | todos.mutate((currentTodos) => {
56 | currentTodos.push("Participate to Open Source");
57 | }); // update the currentValue by mutating it in-place and notify all the dependents
58 | ```
59 |
60 | ### Get the value of a signal
61 |
62 | The way to get the value of a signal will be the same in the template as in the component.
63 |
64 | ```typescript
65 | const counter = signal(0); // create a signal with initial value
66 | console.log(counter()); // print the value of the signal which is 0
67 | ```
68 |
69 | ### Computed
70 |
71 | `computed()` creates a memoizing signal, which calculates its value from the values of some number of input signals.
72 |
73 | ```typescript
74 | const person = signal<{ firstname: string; lastname: string }>({
75 | firstname: "John",
76 | lastname: "Doe",
77 | });
78 |
79 | // Automatically updates when person() change;
80 | const presentation = computed(() => {
81 | const fullName = `${person().firstname} ${person().lastname}`;
82 | console.log(fullName);
83 | return fullName;
84 | });
85 |
86 | person.update((person) => ({ firstname: "Angular", lastname: "Snippet" }));
87 | ```
88 |
89 | ### Effect
90 |
91 | `effect()` schedules and runs a side-effectful function inside a reactive context. Signal dependencies of this function are captured, and the side effect is re-executed whenever any of its dependencies produces a new value.
92 |
93 | ```typescript
94 | const idTodo = signal(12);
95 |
96 | effect(
97 | () =>
98 | fetch(`https://awesome-todo.com/todos/${idTodo()}`)
99 | .then((response) => response.json())
100 | .then(console.log) // will sent the request with idTodo() = 12
101 | );
102 | ```
103 |
--------------------------------------------------------------------------------
/src/pages/snippets/source-map.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Make source maps available for reporting tools
3 | description: "Make source maps available for reporting tools"
4 | tags: [json, sourceMaps]
5 | pubDate: Feb 20, 2022
6 | contributedBy: "@SantoshYadavDev"
7 | ---
8 |
9 | import BlogImage from "@components/BlogImage.astro";
10 | import SourceMap from "@images/source-map.jpg";
11 |
12 | Do you want to upload source-map to an reporting tool like
13 | @datadoghq
14 | , but want to make sure no one can debug the code due to sourceMap
15 |
16 | ---
17 |
18 | Set below property in build options, the source-map will not be mapped to the bundle, but you will get a source map.
19 |
20 | ```json
21 | "sourceMap": {
22 | "hidden": true,
23 | }
24 | ```
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/pages/snippets/stateful-directive-with-output.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: NgRx Component Store Stateful Directive with Output
3 | description: "Example of using Component store in a directive to output state changes"
4 | tags: ["angular", "ngrx", "state"]
5 | pubDate: Feb 25, 2023
6 | contributedBy: "@JayCooperBell"
7 | ---
8 |
9 | Using NgRx Component Store to add state to a Directive which then outputs that state to the parent.
10 |
11 | ```typescript
12 | import { Directive, Output } from "@angular/core";
13 | import { ComponentStore } from "@ngrx/component-store";
14 | import { interval, Observable, tap } from "rxjs";
15 |
16 | interface MyState {
17 | counter: number;
18 | }
19 |
20 | @Directive({
21 | selector: "[myStatefulDirective]",
22 | standalone: true,
23 | })
24 | export class StatefulDirectiveWithOutputsDirective extends ComponentStore {
25 | @Output() counterChange = this.select((state) => state.counter);
26 |
27 | private readonly incrementCounter = this.updater((state) => ({
28 | ...state,
29 | counter: state.counter + 1,
30 | }));
31 |
32 | private readonly incrementByInterval = this.effect(
33 | (origin$: Observable) =>
34 | origin$.pipe(tap(() => this.incrementCounter()))
35 | );
36 |
37 | constructor() {
38 | super({
39 | counter: 0,
40 | });
41 |
42 | this.incrementByInterval(interval(1000));
43 | }
44 | }
45 | ```
46 |
47 | And then in your parent component you can use it like this:
48 |
49 | ```typescript
50 | import { Component } from "@angular/core";
51 | import { StatefulDirectiveWithOutputsDirective } from "./stateful-directive-with-outputs.directive";
52 |
53 | @Component({
54 | standalone: true,
55 | selector: "angular-snippet-examples-root",
56 | template: `
57 |
58 | {{ counter }}
59 |
60 | `,
61 | imports: [StatefulDirectiveWithOutputsDirective],
62 | })
63 | export class AppComponent {
64 | counter: number | null = null;
65 | }
66 | ```
67 |
--------------------------------------------------------------------------------
/src/pages/snippets/stateful-standalone-component.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: NgRx Component Store Stateful Standalone Component
3 | description: "Example of using Component store to add state to a component"
4 | tags: ["angular", "ngrx", "state"]
5 | pubDate: Feb 25, 2023
6 | contributedBy: "@JayCooperBell"
7 | ---
8 |
9 | Using NgRx Component Store to add state to a Component without an additional service.
10 |
11 | ```typescript
12 | import { Component } from "@angular/core";
13 | import { ComponentStore } from "@ngrx/component-store";
14 | import { interval, Observable, tap } from "rxjs";
15 | import { AsyncPipe } from "@angular/common";
16 |
17 | interface MyState {
18 | counter: number;
19 | }
20 |
21 | @Component({
22 | selector: "my-stateful-component",
23 | standalone: true,
24 | template: ` {{ counter$ | async }}
`,
25 | imports: [AsyncPipe],
26 | })
27 | export class MyStatefulComponentComponent extends ComponentStore {
28 | readonly counter$ = this.select((state) => state.counter);
29 |
30 | private readonly incrementCounter = this.updater((state) => ({
31 | ...state,
32 | counter: state.counter + 1,
33 | }));
34 |
35 | private readonly incrementByInterval = this.effect(
36 | (origin$: Observable) =>
37 | origin$.pipe(tap(() => this.incrementCounter()))
38 | );
39 |
40 | constructor() {
41 | super({
42 | counter: 0,
43 | });
44 |
45 | this.incrementByInterval(interval(1000));
46 | }
47 | }
48 | ```
49 |
--------------------------------------------------------------------------------
/src/pages/snippets/v15-new-image-directive.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: New Image Directive in Angular v15
3 | description: "Example of New Image Directive in Angular 15 "
4 | tags: ["angular15", "image directive"] # comma separated list which gets trimmed
5 | pubDate: Mar 5, 2023
6 | contributedBy: "@deepakrudrapaul"
7 | ---
8 |
9 | Here is a simple example of how to use the _NgOptimizedImage_ directive.
10 | The _NgOptimizedImage_ directive makes it easy to adopt performance best practices for loading images.
11 |
12 | ### Import NgOptimizedImage
13 |
14 | ```typescript
15 | import { NgOptimizedImage } from "@angular/common";
16 | ```
17 |
18 | ### Enable the directive
19 |
20 | To enable the NgOptimizedImage directive, replace your image's src attribute with _ngSrc_ and to prevent image related layout shifts,
21 | NgOptimizedImage requires that you specify a height and width for the image
22 |
23 | ```html
24 |
25 | ```
26 |
27 | And if you don't know the size of the images, you can use the _fill_ attribute to inherit the size of the parent container.
28 | For the "fill" image to render properly, its parent element must be styled with
29 | `position: "relative", position: "fixed", or position: "absolute".`
30 |
31 | ```html
32 |
33 | ```
34 |
35 | ---
36 |
--------------------------------------------------------------------------------
/src/pages/snippets/v15-standalone.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Angular 15 Standalone Components
3 | description: "Easy example of Angular 15 standalone components in existing projects"
4 | tags: ["angular15", "standalone components"] # comma separated list which gets trimmed
5 | pubDate: Feb 22, 2022
6 | contributedBy: "@olierxleben"
7 | ---
8 |
9 | Here is a quick example of how to use a standalone component in an existing NgModule. It demonstrates the interoperability and recent innovation of Angular v15.
10 | Let`s start with the component itself.
11 |
12 | ```typescript
13 | import { Component } from "@angular/core";
14 |
15 | @Component({
16 | selector: "fancy-button",
17 | standalone: true, // magic lies here
18 |
19 | template: `
20 |
25 | `,
26 | })
27 | export class ButtonComponent {}
28 | ```
29 |
30 | ---
31 |
32 | And for the integration in an NgModule, you can just do the following:
33 |
34 | ```typescript
35 | import {ButtonComponent} from "my-fancy-ui";
36 | ...
37 | @NgModule({
38 | declarations: [AppComponent],
39 | imports: [
40 | BrowserModule,
41 | HttpClientModule, RouterModule.forRoot(routes), ButtonComponent], // import like a module
42 | providers: [],
43 | bootstrap: [AppComponent],
44 | })
45 | export class AppModule {
46 | }
47 | ```
48 |
--------------------------------------------------------------------------------
/src/scripts/main.js:
--------------------------------------------------------------------------------
1 | const themeBtns = document.querySelectorAll(".theme-toggle");
2 | const htmlClassList = document.documentElement.classList;
3 | themeBtns.forEach((btn) =>
4 | btn.addEventListener("click", function () {
5 | if (htmlClassList.contains("dark")) {
6 | localStorage.setItem("theme", "light");
7 | htmlClassList.remove("dark");
8 | } else {
9 | localStorage.setItem("theme", "dark");
10 | htmlClassList.add("dark");
11 | }
12 | })
13 | );
14 |
15 | const navBtn = document.querySelector(".nav-toggle");
16 | const navMenu = document.querySelector(".nav-menu");
17 | navBtn.addEventListener("click", function () {
18 | navMenu.classList.toggle("hidden");
19 | });
20 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | const defaultTheme = require("tailwindcss/defaultTheme");
3 | const colors = require("tailwindcss/colors");
4 |
5 | module.exports = {
6 | darkMode: "class",
7 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
8 | theme: {
9 | extend: {
10 | typography: {
11 | DEFAULT: {
12 | css: {
13 | "code::before": {
14 | content: '""',
15 | },
16 | "code::after": {
17 | content: '""',
18 | },
19 | code: {
20 | fontFamily: "Fira Code, monospace",
21 | span: {
22 | fontFamily: "Fira Code, monospace",
23 | },
24 | },
25 | "div[data-remark-code-title]": {
26 | backgroundColor: colors.neutral[800],
27 | fontFamily: "Fira Code, monospace",
28 | fontSize: "0.8rem",
29 | padding: "0.5rem 1rem",
30 | borderTopRightRadius: "0.25rem",
31 | borderTopLeftRadius: "0.25rem",
32 | transitionDuration: "150ms",
33 | },
34 | "div[data-remark-code-title] + pre": {
35 | marginTop: "0 !important",
36 | borderTopRightRadius: "0 !important",
37 | borderTopLeftRadius: "0 !important",
38 | transitionDuration: "150ms",
39 | },
40 | "p, li": {
41 | code: {
42 | backgroundColor: "#27272a",
43 | padding: "0.250rem 0.4rem",
44 | borderRadius: "0.250rem",
45 | fontWeight: "300",
46 | color: "white",
47 | transitionProperty: "color, background-color",
48 | transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
49 | transitionDuration: "150ms",
50 | },
51 | },
52 | "html:not(.dark) div[data-remark-code-title]": {
53 | backgroundColor: colors.zinc[200],
54 | color: colors.black,
55 | transitionDuration: "150ms",
56 | },
57 | "html:not(.dark) p, li": {
58 | code: {
59 | backgroundColor: colors.zinc[200],
60 | padding: "0.250rem 0.4rem",
61 | borderRadius: "0.250rem",
62 | fontWeight: "300",
63 | color: "black",
64 | transitionProperty: "color, background-color",
65 | transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
66 | transitionDuration: "150ms",
67 | },
68 | },
69 | },
70 | },
71 | },
72 | fontFamily: {
73 | sans: ["InterVariable", "Inter", ...defaultTheme.fontFamily.sans],
74 | },
75 | },
76 | },
77 | plugins: [require("@tailwindcss/typography"), require("tailwind-scrollbar")],
78 | };
79 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "types": ["astro/images/client"],
4 | "baseUrl": ".",
5 | "paths": {
6 | "@components/*": ["src/components/*"],
7 | "@layouts/*": ["src/layouts/*"],
8 | "@content/*": ["src/content/*"],
9 | "@images/*": ["src/images/*"],
10 | "@scripts/*": ["src/scripts/*"]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------