فصل ۸: معرفی Observable یا مشاهده کننده‌ها در انگولار ۴

14 آبان 1397
معرفی Observable و Observer در انگولار ۴

تا به اینجای کار ۶۰ درصد از مسیر یادگیری فریم‌ورک انگولار ۴ را پشت سر گذاشته‌اید و از این پس وارد مباحث تخصصی‌تر می‌شویم. در این جلسه قصد داریم مطلبی بسیار ارزشمند به نام Observable یا مشاهده کننده ها در انگولار ۴ را مورد بررسی قرار دهیم. توجه داشته باشید که مشاهده کننده‌ها و ناظرها (Observable & Observer) به عنوان مقدمه‌ای برای ساخت نرم‌افزارهای وب سرویس می‌باشند بنابراین یادگیری آن را به دوستانی که هدفی بالاتر از طراحی یک وب سایت ساده دارند به شدت توصیه می‌شود.

مقدمه

قطعا تا به اینجای کار این سوال برای شما پیش آمده است که Observable چیست؟

Observable به عنوان یک شیء و منبع داده‌ی متنوع است. یعنی یک Observable شیء ای می‌باشد که انواع مختلف داده را درون خود ذخیره می‌کند و از پکیج Rxjs دریافت و به انگولار ۴ اضافه می‌شود. توجه داشته باشید که Observable به خودی خود در انگولار وجود ندارد. برای افزایش تمرکز شما یک دیاگرام در اختیارتان گذاشته تا ادامه‌ی توضیحات را با این دیاگرام ارائه دهیم:

دیاگرام Observable و Observer در انگولار ۴

همانطور که در این دیاگرام مشاهده می‌کنید هر Observable دارای یک خط نگه‌داری اطلاعات از منبع است که این اطلاعات ممکن است به صورت یک رویداد (event) یا درخواست http ارسال شود. سپس یک Observer یا ناظر پس از خط نگه‌درای اطلاعات ایجاد می‌شود که از طریق ۳ روش داده‌ها را مدیریت می‌کند:

  • Data Handle: کنترل داده‌های نرمال و بدون خطا
  • Error Handle: کنترل خطاها
  • Handle Completion: کنترل مراحل خاتمه و اتمام یک مشاهده کننده یا Observable

در واقع با استفاده از کدنویسی مشخص می‌کنیم که کدام یک از روش‌ها مد نظر ما است.

از Observable و Observer برای داده‌های غیرهمزمان استفاده می‌شود. داده‌های غیرهمزمان یا Async به داده‌هایی گفته می‌شود که زمان ارسال آنها نا مشخص است و معلوم نیست چقدر طول می‌کشد تا به دست ما برسد.

شروع با یک مثال

برای شروع این فصل ابتدا یک مثال که در پیوست انتهای همین صفحه در اختیار شما قرار گرفته است، صفحه‌ای ایجاد کرده‌ایم که شامل ۲ کاربر و یک صفحه اصلی است. ابتدا فایل‌های مربوطه را دانلود کنید و سپس روی سرور لوکال خود اجرا نمایید. در صورتیکه اینکار را به درستی انجام داده باشید با تصویر زیر روبه‌رو خواهید شد:

مثال مربوط به Observable

در ابتدا کدهای موجود در فایل 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 دلخواه ابتدا باید یک ویژگی یا متغییر را ایجاد کنیمو سپس از عبارت 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 چاپ نمی‌شود زیرا فرآیند به اتمام رسیده است.

دستور Unsubscribe

برای یادگیری مفهوم این دستور ابتدا مثال زیر را اجرا کنید:

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

کلاس 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 یا مشاهده کننده‌ها در انگولار ۴

فصل ۹

فصل ۱۰: معرفی فیلترها یا Pipes در انگولار ۴

فصل ۱۱: معرفی درخواست‌های Http در انگولار ۴

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش رایگان انگولار توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (8 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

هیوا
05 اردیبهشت 1399
سلام. وقت بخیر. ممنون از آموزش های خوب و مفیدتون. پسوورد فایلی که برای دانلود گذاشتید فقط برای اعضا قابل دیدن هست؟؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

صالح
28 بهمن 1398
observable مشاهده شونده یا قابل مشاهده هست نه مشاهده کننده. درضمن معنی مشاهده کننده و ناظر یکی هست.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

علی
11 آذر 1397
با درود و سپاس از تلاش شما در راستای گسترش دانش انگولار. بحث RXJS بحثی بسیار پیچیده برای مبتدیان هست و نیاز به مثالهای ملموس تر و قابل فهم تر برای درک این مقوله است. کاش مثال راحت تری میزدید. امیدوارم مطالب بیشتری در مورد RXJS انتشار بدید.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

afshin
29 خرداد 1397
عالی بود

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

simand cable
29 خرداد 1397
سلام ممنون ولی مبحث سنگینی هست به نظرم

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

سبحان
24 دی 1396
سالام عالی بود

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

امید خسروجردی
26 مرداد 1396
آقا این مبحث فوق العاده بود من خیلی استفاده کردم ممنونم فراوان

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

فرشید
27 تیر 1396
لطفا فایل های این بخش را قرار دهید. با تشکر

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
27 تیر 1396
با سلام فایل در قسمت دانلود قرار گرفت هم اکنون می‌توانید آن را در باکس سبز رنگ دریافت کنید.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.