کامپوننت های داینامیک در angular با استفاده از TypeScript

کامپوننت‌ها به عنوان کلید اصلی نرم‌افزارهای مبتنی بر فریم‌ورک قدرتمند angular، محسوب می شوند. اگر به نام‌گذاری فایل‌های موجود در پوشه‌ی app توجه داشته باشید، تمامی اسامی با پسوند component. همراه هستند و این امر اهمیت مبحث کامپوننت‌ها را نشان می‌دهد. از این رو در این مقاله در مورد کامپوننت های داینامیک و نحوه ایجاد، حذف و set و get کردن مقادیر کامپوننت ها در angular آشنا می شویم.

کامپوننت های داینامیک در angular با استفاده از TypeScript

کامپوننت های داینامیک در angular با استفاده از TypeScript

امروز ما نحوه ایجاد کامپوننت های داینامیک در angular 2/4 ، نحوه set کردن مقادیر کنترل داینامیک و نحوه get کردن مقادیر از کامپوننت تولید شده داینامیک را گام به گام و با شرح هر مرحله به چالش کشیده ایم. کامپوننت های داینامیک آن کامپوننت هایی هستند که در زمان اجرا ایجاد می شوند. منظور از زمان اجرا این است که ما یک برنامه در حال اجرا داریم و زمانی که بخواهیم بعضی از کامپوننت ها را روی هر رویداد (event) مانند (رویداد کلیک) render کنیم، این کامپوننت، کامپوننت داینامیک نامیده می شود. بنابراین، این مقاله چهار چیز را در مورد کامپوننت های داینامیک در زیر شرح می دهد:

1. نحوه ایجاد کامپوننت داینامیک

2. نحوه حذف کردن کامپوننت داینامیک

3. نحوه set کردن مقادیر کنترل ها در کامپوننت داینامیک

4. نحوه get کردن مقادیر از کنترل های کامپوننت داینامیک.

مانند صفحه نمایشی که در زیر نشان داده شده است صفحه نمایشی ایجاد می کنیم. دو بخش در صفحه نمایش مشاهده می کنید، در بخش اول، ما یک دکمه Add New Skills داریم و بر روی آن کلیک می نماییم. با کلیک بر روی این دکمه،  یک کامپوننت داینامیک جدید که تعدادی کنترل مانند dropdown برای Skills، یک textbox برای وارد کردن مقادیرExperiences  و یک dropdown برای Rating دارد، برای ما ایجاد می شود. به غیر از این موارد، در نهایت ما یک دکمه cross  داریم که کامپوننت داینامیک را حذف می کند. در بخش اول یک بار دیتاها با استفاده از کنترل ها آماده خواهند شد و وقتی ما بر روی دکمه Save Skills کلیک می نماییم مقادیر کنترل ها get می شود و این مقادیر در بخش دوم نمایش داده می شود.

بنابراین در این صفحه نمایش، شما ابتدا نحوه ایجاد یک component داینامیک را با کلیک روی دکمه « Add New Skills»، چگونگی حذف component داینامیک را با کلیک روی دکمه  «Cross»خواهید دید. و همچنین جدا از این موارد خواهید دید که چطور مقادیر را برای component های داینامیک در angular را set و get کنیم.

بنابراین، بیایید یک برنامه Angular 5 را با استفاده از CLI در Visual Studio  ایجاد کنیم.

به اشتراک گذاشتن اطلاعات بین کامپوننت هایی که در یک سطح قرار دارند (sibling) با استفاده از Rxjs BehaviorSubject

از آنجا که امیدواریم شما قادر باشید یک پروژه  Angular 5 CLI  را در Visual Studio 2015/2017  یا Visual Studio Code ایجاد کنید، به مرحله بعدی می رویم. بنابراین، برای ایجاد کامپوننت داینامیک، به صورت عملی وارد کار می شویم.  پس از ایجاد  پروژه، "Index.html" را باز کنید و CDN  را به مسیر bootstrap اضافه کنید تا بتوانید از CSS بوت استرپ استفاده کنید. همانطور که می بینید، ما bootstrap.min.css  را درون تگ <head> اضافه کردیم.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>DynamicComponentDemo</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
</head>
<body>
  <app-root></app-root>
</body>
</html>

اکنون یک کامپوننت جدید که دارای سه کنترل drop-down برای Skills، یک textbox  برای Experiences  وdropdown  برای Rating است، ایجاد می کنیم. بنابراین ابتدا یک پوشه به نام "components" در مسیر برنامه ایجاد کنید و یک کامپوننت  با استفاده از دستور زیر تولید کنید.

ng g c SkillsRating --save

شما تنها یک بار قادر هستید که یک کامپوننت جدید تحت عنوان"SkillsRatingComponent" ایجاد کنید. فقط تنها کاری که انجام می دهید این کامپوننت ،"SkillsRatingComponent" را در قسمت اعلانات "AppModule" اضافه کنید. جدا از این،ما مجبوریم "FormsModule" یا "ReactiveFormsModule" را برای استفاده از کنترل های فرم import کنیم. اگر این کار را نکنید، چندین error برای کنترل های فرمها خواهید یافت.

AppModule.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { SkillsRatingComponent } from './components/skills-rating/skills-rating.component';


@NgModule({
  declarations: [
    AppComponent,
    SkillsRatingComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents:[SkillsRatingComponent]
})
export class AppModule { }

 

اکنون زمان تغییر دادن SkillsRatingComponent رسیده است. بنابراین، " SkillsRatingComponent "  را باز کرده و کد template  زیر را اضافه کنید. شما می توانید این موارد که ذکر می شود را در کد زیر مشاهده کنید، ما یکdropdown برای نشان دادن لیست skill های کاربر داریم، یک textbox برای experience کاربر ، یک dropdown  دیگر برای نشان دادن rating  ارائه شده توسط کاربر و در نهایت، یک دکمه برای حذف اطلاعات skills  ، به معنی حذف کامپوننت داینامیک داریم.

skills-rating-component.html

<div class="form-inline" style="margin: 10px;" id="row{{index}}">
  <b>Skills:</b>
  <select [(ngModel)]="selectedSkill" class="form-control">
    <option *ngFor="let item of skills" [value]="item">{{item}}</option>
  </select>

  <b>Experiences: </b>
  <input [(ngModel)]="yearsOfExperiences" type="text" class="form-control" style="margin-left:10px; margin-right:10px; width: 60px;">

  <b>Rating: </b>
  <select [(ngModel)]="selectedRating" class="form-control" style="width:70px;">
    <option *ngFor="let item of ratings" [value]="item">{{item}}</option>
  </select>

  <img src="../../../../../assets/remove.png" width="15" height="15" title="Remove" (click)="removeSkills()" />
</div>

پس از تزئین قالب SkillsRatingComponent  ، اکنون زمان تغییر دادن کامپوننت آن است. بنابراین  SkillsRatingComponent.ts را باز کنید و کدهای زیر را اضافه کنید. دراینجا قادر به مشاهده property های زیادی هستید که به معرفی آن ها می پردازیم و یک به یک آن ها را توضیح می دهیم. Property های reference از reference کامپوننت های داینامیک تولید شده حفاظت می کند، index تعداد شاخص برای کامپوننتهای داینامیک تولید شده است، selectedSkill،  yearsOfExperiences و selectedRating  به ترتیب مقادیر انتخاب شده برای skills ، experience  و rating  می باشند. جدا از این، ما دو property دیگر مانند skills و ratings داریم، که برای اتصال داده ها در dropdown استفاده می شود.

توجه داشته باشید، ما یک روش به نام removeSkills ()داریم که دکمه داینامیک انتخاب شده را با کلیک روی دکمه cross حذف می کند.

SkillsRatingComponent.ts

import { Component, OnInit, ElementRef } from '@angular/core';

@Component({
  selector: 'app-skills-rating',
  templateUrl: './skills-rating.component.html',
  styleUrls: ['./skills-rating.component.css']
})
export class SkillsRatingComponent implements OnInit {

  reference: any;
  index: number;

  selectedSkill: string;
  yearsOfExperiences: string = '0';
  selectedRating: string;

  skills: any = [];
  ratings: any = [];

  constructor(private elementRef: ElementRef) {
  }

  removeSkills() {
    this.reference.destroy();
  }

  ngOnInit() {
  }
}

بنابراین، اکنون ما کامپوننتی را که شرح دادیم آماده کرده ایم و قالبش با کلیک بر روی دکمه "Add New Skills"  به صورت داینامیک تولید می شود. بنابراین، به  app.component.html   بروید و کد زیر را داخل آن قرار دهید. در اینجا همانطور که مشاهده می نمایید، ما دو بخش داریم که قبلا در بالا بحث کردیم، برای اولین بار یک دکمه برای اضافه کردن skill های جدید همراه با یک #dynamicContianer وجود دارد. این container داینامیک یک container برای اضافه کردن همه کامپوننت های داینامیک تولید شده درون آن با کلیک کردن روی "add new skills"  است.

قسمت دوم فقط شامل یک تگ span  با شناسه «addSkills» است که تمام لیست skill های انتخاب شده را از قسمت اول با کلیک کردن روی دکمه Save Skills  نشان می دهد.

app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2 style="text-align: center;">Dynamic Components in Angular</h2>
    </div>
    <div class="col-md-7" style="border: 1px solid gray; height: 500px; padding: 10px;">
      <div style="float:left; margin-bottom:5px;" id="dynamicContainer">
        <button type="button" (click)="addNewSkills()" width="20" height="20" class="btn btn-primary" style=" margin-left:10px;"
          title="Add New Skills">Add New Skills</button>

        <!--Create container for Dynamic Components -->
        <div #dynamicContainer></div>

        <button type="button" (click)="saveSkills()" *ngIf="embeddedViews>0" width="20" height="20" class="btn btn-primary" style=" margin-left:10px;"
          title="Save Skills">Save Skills</button>
      </div>
    </div>
    <div class="col-md-5" style="border: 1px solid gray; height: 500px;">
      <h3>Skills Rating By User</h3>
      <span id="addedSkills"></span>
    </div>
  </div>
</div>
<router-outlet></router-outlet>

در نهایت، ما باید AppComponent.ts را به جایی که ما داده ها را برای اتصال با کنترل های داینامیکی تولید شده آماده می کنیم، جابه جا کنیم.

بنابراین، اول از همه، instance  ای از dynamicContainer با استفاده از ViewContainerRef بسازید  و از این طریق، تزریق وابستگی را برای کامپوننت FactoryResolver  ایجاد کنید. کامپوننت FactoryResolver  برای حل کامپوننت ای است که در زمان اجرا منتقل می شود. هنگامی که کامپوننت حل و فصل خواهد شد، کامپوننت داینامیک را می توان با استفاده از متد CreateComponent که کامپوننت های حل شده(resolved) را به عنوان آرگومان می گیرد، در داخل container  ایجاد کرد.

let comp = this.comFacResolver.resolveComponentFactory(SkillsRatingComponent);
let dynamicComp = this.container.createComponent(comp);

هنگامی که کامپوننت داینامیک تولید می شود، وقت آن است که کنترل های خود را در زمان اجرا متصل کنیم. ابتدا همه مجموعه reference ها  را با شیء  (object) dynamicComp تعیین کنید. سپس شما قادر به مشاهده کد زیر هستید، ما هر property را تنظیم و با مقادیرشان مرتبط می سازیم.

dynamicComp.instance.reference = dynamicComp;

dynamicComp.instance.skills = this.skills;
dynamicComp.instance.ratings = this.ratings;
dynamicComp.instance.index = this.rowId;

dynamicComp.instance.selectedSkill = '';
dynamicComp.instance.yearsOfExperiences = '0';
dynamicComp.instance.selectedRating = this.ratings[0];

تا بالا ما دیدیم که چگونه کامپوننت داینامیک را ایجاد ، حذف و مقادیر کامپوننت داینامیک را set کنیم. اما سوالی که مطرح می شود این است که چگونه می توان مقادیر کنترل را از کامپوننت های تولید شده به صورت داینامیک get کرد. با کلیک روی دکمه Save Skills، ابتدا باید instance  ای از container  ایجاد کنیم و سپس در داخل آن، ما می توانیم همه view  های جاسازی شده (embedded ) را پیدا کنیم که جزء کامپوننت مولد داینامیک نیست. بنابراین، برای حلقه استفاده می کنیم، می توانیم مقادیر را از هر view  جاسازی شده بدست آوریم و آن را به span قسمت دوم اضافه کنیم.

شما می توانید کل کد AppComponent را به صورت زیر در اختیار داشته باشید.

AppComponent.ts

import { Component, ViewContainerRef, ViewChild, ComponentFactoryResolver } from '@angular/core';
import { SkillsRatingComponent } from './components/skills-rating/skills-rating.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  @ViewChild('dynamicContainer', { read: ViewContainerRef }) container: ViewContainerRef;
  skills: any;
  ratings: any;
  rowId: number;
  data: string;
  embeddedViews: number = 0;

  constructor(private comFacResolver: ComponentFactoryResolver) {
  }

  addNewSkills() {
    let rows = document.getElementById("dynamicContainer");
    let rowIdIndex = rows.innerHTML.indexOf("row");
    if (rowIdIndex == -1) {
      this.rowId = 1;
    }

    this.skills = ['CSharp', '.Net Framework', 'Asp.Net', 'Asp.Net Core', 'Angular 1.x', 'Angular 2.x', 'Web API', 'Azure', 'Javascript', 'SQL'];
    this.ratings = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let comp = this.comFacResolver.resolveComponentFactory(SkillsRatingComponent);
    let dynamicComp = this.container.createComponent(comp);
    dynamicComp.instance.reference = dynamicComp;

    dynamicComp.instance.skills = this.skills;
    dynamicComp.instance.ratings = this.ratings;
    dynamicComp.instance.index = this.rowId;

    dynamicComp.instance.selectedSkill = '';
    dynamicComp.instance.yearsOfExperiences = '0';
    dynamicComp.instance.selectedRating = this.ratings[0];

    this.rowId += 1;

    let com = this.container;
    if (com !== undefined) {
      this.embeddedViews = com['_embeddedViews'].length;
    }

  }

  saveSkills() {
    let comp = this.container;
    this.data = "";
    (<HTMLSpanElement>document.getElementById("addedSkills")).innerText = "";

    if (comp !== undefined) {
      debugger;
      if (comp['_embeddedViews'].length != 0) {
        for (let i = 0; i < comp['_embeddedViews'].length; i++) {
          if (comp['_embeddedViews'][i] != undefined) {
            debugger;
            let selectedSkill = comp['_embeddedViews'][i].nodes[1].instance.selectedSkill == '' ? "NONE" : comp['_embeddedViews'][i].nodes[1].instance.selectedSkill;
            let yearsOfExperiences = comp['_embeddedViews'][i].nodes[1].instance.yearsOfExperiences == '' ? "NONE" : comp['_embeddedViews'][i].nodes[1].instance.yearsOfExperiences;
            let selectedRating = comp['_embeddedViews'][i].nodes[1].instance.selectedRating;

            this.data = 'User knows <b style="color:green;">' + selectedSkill + ' </b> from <b>' + yearsOfExperiences + ' years</b> , provided rating as <b>' + selectedRating + '</b>.';

            (<HTMLSpanElement>document.getElementById("addedSkills")).innerHTML += this.data;
            (<HTMLSpanElement>document.getElementById("addedSkills")).appendChild(document.createElement('br'));

          }
        }
      }
    }
  }
}

هنگامی که هر آنچه در بالا گفته شد را تنظیم کردید و پروژه را اجرا کردید، سپس همان صفحه ای که قبلا در بالا نشان دادیم را پیدا کنید. از اینجا، ما قادر به ایجاد و حذف کامپوننت داینامیک و set یا get کردن مقادیر کامپوننت های داینامیک می باشیم.