Additions/52 lazy load component (#963)

* feat: create challenge 52 on lazy load component

Initial boilerplate from `nx g @angular-challenges/cli:challenge`

* feat: revert to NgModule

Copy of 31

* feat: initial version of starting point

* feat: add documentation

* feat: pr tweaks

Add author file
Remove thomas from the contributor list
Add a more direct hint

* feat: pr tweaks

Update "Go to the latest Challenge" link
This commit is contained in:
Lance Finney
2024-05-27 10:27:15 -05:00
committed by GitHub
parent a07954fa73
commit c8ba057fe0
29 changed files with 397 additions and 20 deletions

View File

@@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f
## Challenges
Check [all 51 challenges](https://angular-challenges.vercel.app/)
Check [all 52 challenges](https://angular-challenges.vercel.app/)
## Contributors ✨

View File

@@ -0,0 +1,36 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}

View File

@@ -0,0 +1,13 @@
# lazy-load-component
> author: thomas-laforge
### Run Application
```bash
npx nx serve angular-lazy-load-component
```
### Documentation and Instruction
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/angular/52-lazy-load-component/).

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'angular-lazy-load-component',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/apps/angular/52-lazy-load-component',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

View File

@@ -0,0 +1,79 @@
{
"name": "angular-lazy-load-component",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/angular/52-lazy-load-component/src",
"tags": [],
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/angular/52-lazy-load-component",
"index": "apps/angular/52-lazy-load-component/src/index.html",
"browser": "apps/angular/52-lazy-load-component/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "apps/angular/52-lazy-load-component/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"apps/angular/52-lazy-load-component/src/favicon.ico",
"apps/angular/52-lazy-load-component/src/assets"
],
"styles": ["apps/angular/52-lazy-load-component/src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "angular-lazy-load-component:build:production"
},
"development": {
"buildTarget": "angular-lazy-load-component:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "angular-lazy-load-component:build"
}
},
"lint": {
"executor": "@nx/eslint:lint"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/angular/52-lazy-load-component/jest.config.ts"
}
}
}
}

View File

@@ -0,0 +1,22 @@
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="h-screen bg-gray-500">
@if (topLoaded()) {
<app-top />
} @else {
<app-placeholder />
<button
class="rounded-sm border border-blue-500 bg-blue-300 p-2"
(click)="topLoaded.set(true)">
Load Top
</button>
}
</div>
`,
})
export class AppComponent {
topLoaded = signal(false);
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { PlaceholderComponent } from './placeholder.component';
import { TopComponent } from './top.component';
@NgModule({
declarations: [AppComponent, PlaceholderComponent, TopComponent],
imports: [BrowserModule],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-placeholder',
template: `
I'm a placeholder component.
`,
styles: `
:host {
display: grid;
padding: 20px;
background-color: #f0f0f0;
height: 50%;
}
`,
})
export class PlaceholderComponent {}

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-top',
template: `
I am a very heavy, expensive component that should be lazy loaded.
`,
styles: `
:host {
display: grid;
padding: 20px;
background-color: #f0f0f0;
height: 50%;
}
`,
})
export class TopComponent {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>angular-lazy-load-component</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -0,0 +1,6 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));

View File

@@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* You can add global styles to this file, and also import other style files */

View File

@@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'jest-preset-angular/setup-jest';

View File

@@ -0,0 +1,14 @@
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"],
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {},
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.editor.json"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node", "@testing-library/jest-dom"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@@ -1,6 +1,6 @@
{
"total": 51,
"🟢": 20,
"total": 52,
"🟢": 21,
"🟠": 121,
"🔴": 209
}

View File

@@ -0,0 +1,6 @@
{
"name": "Lance Finney",
"twitter": "https://twitter.com/LMFinneyCoder",
"linkedin": "https://www.linkedin.com/in/lmfinney/",
"github": "https://github.com/LMFinney"
}

View File

@@ -0,0 +1,49 @@
---
title: 🟢 Lazy Load a Component
description: Challenge 52 is about understanding how to lazy load a component in Angular.
author: lance-finney
contributors:
- LMFinney
challengeNumber: 52
command: angular-lazy-load-component
sidebar:
order: 21
badge: New
---
## Information
Angular has long had route-based lazy loading for entire modules, but lazy loading individual components was much more complicated. This challenge is about understanding how to lazy load a component easily with a feature that was introduced in Angular 17.
## Statement
This is a simple application that can display a `TopComponent` that we are pretending would slow the application down if it were part of the initial bundle (it actually contains just a bit of text, but we are pretending).
The current implementation shows a `PlaceholderComponent` until the user clicks a button to display the `TopComponent`. However, even though the `TopComponent` isn't visible until the button is clicked, it is still loaded as part of the initial bundle.
Use a new feature of Angular 17 to lazy load the `TopComponent` so that it is visible _and loaded_ when the user clicks the button to display it.
When you are done, you should be able to see the `TopComponent` being loaded into the browser in a separate bundle when you click the button to display it. In Chrome, you should see this by opening the DevTools, going to the Network tab, and then clicking the button to display the `TopComponent`.
## Hints
<details>
<summary>Hint 1</summary>
You should be able to remove the `topLoaded` signal when you are done.
</details>
<details>
<summary>Hint 2</summary>
The new Angular feature will hide the `TopComponent` from view, but it will still be loaded in the initial bundle unless you change how both `AppComponent` and `TopComponent` are defined in their decorators. This challenge start with the old `NgModule`-based architecture, but you will need to change it to use the new feature.
</details>
<details>
<summary>Hint 3</summary>
The new feature is [Deferrable Views](https://angular.dev/guide/defer), which provides several different triggers. One of them is ideal for this challenge.
</details>

View File

@@ -8,7 +8,6 @@ challengeNumber: 51
command: signal-function-call-effect
sidebar:
order: 20
badge: New
---
## Information

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Ir al Desafío más reciente
link: /es/challenges/signal/51-function-call-effect
link: /es/challenges/angular/52-lazy-load-component/
icon: rocket
- text: Dar una estrella
link: https://github.com/tomalaforge/angular-challenges
@@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro';
import SubscriptionForm from '../../../components/SubscriptionForm.astro';
<CardGrid>
<Card title="51 Desafíos">
Este repositorio contiene 51 Desafíos relacionados con <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> y <b>Typescript</b>.
<Card title="52 Desafíos">
Este repositorio contiene 52 Desafíos relacionados con <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> y <b>Typescript</b>.
Estos desafíos se resuelven en torno a problemas de la vida real o características específicas para mejorar tus habilidades.
</Card>

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Aller au dernier Challenge
link: /fr/challenges/signal/51-function-call-effect
link: /fr/challenges/angular/52-lazy-load-component/
icon: rocket
- text: Donne une étoile
link: https://github.com/tomalaforge/angular-challenges
@@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro';
import SubscriptionForm from '../../../components/SubscriptionForm.astro';
<CardGrid>
<Card title="51 Défis">
Ce répertoire rassemble 51 Défis liés à <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> et <b>Typescript</b>. Ces défis portent sur des problèmes réels ou des fonctionnalités spécifiques pour améliorer vos compétences.
<Card title="52 Défis">
Ce répertoire rassemble 52 Défis liés à <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> et <b>Typescript</b>. Ces défis portent sur des problèmes réels ou des fonctionnalités spécifiques pour améliorer vos compétences.
</Card>
<Card title="Subscribe to get notify of latest challenges">

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Go to the latest Challenge
link: /challenges/signal/51-function-call-effect
link: /challenges/angular/52-lazy-load-component
icon: rocket
- text: Give a star
link: https://github.com/tomalaforge/angular-challenges
@@ -27,8 +27,8 @@ import MyIcon from '../../components/MyIcon.astro';
import SubscriptionForm from '../../components/SubscriptionForm.astro';
<CardGrid>
<Card title="51 Challenges">
This repository gathers 51 Challenges related to <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> and <b>Typescript</b>.
<Card title="52 Challenges">
This repository gathers 52 Challenges related to <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> and <b>Typescript</b>.
These challenges resolve around real-life issues or specific features to elevate your skills.
</Card>
@@ -43,8 +43,8 @@ import SubscriptionForm from '../../components/SubscriptionForm.astro';
</Card>
<Card title="Learn Alongside Others">
Learning and practicing a new framework is always challenging. This set of
  challenges provide real-world use cases to apply what you've been learning.
Learning and practicing a new framework is always challenging. This set of  
challenges provide real-world use cases to apply what you've been learning.
Anyone can comment or offer assistance.
<b>
Learning alone is great, but learning alongside others will get you further.
@@ -57,7 +57,8 @@ import SubscriptionForm from '../../components/SubscriptionForm.astro';
</Card>
<Card title="Prepare Yourself for Interviews">
By completing these challenges, you'll be ready for any technical questions that may come up during a frontend job interview.
By completing these challenges, you'll be ready for any technical questions
that may come up during a frontend job interview.
</Card>
<Card title="Sponsoring">

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Ir para o desafio mais recente
link: /pt/challenges/signal/51-function-call-effect
link: /pt/challenges/angular/52-lazy-load-component/
icon: rocket
- text: Dar uma estrela
link: https://github.com/tomalaforge/angular-challenges
@@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro';
import SubscriptionForm from '../../../components/SubscriptionForm.astro';
<CardGrid>
<Card title="51 Desafios">
Este repositório possui 51 Desafios relacionados a <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>,
<Card title="52 Desafios">
Este repositório possui 52 Desafios relacionados a <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>,
<b>Ngrx</b> e <b>Typescript</b>.
Esses desafios são voltados para problemas reais ou funcionalidades específicas afim de
melhorar suas habilidades.

View File

@@ -13,7 +13,7 @@ hero:
icon: right-arrow
variant: primary
- text: Перейти к последней задаче
link: /ru/challenges/signal/51-function-call-effect
link: /ru/challenges/angular/52-lazy-load-component/
icon: rocket
- text: Добавить звезду
link: https://github.com/tomalaforge/angular-challenges