mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-10 04:43:03 -05:00
feat(challenge37): ngfor optimize big list
This commit is contained in:
@@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f
|
||||
|
||||
## Challenges
|
||||
|
||||
Check [all 36 challenges](https://angular-challenges.vercel.app/)
|
||||
Check [all 37 challenges](https://angular-challenges.vercel.app/)
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
|
||||
36
apps/performance/ngfor-biglist/.eslintrc.json
Normal file
36
apps/performance/ngfor-biglist/.eslintrc.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"extends": ["../../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"rules": {
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "attribute",
|
||||
"prefix": "app",
|
||||
"style": "camelCase"
|
||||
}
|
||||
],
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "element",
|
||||
"prefix": "app",
|
||||
"style": "kebab-case"
|
||||
}
|
||||
]
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"extends": ["plugin:@nx/angular-template"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
13
apps/performance/ngfor-biglist/README.md
Normal file
13
apps/performance/ngfor-biglist/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# NgFor optimize big list
|
||||
|
||||
> Author: Thomas Laforge
|
||||
|
||||
### Run Application
|
||||
|
||||
```bash
|
||||
npx nx serve performance-ngfor-biglist
|
||||
```
|
||||
|
||||
### Documentation and Instruction
|
||||
|
||||
Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/angular-performance/37-ngfor-biglist/).
|
||||
81
apps/performance/ngfor-biglist/project.json
Normal file
81
apps/performance/ngfor-biglist/project.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"name": "performance-ngfor-biglist",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"sourceRoot": "apps/performance/ngfor-biglist/src",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular-devkit/build-angular:browser",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/apps/performance/ngfor-biglist",
|
||||
"index": "apps/performance/ngfor-biglist/src/index.html",
|
||||
"main": "apps/performance/ngfor-biglist/src/main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "apps/performance/ngfor-biglist/tsconfig.app.json",
|
||||
"assets": [
|
||||
"apps/performance/ngfor-biglist/src/favicon.ico",
|
||||
"apps/performance/ngfor-biglist/src/assets"
|
||||
],
|
||||
"styles": ["apps/performance/ngfor-biglist/src/styles.scss"],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "performance-ngfor-biglist:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "performance-ngfor-biglist:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "performance-ngfor-biglist:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"apps/performance/ngfor-biglist/**/*.ts",
|
||||
"apps/performance/ngfor-biglist/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
apps/performance/ngfor-biglist/src/app/app.component.ts
Normal file
42
apps/performance/ngfor-biglist/src/app/app.component.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { NgIf } from '@angular/common';
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { generateList } from './generateList';
|
||||
import { PersonService } from './list.service';
|
||||
import { PersonListComponent } from './person-list.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf,
|
||||
PersonListComponent,
|
||||
FormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
],
|
||||
providers: [PersonService],
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<button
|
||||
(click)="loadList.set(true)"
|
||||
class="border border-black p-2 rounded-md">
|
||||
Load List
|
||||
</button>
|
||||
|
||||
<app-person-list
|
||||
*ngIf="loadList()"
|
||||
class="max-w-2xl w-3/4"
|
||||
[persons]="persons()" />
|
||||
`,
|
||||
host: {
|
||||
class: 'flex items-center flex-col gap-5',
|
||||
},
|
||||
})
|
||||
export class AppComponent {
|
||||
readonly persons = signal(generateList());
|
||||
readonly loadList = signal(false);
|
||||
|
||||
label = '';
|
||||
}
|
||||
6
apps/performance/ngfor-biglist/src/app/app.config.ts
Normal file
6
apps/performance/ngfor-biglist/src/app/app.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ApplicationConfig } from '@angular/core';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideAnimations()],
|
||||
};
|
||||
15
apps/performance/ngfor-biglist/src/app/generateList.ts
Normal file
15
apps/performance/ngfor-biglist/src/app/generateList.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { randEmail, randFirstName } from '@ngneat/falso';
|
||||
import { Person } from './person.model';
|
||||
|
||||
export function generateList() {
|
||||
const arr: Person[] = [];
|
||||
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
arr.push({
|
||||
email: randEmail(),
|
||||
name: randFirstName(),
|
||||
});
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
44
apps/performance/ngfor-biglist/src/app/list.service.ts
Normal file
44
apps/performance/ngfor-biglist/src/app/list.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { randEmail, randFirstName } from '@ngneat/falso';
|
||||
import { generateList } from './generateList';
|
||||
import { Person } from './person.model';
|
||||
|
||||
@Injectable()
|
||||
export class PersonService {
|
||||
private readonly fakeBackend = inject(FakeBackendService);
|
||||
readonly persons = signal<Person[]>([]);
|
||||
|
||||
loadPersons() {
|
||||
this.persons.set(generateList());
|
||||
}
|
||||
|
||||
deletePerson(email: string) {
|
||||
this.persons.set(
|
||||
this.fakeBackend
|
||||
.returnNewList(this.persons())
|
||||
.filter((p) => p.email !== email)
|
||||
);
|
||||
}
|
||||
|
||||
updatePerson(email: string) {
|
||||
this.persons.set(
|
||||
this.fakeBackend
|
||||
.returnNewList(this.persons())
|
||||
.map((p) => (p.email === email ? { email, name: randFirstName() } : p))
|
||||
);
|
||||
}
|
||||
|
||||
addPerson(name: string) {
|
||||
this.persons.set([
|
||||
{ email: randEmail(), name },
|
||||
...this.fakeBackend.returnNewList(this.persons()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FakeBackendService {
|
||||
returnNewList = (input: Person[]): Person[] => [
|
||||
...input.map((i) => ({ ...i })),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
|
||||
import { NgForTrackByModule } from '@angular-challenges/shared/directives';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Person } from './person.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-person-list',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgForTrackByModule],
|
||||
template: `
|
||||
<div class="h-[300px] relative overflow-hidden">
|
||||
<div class="absolute inset-0 overflow-scroll">
|
||||
<div
|
||||
*ngFor="let person of persons; trackByProp: 'email'"
|
||||
class="flex justify-between items-center border-b h-9">
|
||||
<h3>{{ person.name }}</h3>
|
||||
<p>{{ person.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
host: {
|
||||
class: 'w-full flex flex-col',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PersonListComponent {
|
||||
@Input() persons: Person[] = [];
|
||||
}
|
||||
4
apps/performance/ngfor-biglist/src/app/person.model.ts
Normal file
4
apps/performance/ngfor-biglist/src/app/person.model.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Person {
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
0
apps/performance/ngfor-biglist/src/assets/.gitkeep
Normal file
0
apps/performance/ngfor-biglist/src/assets/.gitkeep
Normal file
BIN
apps/performance/ngfor-biglist/src/favicon.ico
Normal file
BIN
apps/performance/ngfor-biglist/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
13
apps/performance/ngfor-biglist/src/index.html
Normal file
13
apps/performance/ngfor-biglist/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>performance-ngfor-biglist</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>
|
||||
7
apps/performance/ngfor-biglist/src/main.ts
Normal file
7
apps/performance/ngfor-biglist/src/main.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||
console.error(err)
|
||||
);
|
||||
5
apps/performance/ngfor-biglist/src/styles.scss
Normal file
5
apps/performance/ngfor-biglist/src/styles.scss
Normal 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 */
|
||||
14
apps/performance/ngfor-biglist/tailwind.config.js
Normal file
14
apps/performance/ngfor-biglist/tailwind.config.js
Normal 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: [],
|
||||
};
|
||||
10
apps/performance/ngfor-biglist/tsconfig.app.json
Normal file
10
apps/performance/ngfor-biglist/tsconfig.app.json
Normal 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"]
|
||||
}
|
||||
7
apps/performance/ngfor-biglist/tsconfig.editor.json
Normal file
7
apps/performance/ngfor-biglist/tsconfig.editor.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"types": []
|
||||
}
|
||||
}
|
||||
29
apps/performance/ngfor-biglist/tsconfig.json
Normal file
29
apps/performance/ngfor-biglist/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"useDefineForClassFields": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.editor.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"total": 36,
|
||||
"total": 37,
|
||||
"🟢": 13,
|
||||
"🟠": 116,
|
||||
"🟠": 117,
|
||||
"🔴": 207
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 🟢 Memoization
|
||||
description: Challenge 35 is about learning
|
||||
description: Challenge 35 is about learning how pure pipe works
|
||||
sidebar:
|
||||
order: 8
|
||||
---
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
title: 🟢 NgFor Optimization
|
||||
description: Challenge 36 is about ...
|
||||
description: Challenge 36 is about learning how trackby works
|
||||
sidebar:
|
||||
order: 13
|
||||
badge: New
|
||||
---
|
||||
|
||||
<div class="chip">Challenge #36</div>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: 🟠 NgFor Optimization Big List
|
||||
description: Challenge 37 is about learning how virtualization optimize big list rendering
|
||||
sidebar:
|
||||
order: 117
|
||||
badge: New
|
||||
---
|
||||
|
||||
<div class="chip">Challenge #37</div>
|
||||
|
||||
## Information
|
||||
|
||||
In this application, we can render a list of 100,000 individuals by clicking on the **loadList** button. If you open the Chrome developer panel by pressing **F12**, go to the <b>Source</b> tab, and expand the element to see the list, you will notice that all 100,000 elements are rendered in the DOM, even though we can only see about 20 elements in the viewport. This process takes a lot of time, which is why the application is very slow at displaying the list.
|
||||
|
||||
We can use the <b>Angular DevTool</b> to profile our application and understand what is happening inside our application. I will show you how to do it inside the following video.
|
||||
|
||||
:::note
|
||||
If you don't know how to use it, read [the performance introduction page](/challenges/angular-performance/) first and come back after.
|
||||
:::
|
||||
|
||||
## Statement
|
||||
|
||||
The goal of this challenge is to implement a better alternative to display big list of items.
|
||||
|
||||
## Hints:
|
||||
|
||||
<details>
|
||||
<summary>Hint 1</summary>
|
||||
|
||||
If you're unsure where to begin, I recommend reading the [Angular CDK virtualization documentation](https://material.angular.io/cdk/scrolling/overview)
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
:::note
|
||||
Start the project by running: `npx nx serve performance-ngfor-biglist`.
|
||||
:::
|
||||
|
||||
:::tip[Reminder]
|
||||
Your PR title must start with <b>Answer:37</b>.
|
||||
:::
|
||||
|
||||
<div class="article-footer">
|
||||
<a
|
||||
href="https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A37+label%3Aanswer"
|
||||
alt="NgFor optimize big list community solutions">
|
||||
❖ Community Answers
|
||||
</a>
|
||||
<a
|
||||
href='https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A37+label%3A"answer+author"'
|
||||
alt="NgFor optimize big list solution author">
|
||||
▶︎ Author Answer
|
||||
</a>
|
||||
</div>
|
||||
@@ -23,8 +23,8 @@ hero:
|
||||
import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||
|
||||
<CardGrid>
|
||||
<Card title="36 Challenges">
|
||||
This repository gathers 36 Challenges related to <b>Angular</b>, <b>Nx</b>, <b>RxJS</b>, <b>Ngrx</b> and <b>Typescript</b>.
|
||||
<Card title="37 Challenges">
|
||||
This repository gathers 37 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user