تا به اینجای کار ۶۰ درصد از مسیر یادگیری فریمورک انگولار ۴ را پشت سر گذاشتهاید و از این پس وارد مباحث تخصصیتر میشویم. در این جلسه قصد داریم مطلبی بسیار ارزشمند به نام Observable یا مشاهده کننده ها در انگولار ۴ را مورد بررسی قرار دهیم. توجه داشته باشید که مشاهده کنندهها و ناظرها (Observable & Observer) به عنوان مقدمهای برای ساخت نرمافزارهای وب سرویس میباشند بنابراین یادگیری آن را به دوستانی که هدفی بالاتر از طراحی یک وب سایت ساده دارند به شدت توصیه میشود.
قطعا تا به اینجای کار این سوال برای شما پیش آمده است که Observable چیست؟
Observable به عنوان یک شیء و منبع دادهی متنوع است. یعنی یک Observable شیء ای میباشد که انواع مختلف داده را درون خود ذخیره میکند و از پکیج Rxjs دریافت و به انگولار ۴ اضافه میشود. توجه داشته باشید که Observable به خودی خود در انگولار وجود ندارد. برای افزایش تمرکز شما یک دیاگرام در اختیارتان گذاشته تا ادامهی توضیحات را با این دیاگرام ارائه دهیم:
همانطور که در این دیاگرام مشاهده میکنید هر Observable دارای یک خط نگهداری اطلاعات از منبع است که این اطلاعات ممکن است به صورت یک رویداد (event) یا درخواست http ارسال شود. سپس یک Observer یا ناظر پس از خط نگهدرای اطلاعات ایجاد میشود که از طریق ۳ روش دادهها را مدیریت میکند:
در واقع با استفاده از کدنویسی مشخص میکنیم که کدام یک از روشها مد نظر ما است.
از Observable و Observer برای دادههای غیرهمزمان استفاده میشود. دادههای غیرهمزمان یا Async به دادههایی گفته میشود که زمان ارسال آنها نا مشخص است و معلوم نیست چقدر طول میکشد تا به دست ما برسد.
برای شروع این فصل ابتدا یک مثال که در پیوست انتهای همین صفحه در اختیار شما قرار گرفته است، صفحهای ایجاد کردهایم که شامل ۲ کاربر و یک صفحه اصلی است. ابتدا فایلهای مربوطه را دانلود کنید و سپس روی سرور لوکال خود اجرا نمایید. در صورتیکه اینکار را به درستی انجام داده باشید با تصویر زیر روبهرو خواهید شد:
در ابتدا کدهای موجود در فایل user.component.ts را برای شما تشریح میکنیم:
export class UserComponent implements OnInit { id: number; constructor(private route: ActivatedRoute) { } ngOnInit() { this.route.params .subscribe( (params: Params) => { this.id = +params['id']; } ); } }
همانطور که مشاهده میکنید از هوک ngOnInit استفاده کردهایم و پارامتر id که در آدرس قرار دارد را استخراج کرده و درون متغییری به نام id ذخیره میکنیم. توجه کنید که این پارامتر توسط یک دستور به نام routerLink در قالب user.component.html ارسال شده است. بنابراین در این مرحله اطلاعات به صورت کلیک روی یک لینک ارسال میشوند و روی خط نگهداری اطلاعات Observable قرار میگیرند. سپس یک observer به نام subscribe وجود دارد که این اطلاعات را به عنوان ناظر مورد بررسی قرار میدهد. همچنین درنظر داریم که متد subscribe به عنوان یک ناظر ۳ روش برای بازگردانی اطلاعات دارد. روش اول را که در مثال فوق مشاهده میکنید و برای کنترل دادههای نرمال است اما اگر بخواهیم روش ۲ و ۳ را به این متد اضافه کنیم باید مجموعه دستورهای زیر را اعمال کنیم:
ngOnInit() { this.route.params .subscribe( (params: Params) => { this.id = +params['id']; }, ()=>{ }, ()=>{ }, ); }
دو قسمت که به کدها اضافه شد به ترتیب برای error handling و completion handling است که در آینده به آنها خواهیم پرداخت. در ادامهی این مطلب حال میخواهیم Observable دلخواه خودمان را تولید کرده و مورد استفاده قرار دهیم.
برای ایجاد یک Observable دلخواه ابتدا باید یک ویژگی یا متغییر را ایجاد کنیمو سپس از عبارت Observable به همراه متدهای و helperهای آن بهره ببریم. در مثال زیر میخواهیم یک تایمر ایجاد کرده بگونهای که با استفاده از متد interval در هر ثانیه ۱ عدد شمارش کرده و رو به بالا این اعداد را به عنوان منبع داده درون Observable ذخیره و سپس توسط یک ناظر که در اینجا متد subscribe است نمایش داده شود. برای انجام اینکار کامپوننت home.component.ts را باز میکنیم و سپس دستورهای زیر را درون هوک ngOnInit قرار میدهیم:
ngOnInit() { const myNumber = Observable.interval(1000); myNumber.subscribe( (number: number)=>{ console.log(number) } ) }
حال اگر صفحه مرورگر خود را باز کنید و آدرس http://localhost:4200 را وارد نمایید. در صفحه console اعداد به صورت شمارشی از ۰ تا بی نهایت افزایش مییابد. یعنی یک منبع داده مستقیم به نام myNumber است که از نوع Observable بوده و اطلاعات را درون خود ذخیره میکند این اطلاعات با استفاده از متد interval هر ثانیه درون این منبع قرار گرفته و سپس توسط یک ناظر به نام subscribe و در روش اول یعنی Data Handling بررسی شده و در نتیجه نمایش داده میشود. اما این مثال یک مثال کاربردی نیست و تنها برای بیان مفهوم مشاهده کننده یا Observable مطرح شد. در ادامه به صورت تخصصیتر مشاهده کنندهها را مورد بررسی قرار میدهیم.
همانطور که در مثال قبل مشاهده کردید ما از یک متد از پیش ساخته شده به نام interval برای تولید یک ثانیه شمار استفاده کردیم اما در مثال بعدی که خدمت شما عزیزان ارائه خواهیم داد، قصد داریم یک ناظر جدید ایجاد کنیم تا به برنامه اطلاع دهیم که چه دادهای ارسال میشود و وقتی آن داده ارسال شد چه کاری انجام دهیم! بنابراین داریم:
const myObservable = Observable.create((observer: Observer)=>{ setTimeout( ()=>{ observer.next('First Package') } , 2000); setTimeout( ()=>{ observer.next('Second Package') } , 4000); setTimeout( ()=>{ observer.error('This Dose Not Work') } , 5000) });
همانطور که در این مثال مشاهده میکنید از دستور observer.next برای کنترل دادهها و از دستور observer.error برای مشاهده خطاها استفاده کردهایم. اما کار ما هنوز به اتمام نرسیده است. زیرا باید این Observable را با استفاده از دستور subscribe به اشتراک بگذاریم:
ngOnInit() { // const myNumber = Observable.interval(1000); // myNumber.subscribe( // (number: number)=>{ // console.log(number) // } // ) const myObservable = Observable.create((observer: Observer<string>)=>{ setTimeout( ()=>{ observer.next('First Package') } , 2000); setTimeout( ()=>{ observer.next('Second Package') } , 4000); setTimeout( ()=>{ observer.error('This Dose Not Work') } , 5000) }); myObservable.subscribe( (data: string)=>{console.log(data)}, (error: string)=>{console.log(error)}, ()=>{console.log("Project is Completed")} ) }
همانطور که ملاحظه کردید این دادههایی که توسط ناظر بررسی و کنترل میشوند با استفاده از متد subscribe نمایش داده خواهند شد. در این متد خط اول بیانگر روش اول است، خط دوم خطاها را بررسی می کند و در نهایت در خط سوم شما میتوانید پیغام مورد نظر را به هنگام اتمام فرآیند نمایش دهید. اما برای استفاده از آن باید دستور complete را به صورت زیر اجرا کنیم:
const myObservable = Observable.create((observer: Observer<string>)=>{ setTimeout( ()=>{ observer.next('First Package') } , 2000); setTimeout( ()=>{ observer.next('Second Package') } , 4000); setTimeout( ()=>{ observer.complete() } , 5000) setTimeout( ()=>{ observer.next('Three Package') } , 5000) }); myObservable.subscribe( (data: string)=>{console.log(data)}, (error: string)=>{console.log(error)}, ()=>{console.log("Project is Completed")} ) }
اگر مجموعهی کد بالا را اجرا کنید در صفحه کنسول شما پس از عبارت First Package و Secound Package، متن Project is Completed نمایش داده خواهد شد. اما توجه داشته باشید دیگر دستور بعدی تحت عنوان Three Package چاپ نمیشود زیرا فرآیند به اتمام رسیده است.
برای یادگیری مفهوم این دستور ابتدا مثال زیر را اجرا کنید:
import {Component, OnInit} from '@angular/core'; import {Observable, Observer} from "rxjs"; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { constructor() { } ngOnInit() { const myNumber = Observable.interval(1000); myNumber.subscribe( (number: number)=>{ console.log(number) } ) const myObservable = Observable.create((observer: Observer<string>)=>{ setTimeout( ()=>{ observer.next('First Package') } , 2000); setTimeout( ()=>{ observer.next('Second Package') } , 4000); setTimeout( ()=>{ observer.complete() } , 5000) setTimeout( ()=>{ observer.next('Three Package') } , 5000) }); myObservable.subscribe( (data: string)=>{console.log(data)}, (error: string)=>{console.log(error)}, ()=>{console.log("Project is Completed")} ) } }
توجه داشته باشید که این دستورها درون فایل home.component.ts انجام میشود بنابراین وقتی ما میخواهیم وارد صفحه کاربر شماره ۱ یا کاربر شماره ۲ شویم انتظار داریم که تمام دستورها شامل Observable و Observer متوقف شوند زیرا متعلق به کامپوننت home هستند و وقتی وارد صفحه کاربر شماره ۱ میشویم باید دستورات موجود در کامپوننت user فعال شوند. ولی این اتفاق نمیافتد! زیرا دستوری برای توقف این Observable ارائه نکردیم و این مشاهده کننده و مشاهدهگر مدام در حال تبادل اطلاعات و نمایش آنها هستند. حال برای ایجاد این توقف از دستور unsubscribe استفاده خواهیم کرد:
import {Component, OnDestroy, OnInit} from '@angular/core'; import {Observable, Observer, Subscription} from "rxjs"; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit, OnDestroy { numberObsSubscription: Subscription; customObsSubscription: Subscription; constructor() { } ngOnInit() { const myNumber = Observable.interval(1000); this.numberObsSubscription = myNumber.subscribe( (number: number)=>{ console.log(number) } ) const myObservable = Observable.create((observer: Observer<string>)=>{ setTimeout( ()=>{ observer.next('First Package') } , 2000); setTimeout( ()=>{ observer.next('Second Package') } , 4000); setTimeout( ()=>{ observer.complete() } , 5000) setTimeout( ()=>{ observer.next('Three Package') } , 5000) }); this.customObsSubscription =myObservable.subscribe( (data: string)=>{console.log(data)}, (error: string)=>{console.log(error)}, ()=>{console.log("Project is Completed")} ) } ngOnDestroy(){ this.numberObsSubscription.unsubscribe() this.customObsSubscription.unsubscribe() } }
همانطور که ملاحظه میکنید ابتدا دو ویژگی با نام numberObsSubscription و customObsSuberscription از نوع Subscription ایجاد کردهایم. این دو ویژگی مقادیری که متد subscribe جهت نمایش ارسال میکند را درون خود ذخیره مینماید. سپس در هوک ngOnDestroy با استفاده از متد unsubscribe مشاهده کننده یا Observable را متوقف کردهایم. بسیار عالی حال اگر آدرس http://localhost:4200 را وارد نمایید و صفحه کنسول را نیز باز کنید مشاهده خواهید کرد که شماره شروع به افزایش میکند و اگر روی یکی از آدرسهای کاربر شماره ۱ یا کاربر شماره ۲ کلیک نمایید متوجه خواهید شد که این شماره از کار میافتد زیرا Observable را متوقف کردهایم. در ادامه به توضیح Subject به عنوان یک نوع داده میپردازیم.
کلاس Subject جهت ارسال اطلاعات و پاسخ به دادهی بازگشتی استفاده میشود. این کلاس از کلاس Observable ارثبری میکند. برای تفهیم بیشتر یک مثال کاربردی خدمت شما عزیزان ارائه خواهیم کرد. ابتدا یک سرویس به نام user.service.ts در پوشه اصلی app ایجاد و سپس دستورهای زیر را درون آن قرار میدهیم:
import {Subject} from "rxjs"; export class UserService{ userActivated = new Subject(); }
توجه داشته باشید که این سرویس را باید به فایل app.module.ts و در قسمت provider معرفی کنیم. سپس در فایل user.component.html یک دکمه به نام فعال سازی به صورت زیر ایجاد میکنیم:
<p> کاربری با شناسه<strong> ID {{ id }}</strong> بارگذاری شد</p> <button class="btn btn-primary" (click)="onActivated()">فعال سازی</button>
سپس درون فایل user.component.ts این متد را تعریف کرده و از سرویس user.service.ts برای فعالسازی کاربر بهره میبریم:
import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, Params} from '@angular/router'; import {UserService} from "../user.service"; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit { id: number; constructor(private route: ActivatedRoute, private userService: UserService) { } ngOnInit() { this.route.params .subscribe( (params: Params) => { this.id = +params['id']; } ); } onActivated(){ this.userService.userActivated.next(this.id) } }
تا به اینجای کار از یک ویژگی به نام userActivated که در سرویس کاربر تعریف کرده بودیم استفاده کرده و با استفاده از متد next یک پیام را برای کنترل داده ارسال می کنیم.
حال میخواهیم وقتی روی دکمه کلیک شد عبارت active در کنار کاربری که فعال سازی شده است نمایش داده شود بنابراین فایل app.component.html را باز کرده و دستورات زیر را درون آن قرار میدهیم:
<div class="container"> <div class="row"> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <a routerLink="/">صفحه اصلی</a> <a [routerLink]="['user', 1]">کاربر شماره ۱ {{ user1Activated ? '(active)': '' }}</a> <a [routerLink]="['user', 2]">کاربر شماره ۲ {{ user2Activated ? '(active)': '' }}</a> </div> </div> <hr> <div class="row"> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <router-outlet></router-outlet> </div> </div> </div>
سپس وارد app.component.ts شده و در نهایت دستورات زیر را برای فعال سازی یک کاربر اعمال میکنیم:
import {Component, OnInit} from '@angular/core'; import {UserService} from "./user.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit{ user1Activated = false; user2Activated = false; constructor(private userService : UserService){} ngOnInit(){ this.userService.userActivated.subscribe( (id: number)=>{ if(id == 1){ this.user1Activated = true; }else{ this.user2Activated = true; } } ) } }
همانطور که ملاحظه میکنید با توجه به اینکه نوع userActicvated را Subject تعریف کردهایم میتوانیم از متد subscribe استفاده کرده و در نهایت با دریافت یک پارامتر به عنوان آرگومان مقدار user1Activated را تغییر دهیم توجه داشته باشید که از Subject میتوان به جای event و EventEmitter استفاده کرد.
بسیار عالی به شما تبریک میگوییم. شما با مبحث مشاهده کننده ها تا میزان قابل توجهی آشنایی پیدا کردید. مفاهیمی چون مشاهده کننده ها یا Observable در درخواستهای HTTP و وب سرویس ها بسیار کاربرد دارد. در فصل بعدی به توضیح فرمها در انگولار و کار کردن با آنها میپردازیم. با ما همراه باشید.
توجه: دوستان عزیز آموزش ویدیویی انگولار ۵ از مقدماتی تا پیشرفته به زبان فارسی را میتوانید با کلیک روی اینجا یاد بگیرید. (این دوره در حال برگزاری است)
فصل ۱
فصل ۲
فصل ۳: خطایابی (Debugging) در انگولار ۴
فصل ۴
فصل ۵
فصل ۶
فصل ۷
فصل ۸: معرفی Observable یا مشاهده کنندهها در انگولار ۴
فصل ۹
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.