Angular और Firebase की मदद से वेब ऐप्लिकेशन बनाना

1. परिचय

पिछली बार अपडेट किया गया: 11-09-2020

आपको क्या बनाने को मिलेगा

इस कोडलैब में, हम Angular और Firebase की मदद से एक वेब कानबन बोर्ड बनाएंगे! हमारे फ़ाइनल ऐप्लिकेशन में, टास्क की तीन कैटगरी होंगी: बैकलॉग, प्रोसेस में हैं, और पूरे हो गए. हम टास्क बना सकेंगे, उन्हें मिटा सकेंगे, और उन्हें खींचकर छोड़ने की सुविधा का इस्तेमाल करके, एक कैटगरी से दूसरी कैटगरी में ट्रांसफ़र कर सकेंगे.

हम Angular का इस्तेमाल करके यूज़र इंटरफ़ेस डेवलप करेंगे. साथ ही, Firestore को अपने परसिस्टेंट स्टोर के तौर पर इस्तेमाल करेंगे. कोड लैब के आखिर में, हम Angular CLI का इस्तेमाल करके ऐप्लिकेशन को Firebase Hosting पर डिप्लॉय करेंगे.

b23bd3732d0206b.png

आपको क्या सीखने को मिलेगा

  • Angular Material और CDK का इस्तेमाल करने का तरीका.
  • अपने Angular ऐप्लिकेशन में Firebase इंटिग्रेशन जोड़ने का तरीका.
  • Firestore में अपने परसिस्टेंट डेटा को सेव रखने का तरीका.
  • Angular CLI का इस्तेमाल करके, एक ही कमांड से अपने ऐप्लिकेशन को Firebase होस्टिंग पर डिप्लॉय करने का तरीका.

आपको इन चीज़ों की ज़रूरत होगी

इस कोडलैब में यह माना गया है कि आपके पास Google खाता है. साथ ही, आपको Angular और Angular CLI के बारे में बुनियादी जानकारी है.

आइए, शुरू करें!

2. नया प्रोजेक्ट बनाना

सबसे पहले, चलिए एक नया Angular वर्कस्पेस बनाते हैं:

ng new kanban-fire
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS

इस चरण में कुछ मिनट लग सकते हैं. ऐंगुलर सीएलआई, आपके प्रोजेक्ट का स्ट्रक्चर बनाता है और सभी डिपेंडेंसी इंस्टॉल करता है. इंस्टॉल करने की प्रोसेस पूरी होने के बाद, kanban-fire डायरेक्ट्री पर जाएं और Angular CLI के डेवलपमेंट सर्वर को शुरू करें:

ng serve

http://localhost:4200 खोलें. आपको इससे मिलता-जुलता आउटपुट दिखेगा:

5ede7bc5b1109bf3.png

अपने एडिटर में, src/app/app.component.html खोलें और इसका पूरा कॉन्टेंट मिटाएं. http://localhost:4200 पर वापस जाने पर, आपको एक खाली पेज दिखेगा.

3. Material और CDK को जोड़ना

Angular में, Material Design के मुताबिक यूज़र इंटरफ़ेस कॉम्पोनेंट लागू करने की सुविधा मिलती है. यह सुविधा, @angular/material पैकेज का हिस्सा है. @angular/material की डिपेंडेंसी में से एक, कॉम्पोनेंट डेवलपमेंट किट या सीडीके है. सीडीके, प्रिमिटिव उपलब्ध कराता है. जैसे, सुलभता से जुड़ी सुविधाएं, खींचकर छोड़ना, और ओवरले. हम @angular/cdk पैकेज में सीडीके डिस्ट्रिब्यूट करते हैं.

ऐप्लिकेशन रन में मटीरियल जोड़ने के लिए:

ng add @angular/material

इस कमांड से आपको थीम चुनने के लिए कहा जाता है. ऐसा तब होता है, जब आपको ग्लोबल मटीरियल टाइपोग्राफ़ी स्टाइल का इस्तेमाल करना हो और Angular Material के लिए ब्राउज़र ऐनिमेशन सेट अप करने हों. इस कोडलैब में दिखाए गए नतीजे पाने के लिए, "इंडिगो/पिंक" चुनें. साथ ही, आखिरी दो सवालों के जवाब "हां" में दें.

ng add कमांड, @angular/material और इसकी डिपेंडेंसी इंस्टॉल करती है. साथ ही, AppModule में BrowserAnimationsModule इंपोर्ट करती है. अगले चरण में, हम इस मॉड्यूल के कॉम्पोनेंट का इस्तेमाल शुरू कर सकते हैं!

सबसे पहले, AppComponent में टूलबार और आइकॉन जोड़ते हैं. app.component.html खोलें और यह मार्कअप जोड़ें:

src/app/app.component.html

<mat-toolbar color="primary">
  <mat-icon>local_fire_department</mat-icon>
  <span>Kanban Fire</span>
</mat-toolbar>

यहां, हमने अपनी मटीरियल डिज़ाइन थीम के प्राइमरी कलर का इस्तेमाल करके एक टूलबार जोड़ा है. साथ ही, इसमें हमने "Kanban Fire" लेबल के बगल में local_fire_depeartment आइकॉन का इस्तेमाल किया है. अगर अब अपनी कंसोल स्क्रीन पर देखा जाए, तो आपको दिखेगा कि Angular ने कुछ गड़बड़ियां दिखाई हैं. इन्हें ठीक करने के लिए, पक्का करें कि आपने AppModule में ये इंपोर्ट जोड़े हों:

src/app/app.module.ts

...
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...
    MatToolbarModule,
    MatIconModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

हम Angular मटीरियल टूलबार और आइकॉन का इस्तेमाल करते हैं. इसलिए, हमें AppModule में इनसे जुड़े मॉड्यूल इंपोर्ट करने होंगे.

अब आपको स्क्रीन पर यह जानकारी दिखेगी:

a39cf8f8428a03bc.png

सिर्फ़ चार लाइनों के एचटीएमएल और दो इंपोर्ट के साथ, यह काफ़ी अच्छा है!

4. टास्क को विज़ुअलाइज़ करना

अगले चरण के तौर पर, हम एक ऐसा कॉम्पोनेंट बनाएंगे जिसका इस्तेमाल, कानबन बोर्ड में टास्क को विज़ुअलाइज़ करने के लिए किया जा सकता है.

src/app डायरेक्ट्री पर जाएं और यह सीएलआई कमांड चलाएं:

ng generate component task

इस कमांड से TaskComponent जनरेट होता है और इसके एलान को AppModule में जोड़ा जाता है. task डायरेक्ट्री में, task.ts नाम की फ़ाइल बनाएं. हम इस फ़ाइल का इस्तेमाल, कानबन बोर्ड में टास्क के इंटरफ़ेस को तय करने के लिए करेंगे. हर टास्क में, स्ट्रिंग टाइप के id, title, और description फ़ील्ड हो सकते हैं. हालांकि, इन्हें भरना ज़रूरी नहीं है:

src/app/task/task.ts

export interface Task {
  id?: string;
  title: string;
  description: string;
}

अब task.component.ts को अपडेट करते हैं. हम चाहते हैं कि TaskComponent, Task टाइप के ऑब्जेक्ट को इनपुट के तौर पर स्वीकार करे. साथ ही, हम चाहते हैं कि यह "edit" आउटपुट दे सके:

src/app/task/task.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Task } from './task';

@Component({
  selector: 'app-task',
  templateUrl: './task.component.html',
  styleUrls: ['./task.component.css']
})
export class TaskComponent {
  @Input() task: Task | null = null;
  @Output() edit = new EventEmitter<Task>();
}

TaskComponent के टेंप्लेट में बदलाव करें! task.component.html खोलें और इसके कॉन्टेंट को इस एचटीएमएल से बदलें:

src/app/task/task.component.html

<mat-card class="item" *ngIf="task" (dblclick)="edit.emit(task)">
  <h2>{{ task.title }}</h2>
  <p>
    {{ task.description }}
  </p>
</mat-card>

ध्यान दें कि अब हमें कंसोल में गड़बड़ियां मिल रही हैं:

'mat-card' is not a known element:
1. If 'mat-card' is an Angular component, then verify that it is part of this module.
2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.ng

ऊपर दिए गए टेंप्लेट में, हम @angular/material से mat-card कॉम्पोनेंट का इस्तेमाल कर रहे हैं. हालांकि, हमने ऐप्लिकेशन में इसके संबंधित मॉड्यूल को इंपोर्ट नहीं किया है. ऊपर दी गई गड़बड़ी को ठीक करने के लिए, हमें AppModule में MatCardModule को इंपोर्ट करना होगा:

src/app/app.module.ts

...
import { MatCardModule } from '@angular/material/card';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...
    MatCardModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

इसके बाद, हम AppComponent में कुछ टास्क बनाएँगे और उन्हें TaskComponent का इस्तेमाल करके विज़ुअलाइज़ करेंगे!

AppComponent में, todo नाम का एक ऐरे तय करें. इसके अंदर दो टास्क जोड़ें:

src/app/app.component.ts

...
import { Task } from './task/task';

@Component(...)
export class AppComponent {
  todo: Task[] = [
    {
      title: 'Buy milk',
      description: 'Go to the store and buy milk'
    },
    {
      title: 'Create a Kanban app',
      description: 'Using Firebase and Angular create a Kanban app!'
    }
  ];
}

अब, app.component.html के सबसे नीचे, यह *ngFor डायरेक्टिव जोड़ें:

src/app/app.component.html

<app-task *ngFor="let task of todo" [task]="task"></app-task>

ब्राउज़र खोलने पर, आपको यह दिखेगा:

d96fccd13c63ceb1.png

5. टास्क के लिए, खींचें और छोड़ें सुविधा लागू करना

अब हम इस वर्कशॉप के सबसे मज़ेदार हिस्से की तरफ़ चलते हैं! आइए, तीन अलग-अलग राज्यों के लिए तीन स्विमलेन बनाएं. साथ ही, Angular CDK का इस्तेमाल करके, ड्रैग-एंड-ड्रॉप की सुविधा लागू करें.

app.component.html में, सबसे ऊपर *ngFor डायरेक्टिव के साथ app-task कॉम्पोनेंट को हटाएं और इसे इससे बदलें:

src/app/app.component.html

<div class="content-wrapper">
  <div class="container-wrapper">
    <div class="container">
      <h2>Backlog</h2>

      <mat-card
        cdkDropList
        id="todo"
        #todoList="cdkDropList"
        [cdkDropListData]="todo"
        [cdkDropListConnectedTo]="[doneList, inProgressList]"
        (cdkDropListDropped)="drop($event)"
        class="list">
        <p class="empty-label" *ngIf="todo.length === 0">Empty list</p>
        <app-task (edit)="editTask('todo', $event)" *ngFor="let task of todo" cdkDrag [task]="task"></app-task>
      </mat-card>
    </div>

    <div class="container">
      <h2>In progress</h2>

      <mat-card
        cdkDropList
        id="inProgress"
        #inProgressList="cdkDropList"
        [cdkDropListData]="inProgress"
        [cdkDropListConnectedTo]="[todoList, doneList]"
        (cdkDropListDropped)="drop($event)"
        class="list">
        <p class="empty-label" *ngIf="inProgress.length === 0">Empty list</p>
        <app-task (edit)="editTask('inProgress', $event)" *ngFor="let task of inProgress" cdkDrag [task]="task"></app-task>
      </mat-card>
    </div>

    <div class="container">
      <h2>Done</h2>

      <mat-card
        cdkDropList
        id="done"
        #doneList="cdkDropList"
        [cdkDropListData]="done"
        [cdkDropListConnectedTo]="[todoList, inProgressList]"
        (cdkDropListDropped)="drop($event)"
        class="list">
        <p class="empty-label" *ngIf="done.length === 0">Empty list</p>
        <app-task (edit)="editTask('done', $event)" *ngFor="let task of done" cdkDrag [task]="task"></app-task>
      </mat-card>
    </div>
  </div>
</div>

यहां बहुत कुछ हो रहा है. आइए, इस स्निपेट के हर हिस्से को एक-एक करके देखें. यह टेंप्लेट का टॉप-लेवल स्ट्रक्चर है:

src/app/app.component.html

...
<div class="container-wrapper">
  <div class="container">
    <h2>Backlog</h2>
    ...
  </div>

  <div class="container">
    <h2>In progress</h2>
    ...
  </div>

  <div class="container">
    <h2>Done</h2>
    ...
  </div>
</div>

यहां हमने एक div बनाया है, जिसमें तीनों स्विमलेन शामिल हैं. इसका क्लास नेम "container-wrapper" है. हर स्विमलेन का क्लास नेम "container" है और h2 टैग के अंदर एक टाइटल है.

अब पहले स्विमलेन के स्ट्रक्चर पर नज़र डालते हैं:

src/app/app.component.html

...
    <div class="container">
      <h2>Backlog</h2>

      <mat-card
        cdkDropList
        id="todo"
        #todoList="cdkDropList"
        [cdkDropListData]="todo"
        [cdkDropListConnectedTo]="[doneList, inProgressList]"
        (cdkDropListDropped)="drop($event)"
        class="list"
      >
        <p class="empty-label" *ngIf="todo.length === 0">Empty list</p>
        <app-task (edit)="editTask('todo', $event)" *ngFor="let task of todo" cdkDrag [task]="task"></app-task>
      </mat-card>
    </div>
...

सबसे पहले, हम स्विमलेन को mat-card के तौर पर तय करते हैं. यह cdkDropList डायरेक्टिव का इस्तेमाल करता है. हम mat-card का इस्तेमाल करते हैं, क्योंकि यह कॉम्पोनेंट स्टाइल उपलब्ध कराता है. cdkDropList की मदद से, बाद में हम एलिमेंट के अंदर टास्क ड्रॉप कर पाएंगे. हम इन दो इनपुट को भी सेट करते हैं:

  • cdkDropListData - ड्रॉप-डाउन सूची का इनपुट, जिसकी मदद से डेटा सरणी तय की जा सकती है
  • cdkDropListConnectedTo - मौजूदा cdkDropList से कनेक्ट किए गए अन्य cdkDropList के रेफ़रंस. इस इनपुट को सेट करके, हम यह तय करते हैं कि किन अन्य सूचियों में आइटम जोड़े जा सकते हैं

इसके अलावा, हमें cdkDropListDropped आउटपुट का इस्तेमाल करके ड्रॉप इवेंट को हैंडल करना है. cdkDropList से यह आउटपुट मिलने के बाद, हम AppComponent में बताए गए drop तरीके को लागू करेंगे और मौजूदा इवेंट को एक तर्क के तौर पर पास करेंगे.

ध्यान दें कि हमने इस कंटेनर के लिए आइडेंटिफ़ायर के तौर पर इस्तेमाल करने के लिए id और class नाम भी तय किया है, ताकि हम इसे स्टाइल कर सकें. अब mat-card के बच्चों के लिए बने कॉन्टेंट के बारे में जानते हैं. यहां दो एलिमेंट मौजूद हैं:

  • यह एक पैराग्राफ़ है. इसका इस्तेमाल तब किया जाता है, जब todo सूची में कोई आइटम मौजूद न हो और हमें "खाली सूची" टेक्स्ट दिखाना हो
  • app-task कॉम्पोनेंट. ध्यान दें कि यहां हमने edit आउटपुट को हैंडल किया है. इसे हमने मूल रूप से, सूची के नाम और $event ऑब्जेक्ट के साथ editTask तरीके को कॉल करके घोषित किया था. इससे हमें, बदली गई जानकारी वाले टास्क को सही सूची में जोड़ने में मदद मिलेगी. इसके बाद, हम ऊपर की तरह todo सूची पर दोहराते हैं और task इनपुट पास करते हैं. हालांकि, इस बार हम cdkDrag डायरेक्टिव भी जोड़ते हैं. इससे अलग-अलग टास्क को ड्रैग किया जा सकता है.

इन सभी को काम करने के लिए, हमें app.module.ts को अपडेट करना होगा. साथ ही, DragDropModule में इंपोर्ट शामिल करना होगा:

src/app/app.module.ts

...
import { DragDropModule } from '@angular/cdk/drag-drop';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...
    DragDropModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

हमें editTask और drop तरीकों के साथ-साथ, inProgress और done ऐरे भी घोषित करने होंगे:

src/app/app.component.ts

...
import { CdkDragDrop, transferArrayItem } from '@angular/cdk/drag-drop';

@Component(...)
export class AppComponent {
  todo: Task[] = [...];
  inProgress: Task[] = [];
  done: Task[] = [];

  editTask(list: string, task: Task): void {}

  drop(event: CdkDragDrop<Task[]>): void {
    if (event.previousContainer === event.container) {
      return;
    }
    if (!event.container.data || !event.previousContainer.data) {
      return;
    }
    transferArrayItem(
      event.previousContainer.data,
      event.container.data,
      event.previousIndex,
      event.currentIndex
    );
  }
}

ध्यान दें कि drop तरीके में, हम सबसे पहले यह देखते हैं कि हम उसी सूची में ड्रॉप कर रहे हैं जिससे टास्क आ रहा है. अगर ऐसा होता है, तो हम तुरंत वापस आ जाते हैं. ऐसा न होने पर, हम मौजूदा टास्क को डेस्टिनेशन स्विमलेन में ट्रांसफ़र कर देते हैं.

नतीजा यह होना चाहिए:

460f86bcd10454cf.png

अब आपको दोनों सूचियों के बीच आइटम ट्रांसफ़र करने का विकल्प दिखना चाहिए!

6. नए टास्क बनाना

अब, नए टास्क बनाने की सुविधा लागू करते हैं. इसके लिए, AppComponent के टेंप्लेट को अपडेट करें:

src/app/app.component.html

<mat-toolbar color="primary">
...
</mat-toolbar>

<div class="content-wrapper">
  <button (click)="newTask()" mat-button>
    <mat-icon>add</mat-icon> Add Task
  </button>

  <div class="container-wrapper">
    <div class="container">
      ...
    </div>
</div>

हम container-wrapper के चारों ओर एक टॉप-लेवल div एलिमेंट बनाते हैं. साथ ही, "टास्क जोड़ें" लेबल के बगल में "add" मटीरियल आइकॉन वाला बटन जोड़ते हैं. हमें बटन को स्विमलेन की सूची के ऊपर रखने के लिए, अतिरिक्त रैपर की ज़रूरत होती है. बाद में, हम फ़्लेक्सबॉक्स का इस्तेमाल करके, इन्हें एक-दूसरे के बगल में रखेंगे. यह बटन, मटीरियल बटन कॉम्पोनेंट का इस्तेमाल करता है. इसलिए, हमें AppModule में इससे जुड़े मॉड्यूल को इंपोर्ट करना होगा:

src/app/app.module.ts

...
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...
    MatButtonModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

अब AppComponent में टास्क जोड़ने की सुविधा लागू करते हैं. हम मटीरियल डायलॉग का इस्तेमाल करेंगे. डायलॉग बॉक्स में, हमारे पास दो फ़ील्ड वाला एक फ़ॉर्म होगा: टाइटल और जानकारी. जब उपयोगकर्ता "टास्क जोड़ें" बटन पर क्लिक करेगा, तब हम डायलॉग खोलेंगे. इसके बाद, जब उपयोगकर्ता फ़ॉर्म सबमिट करेगा, तब हम नई बनाई गई टास्क को todo सूची में जोड़ देंगे.

आइए, AppComponent में इस सुविधा को लागू करने के बारे में खास जानकारी देखें:

src/app/app.component.ts

...
import { MatDialog } from '@angular/material/dialog';

@Component(...)
export class AppComponent {
  ...

  constructor(private dialog: MatDialog) {}

  newTask(): void {
    const dialogRef = this.dialog.open(TaskDialogComponent, {
      width: '270px',
      data: {
        task: {},
      },
    });
    dialogRef
      .afterClosed()
      .subscribe((result: TaskDialogResult|undefined) => {
        if (!result) {
          return;
        }
        this.todo.push(result.task);
      });
  }
}

हम एक कंस्ट्रक्टर का एलान करते हैं, जिसमें हम MatDialog क्लास को इंजेक्ट करते हैं. newTask में हम:

  • हम TaskDialogComponent का इस्तेमाल करके एक नया डायलॉग खोलेंगे. इसके बारे में हम कुछ समय बाद बताएंगे.
  • यह तय करना कि डायलॉग की चौड़ाई 270px. होनी चाहिए
  • डायलॉग को डेटा के तौर पर एक खाली टास्क पास करें. TaskDialogComponent में, हमें इस डेटा ऑब्जेक्ट का रेफ़रंस मिल जाएगा.
  • हम क्लोज़ इवेंट के लिए सदस्यता लेते हैं और result ऑब्जेक्ट से टास्क को todo कलेक्शन में जोड़ते हैं.

यह काम करे, इसके लिए हमें सबसे पहले AppModule में MatDialogModule को इंपोर्ट करना होगा:

src/app/app.module.ts

...
import { MatDialogModule } from '@angular/material/dialog';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...
    MatDialogModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

अब TaskDialogComponent बनाएं. src/app डायरेक्ट्री पर जाएं और यह कमांड चलाएं:

ng generate component task-dialog

इसकी सुविधा लागू करने के लिए, पहले src/app/task-dialog/task-dialog.component.html खोलें और इसके कॉन्टेंट को इससे बदलें:

src/app/task-dialog/task-dialog.component.html

<mat-form-field>
  <mat-label>Title</mat-label>
  <input matInput cdkFocusInitial [(ngModel)]="data.task.title" />
</mat-form-field>

<mat-form-field>
  <mat-label>Description</mat-label>
  <textarea matInput [(ngModel)]="data.task.description"></textarea>
</mat-form-field>

<div mat-dialog-actions>
  <button mat-button [mat-dialog-close]="{ task: data.task }">OK</button>
  <button mat-button (click)="cancel()">Cancel</button>
</div>

ऊपर दिए गए टेंप्लेट में, हमने title और description के लिए दो फ़ील्ड वाला एक फ़ॉर्म बनाया है. हम cdkFocusInput डायरेक्टिव का इस्तेमाल करते हैं, ताकि जब उपयोगकर्ता डायलॉग खोले, तो title इनपुट पर अपने-आप फ़ोकस हो जाए.

ध्यान दें कि टेंप्लेट में, हम कॉम्पोनेंट की data प्रॉपर्टी को कैसे रेफ़रंस करते हैं. यह वही data होगा जिसे हम AppComponent में dialog के open तरीके को पास करते हैं. जब उपयोगकर्ता, टास्क के टाइटल और ब्यौरे वाले फ़ील्ड में बदलाव करता है, तब हम ngModel के साथ दो-तरफ़ा डेटा बाइंडिंग का इस्तेमाल करके, टास्क के टाइटल और ब्यौरे को अपडेट करते हैं.

जब उपयोगकर्ता 'ठीक है' बटन पर क्लिक करता है, तो हम नतीजे { task: data.task } को अपने-आप वापस कर देते हैं. यह वह टास्क है जिसे हमने ऊपर दिए गए टेंप्लेट में फ़ॉर्म फ़ील्ड का इस्तेमाल करके बदला है.

अब कॉम्पोनेंट के कंट्रोलर को लागू करें:

src/app/task-dialog/task-dialog.component.ts

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Task } from '../task/task';

@Component({
  selector: 'app-task-dialog',
  templateUrl: './task-dialog.component.html',
  styleUrls: ['./task-dialog.component.css'],
})
export class TaskDialogComponent {
  private backupTask: Partial<Task> = { ...this.data.task };

  constructor(
    public dialogRef: MatDialogRef<TaskDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: TaskDialogData
  ) {}

  cancel(): void {
    this.data.task.title = this.backupTask.title;
    this.data.task.description = this.backupTask.description;
    this.dialogRef.close(this.data);
  }
}

TaskDialogComponent में हम डायलॉग का रेफ़रंस डालते हैं, ताकि हम उसे बंद कर सकें. साथ ही, हम MAT_DIALOG_DATA टोकन से जुड़े सेवा देने वाले व्यक्ति या कंपनी की वैल्यू भी डालते हैं. यह वह डेटा ऑब्जेक्ट है जिसे हमने ऊपर दिए गए AppComponent में open तरीके से पास किया था. हम backupTask प्राइवेट प्रॉपर्टी का भी एलान करते हैं. यह उस टास्क की कॉपी होती है जिसे हमने डेटा ऑब्जेक्ट के साथ पास किया था.

जब उपयोगकर्ता 'रद्द करें' बटन दबाता है, तो हम this.data.task की बदली हुई प्रॉपर्टी को पहले जैसा कर देते हैं. साथ ही, हम डायलॉग बॉक्स को बंद कर देते हैं और this.data को नतीजे के तौर पर पास कर देते हैं.

हमने दो टाइप का रेफ़रंस दिया है, लेकिन अब तक उन्हें तय नहीं किया है - TaskDialogData और TaskDialogResult. src/app/task-dialog/task-dialog.component.ts में, फ़ाइल के सबसे नीचे ये एलान जोड़ें:

src/app/task-dialog/task-dialog.component.ts

...
export interface TaskDialogData {
  task: Partial<Task>;
  enableDelete: boolean;
}

export interface TaskDialogResult {
  task: Task;
  delete?: boolean;
}

सुविधा को तैयार करने से पहले, हमें AppModule में कुछ मॉड्यूल इंपोर्ट करने होंगे!

src/app/app.module.ts

...
import { MatInputModule } from '@angular/material/input';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...
    MatInputModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

अब "टास्क जोड़ें" बटन पर क्लिक करने पर, आपको यह यूज़र इंटरफ़ेस दिखेगा:

33bcb987fade2a87.png

7. ऐप्लिकेशन की स्टाइल को बेहतर बनाना

ऐप्लिकेशन को ज़्यादा आकर्षक बनाने के लिए, हम इसके लेआउट को बेहतर बनाएंगे. इसके लिए, हम इसकी स्टाइल में थोड़ा बदलाव करेंगे. हमें स्विमलेन को एक-दूसरे के बगल में रखना है. हमें "टास्क जोड़ें" बटन और खाली सूची के लेबल में कुछ छोटे-मोटे बदलाव भी करने हैं.

src/app/app.component.css खोलें और सबसे नीचे ये स्टाइल जोड़ें:

src/app/app.component.css

mat-toolbar {
  margin-bottom: 20px;
}

mat-toolbar > span {
  margin-left: 10px;
}

.content-wrapper {
  max-width: 1400px;
  margin: auto;
}

.container-wrapper {
  display: flex;
  justify-content: space-around;
}

.container {
  width: 400px;
  margin: 0 25px 25px 0;
}

.list {
  border: solid 1px #ccc;
  min-height: 60px;
  border-radius: 4px;
}

app-new-task {
  margin-bottom: 30px;
}

.empty-label {
  font-size: 2em;
  padding-top: 10px;
  text-align: center;
  opacity: 0.2;
}

ऊपर दिए गए स्निपेट में, हमने टूलबार और उसके लेबल के लेआउट में बदलाव किया है. हम यह भी पक्का करते हैं कि कॉन्टेंट हॉरिज़ॉन्टल तौर पर अलाइन हो. इसके लिए, हम उसकी चौड़ाई को 1400px और मार्जिन को auto पर सेट करते हैं. इसके बाद, फ़्लेक्सबॉक्स का इस्तेमाल करके, हम स्विमलेन को एक-दूसरे के बगल में रखते हैं. आखिर में, हम टास्क और खाली सूचियों को विज़ुअलाइज़ करने के तरीके में कुछ बदलाव करते हैं.

ऐप्लिकेशन के फिर से लोड होने के बाद, आपको यह यूज़र इंटरफ़ेस दिखेगा:

69225f0b1aa5cb50.png

हमने अपने ऐप्लिकेशन की स्टाइल को काफ़ी हद तक बेहतर बनाया है. हालांकि, अब भी हमें एक समस्या का सामना करना पड़ रहा है. यह समस्या तब होती है, जब हम टास्क को एक जगह से दूसरी जगह ले जाते हैं:

f9aae712027624af.png

"दूध खरीदो" टास्क को खींचकर ले जाते समय, हमें एक ही टास्क के दो कार्ड दिखते हैं. एक वह जिसे हम खींचकर ले जा रहे हैं और दूसरा स्विमलेन में मौजूद कार्ड. Angular CDK हमें सीएसएस क्लास के नाम देता है. इनका इस्तेमाल करके, हम इस समस्या को ठीक कर सकते हैं.

src/app/app.component.css में सबसे नीचे, स्टाइल बदलने की सुविधा जोड़ें:

src/app/app.component.css

.cdk-drag-animating {
  transition: transform 250ms;
}

.cdk-drag-placeholder {
  opacity: 0;
}

किसी एलिमेंट को खींचते समय, Angular CDK का ड्रैग ऐंड ड्रॉप फ़ीचर, उस एलिमेंट का क्लोन बना देता है. इसके बाद, उसे उस जगह पर डाल देता है जहां हमें ओरिजनल एलिमेंट को छोड़ना है. यह पक्का करने के लिए कि यह एलिमेंट न दिखे, हम cdk-drag-placeholder क्लास में ओपैसिटी प्रॉपर्टी सेट करते हैं. सीडीके, इस क्लास को प्लेसहोल्डर में जोड़ेगा.

इसके अलावा, जब हम किसी एलिमेंट को छोड़ते हैं, तो CDK cdk-drag-animating क्लास जोड़ता है. किसी एलिमेंट को सीधे तौर पर स्नैप करने के बजाय, स्मूद ऐनिमेशन दिखाने के लिए, हम 250ms अवधि वाला ट्रांज़िशन तय करते हैं.

हम अपने टास्क की स्टाइल में कुछ छोटे-मोटे बदलाव भी करना चाहते हैं. task.component.css में, होस्ट एलिमेंट के डिसप्ले को block पर सेट करें और कुछ मार्जिन सेट करें:

src/app/task/task.component.css

:host {
  display: block;
}

.item {
  margin-bottom: 10px;
  cursor: pointer;
}

8. मौजूदा टास्क में बदलाव करना और उन्हें मिटाना

मौजूदा टास्क में बदलाव करने और उन्हें हटाने के लिए, हम पहले से लागू की गई ज़्यादातर सुविधाओं का फिर से इस्तेमाल करेंगे! जब उपयोगकर्ता किसी टास्क पर दो बार क्लिक करता है, तब हम TaskDialogComponent खोलेंगे. साथ ही, फ़ॉर्म में मौजूद दो फ़ील्ड में टास्क के title और description की जानकारी भरेंगे.

हम TaskDialogComponent में, मिटाएं बटन भी जोड़ेंगे. जब उपयोगकर्ता इस पर क्लिक करेगा, तब हम मिटाने का निर्देश भेजेंगे. यह निर्देश AppComponent में दिखेगा.

हमें TaskDialogComponent में सिर्फ़ एक बदलाव करना है. यह बदलाव इसके टेंप्लेट में करना है:

src/app/task-dialog/task-dialog.component.html

<mat-form-field>
 ...
</mat-form-field>

<div mat-dialog-actions>
  ...
  <button
    *ngIf="data.enableDelete"
    mat-fab
    color="primary"
    aria-label="Delete"
    [mat-dialog-close]="{ task: data.task, delete: true }">
    <mat-icon>delete</mat-icon>
  </button>
</div>

इस बटन पर, कॉन्टेंट मिटाने का आइकॉन दिखता है. जब उपयोगकर्ता इस पर क्लिक करेगा, तब हम डायलॉग बॉक्स को बंद कर देंगे और ऑब्जेक्ट लिटरल { task: data.task, delete: true } को नतीजे के तौर पर पास कर देंगे. यह भी ध्यान दें कि हमने mat-fab का इस्तेमाल करके बटन को गोल बनाया है. साथ ही, इसके रंग को प्राइमरी के तौर पर सेट किया है. इसके अलावा, इसे सिर्फ़ तब दिखाया जाता है, जब डायलॉग डेटा में मिटाने की सुविधा चालू हो.

बदलाव करने और मिटाने की सुविधा को लागू करने का बाकी काम AppComponent में किया जाता है. इसके editTask तरीके की जगह यह तरीका इस्तेमाल करें:

src/app/app.component.ts

@Component({ ... })
export class AppComponent {
  ...
  editTask(list: 'done' | 'todo' | 'inProgress', task: Task): void {
    const dialogRef = this.dialog.open(TaskDialogComponent, {
      width: '270px',
      data: {
        task,
        enableDelete: true,
      },
    });
    dialogRef.afterClosed().subscribe((result: TaskDialogResult|undefined) => {
      if (!result) {
        return;
      }
      const dataList = this[list];
      const taskIndex = dataList.indexOf(task);
      if (result.delete) {
        dataList.splice(taskIndex, 1);
      } else {
        dataList[taskIndex] = task;
      }
    });
  }
  ...
}

आइए, editTask तरीके के आर्ग्युमेंट देखें:

  • टाइप 'done' | 'todo' | 'inProgress', की सूची. यह एक स्ट्रिंग लिटरल यूनियन टाइप है. इसमें ऐसी वैल्यू होती हैं जो अलग-अलग स्विमलेन से जुड़ी प्रॉपर्टी के हिसाब से होती हैं.
  • यह वह टास्क है जिसमें हमें बदलाव करना है.

तरीके के मुख्य हिस्से में, हम सबसे पहले TaskDialogComponent का इंस्टेंस खोलते हैं. इसके data के तौर पर, हम एक ऑब्जेक्ट लिटरल पास करते हैं. इससे हमें उस टास्क के बारे में पता चलता है जिसमें हमें बदलाव करना है. साथ ही, यह फ़ॉर्म में मौजूद 'बदलाव करें' बटन को भी चालू करता है. इसके लिए, enableDelete प्रॉपर्टी को true पर सेट किया जाता है.

डायलॉग से नतीजा मिलने पर, हम दो स्थितियों को मैनेज करते हैं:

  • जब delete फ़्लैग को true पर सेट किया जाता है (यानी, जब उपयोगकर्ता ने 'मिटाएं' बटन दबाया हो), तो हम टास्क को उससे जुड़ी सूची से हटा देते हैं.
  • इसके अलावा, हम दिए गए इंडेक्स पर मौजूद टास्क को, डायलॉग के नतीजे से मिले टास्क से बदल देते हैं.

9. नया Firebase प्रोजेक्ट बनाना

अब, एक नया Firebase प्रोजेक्ट बनाते हैं!

  • Firebase Console पर जाएं.
  • "KanbanFire" नाम का नया प्रोजेक्ट बनाएं.

10. प्रोजेक्ट में Firebase जोड़ना

इस सेक्शन में, हम अपने प्रोजेक्ट को Firebase के साथ इंटिग्रेट करेंगे! Firebase टीम, @angular/fire पैकेज उपलब्ध कराती है. इससे दोनों टेक्नोलॉजी को इंटिग्रेट किया जा सकता है. अपने ऐप्लिकेशन में Firebase की सुविधा जोड़ने के लिए, अपने वर्कस्पेस की रूट डायरेक्ट्री खोलें और यह कमांड चलाएं:

ng add @angular/fire

इस कमांड से @angular/fire पैकेज इंस्टॉल हो जाता है और आपसे कुछ सवाल पूछे जाते हैं. आपको अपने टर्मिनल में कुछ ऐसा दिखेगा:

9ba88c0d52d18d0.png

इस बीच, इंस्टॉलर एक ब्राउज़र विंडो खोलता है, ताकि आप अपने Firebase खाते से पुष्टि कर सकें. आखिर में, यह आपसे कोई Firebase प्रोजेक्ट चुनने के लिए कहता है और आपकी डिस्क पर कुछ फ़ाइलें बनाता है.

इसके बाद, हमें एक Firestore डेटाबेस बनाना होगा! "Cloud Firestore" में जाकर, "डेटाबेस बनाएं" पर क्लिक करें.

1e4a08b5a2462956.png

इसके बाद, टेस्ट मोड में डेटाबेस बनाएं:

ac1181b2c32049f9.png

आखिर में, कोई देश/इलाका चुनें:

34bb94cc542a0597.png

अब सिर्फ़ Firebase कॉन्फ़िगरेशन को अपने एनवायरमेंट में जोड़ना बाकी है. आपको Firebase कंसोल में प्रोजेक्ट का कॉन्फ़िगरेशन दिखेगा.

  • प्रोजेक्ट की खास जानकारी के बगल में मौजूद गियर आइकॉन पर क्लिक करें.
  • प्रोजेक्ट सेटिंग चुनें.

c8253a20031de8a9.png

"आपके ऐप्लिकेशन" में जाकर, कोई "वेब ऐप्लिकेशन" चुनें:

428a1abcd0f90b23.png

इसके बाद, अपने ऐप्लिकेशन को रजिस्टर करें और पक्का करें कि आपने "Firebase Hosting" को चालू किया हो:

586e44cb27dd8f39.png

"ऐप्लिकेशन रजिस्टर करें" पर क्लिक करने के बाद, अपने कॉन्फ़िगरेशन को src/environments/environment.ts में कॉपी किया जा सकता है:

e30f142d79cecf8f.png

आखिर में, आपकी कॉन्फ़िगरेशन फ़ाइल ऐसी दिखनी चाहिए:

src/environments/environment.ts

export const environment = {
  production: false,
  firebase: {
    apiKey: '<your-key>',
    authDomain: '<your-project-authdomain>',
    databaseURL: '<your-database-URL>',
    projectId: '<your-project-id>',
    storageBucket: '<your-storage-bucket>',
    messagingSenderId: '<your-messaging-sender-id>'
  }
};

11. डेटा को Firestore में ले जाना

हमने Firebase SDK टूल सेट अप कर लिया है. अब @angular/fire का इस्तेमाल करके, अपने डेटा को Firestore में ले जाते हैं! सबसे पहले, AppModule में ज़रूरी मॉड्यूल इंपोर्ट करें:

src/app/app.module.ts

...
import { environment } from 'src/environments/environment';
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';

@NgModule({
  declarations: [AppComponent, TaskDialogComponent, TaskComponent],
  imports: [
    ...
    AngularFireModule.initializeApp(environment.firebase),
    AngularFirestoreModule
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

हम Firestore का इस्तेमाल करेंगे. इसलिए, हमें AppComponent के कंस्ट्रक्टर में AngularFirestore को इंजेक्ट करना होगा:

src/app/app.component.ts

...
import { AngularFirestore } from '@angular/fire/firestore';

@Component({...})
export class AppComponent {
  ...
  constructor(private dialog: MatDialog, private store: AngularFirestore) {}
  ...
}

इसके बाद, हम स्विमलेन ऐरे को शुरू करने के तरीके को अपडेट करते हैं:

src/app/app.component.ts

...

@Component({...})
export class AppComponent {
  todo = this.store.collection('todo').valueChanges({ idField: 'id' }) as Observable<Task[]>;
  inProgress = this.store.collection('inProgress').valueChanges({ idField: 'id' }) as Observable<Task[]>;
  done = this.store.collection('done').valueChanges({ idField: 'id' }) as Observable<Task[]>;
  ...
}

यहां हम AngularFirestore का इस्तेमाल करके, डेटाबेस से सीधे तौर पर कलेक्शन का कॉन्टेंट पाते हैं. ध्यान दें कि valueChanges, ऐरे के बजाय observable दिखाता है. साथ ही, हम यह भी बताते हैं कि इस कलेक्शन में मौजूद दस्तावेज़ों के लिए आईडी फ़ील्ड को id कहा जाना चाहिए, ताकि यह Task इंटरफ़ेस में इस्तेमाल किए गए नाम से मेल खाए. valueChanges से मिला ऑब्ज़र्वेबल, टास्क के कलेक्शन को तब भेजता है, जब उसमें कोई बदलाव होता है.

हम ऐरे के बजाय ऑब्ज़र्वेबल के साथ काम कर रहे हैं. इसलिए, हमें टास्क जोड़ने, हटाने, और उनमें बदलाव करने के तरीके को अपडेट करना होगा. साथ ही, हमें टास्क को स्विमलेन के बीच ले जाने की सुविधा को भी अपडेट करना होगा. हम इन-मेमोरी ऐरे में बदलाव करने के बजाय, डेटाबेस में डेटा अपडेट करने के लिए Firebase SDK टूल का इस्तेमाल करेंगे.

सबसे पहले, देखते हैं कि आइटम का क्रम बदलने पर वह कैसा दिखेगा. src/app/app.component.ts में drop तरीके को इससे बदलें:

src/app/app.component.ts

drop(event: CdkDragDrop<Task[]>): void {
  if (event.previousContainer === event.container) {
    return;
  }
  const item = event.previousContainer.data[event.previousIndex];
  this.store.firestore.runTransaction(() => {
    const promise = Promise.all([
      this.store.collection(event.previousContainer.id).doc(item.id).delete(),
      this.store.collection(event.container.id).add(item),
    ]);
    return promise;
  });
  transferArrayItem(
    event.previousContainer.data,
    event.container.data,
    event.previousIndex,
    event.currentIndex
  );
}

ऊपर दिए गए स्निपेट में, नए कोड को हाइलाइट किया गया है. किसी टास्क को मौजूदा स्विमलेन से टारगेट स्विमलेन में ले जाने के लिए, हम टास्क को पहले कलेक्शन से हटाकर दूसरे कलेक्शन में जोड़ेंगे. हमें दो कार्रवाइयां करनी हैं, लेकिन हम चाहते हैं कि वे एक ही कार्रवाई के तौर पर दिखें.इसका मतलब है कि हमें कार्रवाई को ऐटॉमिक बनाना है. इसलिए, हम उन्हें Firestore लेन-देन में चलाते हैं.

अब, Firestore का इस्तेमाल करने के लिए, editTask तरीके को अपडेट करते हैं! डायलॉग बंद करने वाले हैंडलर में, हमें कोड की इन लाइनों को बदलना होगा:

src/app/app.component.ts

...
dialogRef.afterClosed().subscribe((result: TaskDialogResult|undefined) => {
  if (!result) {
    return;
  }
  if (result.delete) {
    this.store.collection(list).doc(task.id).delete();
  } else {
    this.store.collection(list).doc(task.id).update(task);
  }
});
...

हम Firestore SDK का इस्तेमाल करके, उस टास्क से जुड़े दस्तावेज़ को ऐक्सेस करते हैं जिसमें हमें बदलाव करना है. इसके बाद, हम उसे मिटा देते हैं या अपडेट कर देते हैं.

आखिर में, हमें नए टास्क बनाने के तरीके को अपडेट करना होगा. this.todo.push('task') को इससे बदलें: this.store.collection('todo').add(result.task).

ध्यान दें कि अब हमारे कलेक्शन, ऐरे नहीं बल्कि ऑब्ज़र्वेबल हैं. इन्हें विज़ुअलाइज़ करने के लिए, हमें AppComponent के टेंप्लेट को अपडेट करना होगा. बस todo, inProgress, और done प्रॉपर्टी के हर ऐक्सेस को क्रमशः todo | async, inProgress | async, और done | async से बदलें.

एसिंक पाइप, कलेक्शन से जुड़े ऑब्ज़र्वेबल को अपने-आप सब्सक्राइब कर लेता है. जब ऑब्ज़र्वेबल नई वैल्यू भेजते हैं, तो Angular अपने-आप बदलाव का पता लगाने की प्रोसेस शुरू कर देता है. साथ ही, भेजे गए ऐरे को प्रोसेस करता है.

उदाहरण के लिए, आइए देखते हैं कि हमें todo स्विमलेन में क्या बदलाव करने होंगे:

src/app/app.component.html

<mat-card
  cdkDropList
  id="todo"
  #todoList="cdkDropList"
  [cdkDropListData]="todo | async"
  [cdkDropListConnectedTo]="[doneList, inProgressList]"
  (cdkDropListDropped)="drop($event)"
  class="list">
  <p class="empty-label" *ngIf="(todo | async)?.length === 0">Empty list</p>
  <app-task (edit)="editTask('todo', $event)" *ngFor="let task of todo | async" cdkDrag [task]="task"></app-task>
</mat-card>

जब हम डेटा को cdkDropList डायरेक्टिव को पास करते हैं, तब हम एसिंक पाइप लागू करते हैं. *ngIf डायरेक्टिव के अंदर भी यही होता है. हालांकि, ध्यान दें कि length प्रॉपर्टी को ऐक्सेस करते समय, हम वैकल्पिक चेनिंग (इसे Angular में सेफ़ नेविगेशन ऑपरेटर भी कहा जाता है) का इस्तेमाल करते हैं. इससे यह पक्का किया जाता है कि अगर todo | async, null या undefined नहीं है, तो हमें रनटाइम गड़बड़ी न मिले.

अब यूज़र इंटरफ़ेस में कोई नया टास्क बनाने और Firestore खोलने पर, आपको कुछ ऐसा दिखेगा:

dd7ee20c0a10ebe2.png

12. ऑप्टिमिस्टिक अपडेट को बेहतर बनाना

फ़िलहाल, हम ऐप्लिकेशन में ऑप्टिमिस्टिक अपडेट कर रहे हैं. हमारे पास Firestore में डेटा का सोर्स है. हालांकि, हमारे पास टास्क की लोकल कॉपी भी हैं. जब कलेक्शन से जुड़े किसी भी ऑब्ज़र्वेबल से डेटा मिलता है, तो हमें टास्क की एक ऐरे मिलती है. जब उपयोगकर्ता की किसी कार्रवाई से स्थिति में बदलाव होता है, तो हम सबसे पहले स्थानीय वैल्यू अपडेट करते हैं. इसके बाद, बदलाव को Firestore पर भेजते हैं.

जब हम किसी टास्क को एक स्विमलेन से दूसरी स्विमलेन में ले जाते हैं, तो हम transferArrayItem, को शुरू करते हैं. यह हर स्विमलेन में मौजूद टास्क को दिखाने वाली ऐरे के लोकल इंस्टेंस पर काम करता है. Firebase SDK, इन ऐरे को इम्यूटेबल के तौर पर देखता है. इसका मतलब है कि अगली बार जब Angular बदलाव का पता लगाएगा, तब हमें इनके नए इंस्टेंस मिलेंगे. इससे टास्क ट्रांसफ़र करने से पहले, पिछली स्थिति रेंडर हो जाएगी.

इसके साथ ही, हम Firestore अपडेट को ट्रिगर करते हैं. साथ ही, Firebase SDK टूल सही वैल्यू के साथ अपडेट को ट्रिगर करता है. इसलिए, कुछ मिलीसेकंड में यूज़र इंटरफ़ेस अपनी सही स्थिति में आ जाएगा. इससे, अभी ट्रांसफ़र किया गया टास्क पहली सूची से दूसरी सूची में चला जाता है. नीचे दिए गए GIF में इसे अच्छी तरह से देखा जा सकता है:

70b946eebfa6f316.gif

इस समस्या को हल करने का सही तरीका, ऐप्लिकेशन के हिसाब से अलग-अलग होता है. हालांकि, सभी मामलों में हमें यह पक्का करना होता है कि जब तक हमारा डेटा अपडेट नहीं हो जाता, तब तक हम एक जैसी स्थिति बनाए रखें.

हम BehaviorSubject का इस्तेमाल कर सकते हैं. यह valueChanges से मिले ओरिजनल ऑब्ज़र्वर को रैप करता है. BehaviorSubject, एक ऐसा बदलाव किया जा सकने वाला ऐरे बनाए रखता है जो transferArrayItem से मिले अपडेट को सेव करता है.

समस्या को ठीक करने के लिए, हमें सिर्फ़ AppComponent को अपडेट करना होगा:

src/app/app.component.ts

...
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { BehaviorSubject } from 'rxjs';


const getObservable = (collection: AngularFirestoreCollection<Task>) => {
  const subject = new BehaviorSubject<Task[]>([]);
  collection.valueChanges({ idField: 'id' }).subscribe((val: Task[]) => {
    subject.next(val);
  });
  return subject;
};

@Component(...)
export class AppComponent {
  todo = getObservable(this.store.collection('todo')) as Observable<Task[]>;
  inProgress = getObservable(this.store.collection('inProgress')) as Observable<Task[]>;
  done = getObservable(this.store.collection('done')) as Observable<Task[]>;
...
}

ऊपर दिए गए स्निपेट में, हमने सिर्फ़ एक BehaviorSubject बनाया है. यह हर बार तब वैल्यू देता है, जब कलेक्शन से जुड़ा ऑब्ज़र्वेबल बदलता है.

सब कुछ उम्मीद के मुताबिक काम करता है, क्योंकि BehaviorSubject, बदलाव का पता लगाने के लिए किए गए सभी अनुरोधों में ऐरे का फिर से इस्तेमाल करता है. साथ ही, यह सिर्फ़ तब अपडेट होता है, जब हमें Firestore से कोई नई वैल्यू मिलती है.

13. ऐप्लिकेशन डिप्लॉय करना

अपने ऐप्लिकेशन को डिप्लॉय करने के लिए, हमें सिर्फ़ यह कमांड चलानी होगी:

ng deploy

इस कमांड से:

  1. अपने ऐप्लिकेशन को प्रोडक्शन कॉन्फ़िगरेशन के साथ बनाएं. साथ ही, कंपाइल-टाइम ऑप्टिमाइज़ेशन लागू करें.
  2. अपने ऐप्लिकेशन को Firebase Hosting पर डिप्लॉय करें.
  3. एक यूआरएल जनरेट करो, ताकि हम नतीजे की झलक देख सकें.

14. बधाई हो

बधाई हो, आपने Angular और Firebase की मदद से Kanban बोर्ड बना लिया है!

आपने एक यूज़र इंटरफ़ेस बनाया है. इसमें तीन कॉलम हैं. ये अलग-अलग टास्क की स्थिति दिखाते हैं. आपने Angular CDK का इस्तेमाल करके, कॉलम में टास्क को खींचकर छोड़ने की सुविधा लागू की है. इसके बाद, आपने Angular Material का इस्तेमाल करके, नए टास्क बनाने और मौजूदा टास्क में बदलाव करने के लिए एक फ़ॉर्म बनाया. इसके बाद, आपने @angular/fire का इस्तेमाल करना सीखा और ऐप्लिकेशन की सभी स्थितियों को Firestore में ले गए. आखिर में, आपने अपने ऐप्लिकेशन को Firebase Hosting पर डिप्लॉय किया.

आगे क्या करना है?

ध्यान दें कि हमने ऐप्लिकेशन को टेस्ट कॉन्फ़िगरेशन का इस्तेमाल करके डिप्लॉय किया है. अपने ऐप्लिकेशन को प्रोडक्शन में डिप्लॉय करने से पहले, पक्का करें कि आपने सही अनुमतियां सेट अप की हों. ऐसा करने का तरीका यहां बताया गया है.

फ़िलहाल, हम किसी स्विमलेन में मौजूद अलग-अलग टास्क के क्रम को बनाए नहीं रखते. इसे लागू करने के लिए, टास्क वाले दस्तावेज़ में ऑर्डर फ़ील्ड का इस्तेमाल किया जा सकता है. साथ ही, इसके आधार पर क्रम से लगाया जा सकता है.

इसके अलावा, हमने सिर्फ़ एक उपयोगकर्ता के लिए कैनबन बोर्ड बनाया है. इसका मतलब है कि ऐप्लिकेशन खोलने वाले हर व्यक्ति के लिए, हमारे पास एक ही कैनबन बोर्ड है. अगर आपको अपने ऐप्लिकेशन के अलग-अलग उपयोगकर्ताओं के लिए अलग-अलग बोर्ड लागू करने हैं, तो आपको अपने डेटाबेस स्ट्रक्चर में बदलाव करना होगा. Firestore इस्तेमाल करने के सबसे सही तरीकों के बारे में जानने के लिए, यहां जाएं.