Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14,258 changes: 14,258 additions & 0 deletions code-snapshots/02-essentials/36-localstorage/package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@

<!-- kemi header -->
<app-header />


<main>
<!-- list users -->
<ul id="users">
@for (user of users; track user.id) {
<li>
<!-- [user]="user" -> Me ndimen e @Input ndergojme user_array ne app-user -->

<!-- [selected]="user.id === selectedUserId" -> if user.id === selectedUserId qe vjen nga
@Output select ose nga nje nga list qe shtyp user eshte e barrabart return true else false,
nese eshte false keyword ne user_html [class.active]="selected" nuk aktivizohet -->

<!-- (select)="onSelectUser($event)" -> marim user_id_string me @Output nga app-user, kur user shtyp nje nga listat -->
<app-user [user]="user" [selected]="user.id === selectedUserId" (select)="onSelectUser($event)" />
</li>
}
</ul>

<!-- task for each user -->
<!-- marim object user nga getter method 'selectedUser' e cila edhe ajo eshte nje vler undefiend
ne vetevete edhe pse kryen nje funksjon, prandaj @if na mundeson qe vlerat user dhe name mos
te jet ' [userId]="selectedUser!.id" [name]="selectedUser!.name" ', pra me '!'. -->
@if (selectedUser) {
<app-tasks [userId]="selectedUser.id" [name]="selectedUser.name" />
} @else {
<p id="fallback">Select a user to see their tasks!</p>
}

</main>

Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,22 @@ import { TasksComponent } from "./tasks/tasks.component";
imports: [HeaderComponent, UserComponent, TasksComponent]
})
export class AppComponent {
users = DUMMY_USERS;
selectedUserId?: string;
users = DUMMY_USERS; // marim vlerat fallso
selectedUserId!: string; // eshte undifiend sepse vlera ne fillim eshte bosh

// nga komponenti app-user marim me output user_id_string, pasi user shtype nje nga pikat ne list
onSelectUser(id: string) {
this.selectedUserId = id;
}

// nga user_id_string qe morem gjejme objektin ne user_array_dommy
// pasi gjejme objektin nga user_is_string me ndimen keyword 'get' e katapultojme ne html inside app-tasks
// por deri ne momentin qe code nuk eshte run, i gjith funksjoni me foshtem eshte UNDIFIEND
// qe do te thot, nuk mundemi ta bashkagjisim ne html pa ndimen e keyword '!'
// ose si pas llogikes se re @if(selectedUser)
get selectedUser() {
return this.users.find((user) => user.id === this.selectedUserId);
}

onSelectUser(id: string) {
this.selectedUserId = id;
}

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<div>
<ng-content />
</div>

<!-- ne cdo komponent femi, nese shofim <ng-content />, kuptojme qe ne komponentet prind
app-user dhe app-task jemi duke perdorur <app-card> .... me sintaksa html brenda .... </app-card> -->

Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ import { Component } from '@angular/core';
export class CardComponent {

}

Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@

<!-- e mbyllim djalogun nga backdrop - sfond -->
<div class="backdrop" (click)="onCancel()"></div>
<dialog open>
<h2>Add Task</h2>
<form (ngSubmit)="onSubmit()">
<p>
<label for="title">Title</label>
<input type="text" id="title" name="title" [(ngModel)]="enteredTitle" />
<input type="text" id="title" name="title" (ngModel)="enteredTitle" />
</p>

<p>
Expand All @@ -23,3 +25,4 @@ <h2>Add Task</h2>
</p>
</form>
</dialog>

Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { type NewTaskData } from '../task/task.model';
import { TasksService } from '../tasks.service';

@Component({
Expand All @@ -12,19 +11,24 @@ import { TasksService } from '../tasks.service';
styleUrl: './new-task.component.css',
})
export class NewTaskComponent {
@Input({ required: true }) userId!: string;
@Output() close = new EventEmitter<void>();
@Input({ required: true }) userId!: string; // marim user id nga app-tasks
// eshte void sepse nuk po percjell te dhena, thjesht po percjellim sinjalin
@Output() close = new EventEmitter<void>(); // sinjalizojme (close)="onCloseAddTask()" ne app-tasks duke mbullur dialogun

// specifikoj ca vlera boshe
enteredTitle = '';
enteredSummary = '';
enteredDate = '';
private tasksService = inject(TasksService);

// constructor(private tasksService: TasksService) {}
private tasksService = inject(TasksService); // si inject si constructor funksjoni eshte i njejte

onCancel() {
this.close.emit();
}

onSubmit() {
this.tasksService.addTask(
onSubmit() {
this.tasksService.addTask( // krijojme vlera te reja duke mbyllur dialog
{
title: this.enteredTitle,
summary: this.enteredSummary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ <h2>{{ task.title }}</h2>
</p>
</article>
</app-card>

Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import { TasksService } from '../tasks.service';
})
export class TaskComponent {
@Input({required: true}) task!: Task;

// " inject method " eshte ne vend te -> constructor(private tasksService: TasksService) {}
private tasksService = inject(TasksService);

onCompleteTask() {
this.tasksService.removeTask(this.task.id);
}
}

Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
@if (isAddingTask) {

<!-- if true return app-new-task qe permban nje dialog me form-data -->
@if (isAddingTask) { <!-- dergojme user_id me @Input, marim thirjen close me @Output duke mbyllur dialogun -->
<app-new-task [userId]="userId" (close)="onCloseAddTask()" />
}

<!-- *** po ta ven re si @if si @for jan jashte keyword te html, kurse for() te angular 17 eshte breda keyword <ul for( task of selectedUserTasks ) /> *** -->

<section id="tasks">
<header>
<h2>{{ name }}'s Tasks</h2>
<menu>
<!-- kur shtypim Add task shfaqet dialog i cili ndodhen ne app-new-task -->
<button (click)="onStartAddTask()">Add Task</button>

</menu>
</header>




<ul>
<!-- pra track - gjurmoje eshte nje keyword or feature e cila i jep akses Angularit te rendis task si pas radhes se reshtave,
ku cdo reshte perfaqeson nje id, prandaj shtojme task.id sepse perfaqeson id cdo reshti, i cili permban nje objekt. -->

<!-- Angular 18 introduces a new syntax for iteration using @for instead of *ngFor. This change aligns with a more modern and
explicit approach to loops in templates. -->

<!-- Tracking by ID: The track keyword is used to specify how Angular should track items for performance optimization, similar
to trackBy in *ngFor. -->
@for (task of selectedUserTasks; track task.id) {
<li>
<app-task [task]="task" />
</li>
}

<!-- If you're upgrading to Angular 18, you'll need to update your templates to use this new syntax wherever you previously used *ngFor -->

</ul>
</section>


Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ import { TasksService } from './tasks.service';
imports: [TaskComponent, NewTaskComponent],
})
export class TasksComponent {
@Input({ required: true }) userId!: string;
// marim vlerat userId dhe name nga app-root
// ketu marim userId i cili burron nga njera nga listat qe user ka shtypur
@Input({ required: true }) userId!: string;
@Input({ required: true }) name!: string;
isAddingTask = false;

constructor(private tasksService: TasksService) {}
// private tasksService = inject(TasksService);
constructor(private tasksService: TasksService) {} // si inject si constructor funksjoni eshte i njejte

// marim ato objekte nga tasks_array te cilat filtrohen nga user_id, te cilat i bashkagjisim ne app-task
get selectedUserTasks() {
return this.tasksService.getUserTasks(this.userId);
}
Expand All @@ -29,4 +33,4 @@ export class TasksComponent {
onCloseAddTask() {
this.isAddingTask = false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ import { Injectable } from '@angular/core';

import { type NewTaskData } from './task/task.model';

// Type Alias: The type keyword allows you to create a type alias for complex types, making your code
// more readable and easier to manage.

// Union Types: You can use type to define a union of multiple types.

// type: Used for creating type aliases, can define unions, intersections, and more complex types.
// interface: Used to define object shapes, especially for objects and classes, and supports declaration merging.

// The type keyword in TypeScript (and Angular 18) is used to define custom types, which can be
// imported and used across your application. It allows for better code organization, type safety,
// and readability. You can create complex, union, or intersection types with the type keyword.

@Injectable({ providedIn: 'root' })
export class TasksService {
private tasks = [
Expand Down Expand Up @@ -34,31 +46,49 @@ export class TasksService {
const tasks = localStorage.getItem('tasks');

if (tasks) {
// veretet ne kemi nje tasks_array me 3 objekte por this.tasks do updetohen te baz te vlerave qe
// mer nga localStorage.getItem('tasks');
this.tasks = JSON.parse(tasks);
}
}

getUserTasks(userId: string) {
// NOTE: mos ngaterohesh per her tjeter:
//
return this.tasks.filter((task) => task.userId === userId);
}

addTask(taskData: NewTaskData, userId: string) {
this.tasks.unshift({

// The unshift() method of Array instances adds the specified elements to the beginning of an array and returns the new length of the array
this.tasks.unshift({ // shtojme nje objekt
id: new Date().getTime().toString(),
userId: userId,
userId: userId, // foreignKey by user id, per te autorizuar tasqet e secilit user.
title: taskData.title,
summary: taskData.summary,
dueDate: taskData.date,
});
this.saveTasks();
this.saveTasks(); // updetojme tasks array
}

removeTask(id: string) {
this.tasks = this.tasks.filter((task) => task.id !== id);
this.saveTasks();

// ** ideja se si e fshin keyword filter nje objekt eshte keshtu: **
// nese do ishte task.id === id, do shfaqt vetem te objekt qe perputhet me id
// ne rastin ton theret vetem ato objekte te cilat jan te ndryshme me id qe therasim nga removeTask(id: string)
// duke updetuar localStorage me te dhena te reja, sa here qe fshim apo shtojme new tast with user id
this.tasks = this.tasks.filter((task) => task.id !== id); // fshim nje objekt by id
this.saveTasks(); // updetojme tasks array
}

private saveTasks() {
private saveTasks() {
// per cdo update te tasks_array, updetojme dhe localStorage
localStorage.setItem('tasks', JSON.stringify(this.tasks));

// i vetem BUG i vogel ketu ishte kur fshim te gjitha vlerat nga localStorage ajo ngelesh nje array bosht []
// dhe kur i ben refresh browser kodi lexon localStorage me array bosh [], kur duhet te lexone private tasks = [...] me vlera
// kesthu qe shtova reshtin me posht kur ska vlera, fshi email e localStorage
// dhe sa here qe behet refresh browser kodi lexon private tasks = [...] dhe jo localStorage
if (this.tasks.length == 0) localStorage.removeItem('tasks');
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@

<!-- po te shofim me vemendje brenda <app-card> .... </app-card> kemi bashkagjitur disa sintaksa html -->
<!-- ku ne komponentin child, ne app-card shtojme <ng-content /> i cili i jep akses sitaksave brenda app-card ne komponentin prind -->

<app-card>
<!-- if sekected == true, the cade call class active ' [class.active] ' inside html, otherwise false, not call anything -->
<button [class.active]="selected" (click)="onSelectUser()">
<img [src]="imagePath" [alt]="user.name" />
<span>{{ user.name }}</span>
</button>
</app-card>

Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import { CardComponent } from "../shared/card/card.component";
imports: [CardComponent]
})
export class UserComponent {
@Input({ required: true }) user!: User;

// required: true esht enje feature e re e @Input e cila e palajmeron inputin qe <app-user [user]="user" /> mungon
@Input({ required: true }) user!: User; // cdo vler eshte undifiend or '!' sepse nuk kemi vlera, kur kodi behet run mbushet me flera
@Input({required: true}) selected!: boolean;
@Output() select = new EventEmitter<string>();
@Output() select = new EventEmitter<string>(); // user_is_string qe marim nga this.select.emit e bashkagjisim ne (select)="onSelectUser($event)"

// getter methond enabling dynamic values in your template
get imagePath() {
return 'assets/users/' + this.user.avatar;
}
Expand All @@ -23,3 +26,4 @@ export class UserComponent {
this.select.emit(this.user.id);
}
}