روزنوشته های فربد صالحی

درباره زندگی و برنامه نویسی

۶ مطلب با موضوع «برنامه نویسی» ثبت شده است

انتقال اطلاعات از یک کامپوننت به کامپوننت دیگر در Angular

در Angular روش‌های متفاوتی برای انتقال اطلاعات از یک کامپوننت به کامپوننت دیگه وجود داره که انتخاب هر کدوم بستگی به نیاز ما و موقعیت اون دو تا کامپوننت‌ها نسبت به همدیگه داره. 

 

الف) وقتی کامپوننت‌ها در وضعیت «والد/فرزند» هستند

@Component({
  selector: 'app-child',
  template: `
    <p> {{ username }}'s code is: {{ userCode }}<p>
    <p> 
        <button 
            (click)="sendMessage('Hello from {{ username }}')"> 
                    Send Message 
        </button> 
    </p>
  `
})
export class ChildComponent {
  @Input() userCode: number;
  @Input('name') username: string;
  @Output() sendInfo = new EventEmitter<string>();

  sendMessage(message: string) { 
     this.sendInfo.emit(message); 
  }
}

در قطعه کد بالا، کامپوننت ChildComponnet تعریف شده و دارای دو ویژگی userCode و username هستش که قبل از هر دوی اونها Input@ قرار دادیم تا مشخص کنه انتظار داریم مقدارشون از بیرون از این کامپوننت وارد بشه. یه نکته‌ی ریز هم در این کد استفاده از Input('name')@ هستش و معنیش اینه که کامپوننت‌های دیگه این ویژگی رو با نام name شناسایی و مقداردهی خواهند کرد (همونطور که در کد پایین این کار انجام شده)، هر چند که در داخل خود این کامپوننت از username برای ارجاع بهش استفاده میشه. 

در قطعه کد پایین، کامپوننت ParentComponent تعریف شده که در بخش template اش با استفاده از تگ <app-child>، کامپوننتِ قطعه کد قبلی رو در یک حلقه‌ی for فراخوانی کرده. در اینجا نحوه‌ی انتقال داده به ChildComponent با استفاده از [userCode] و [name] واضح و روشنه. 

@Component({
  selector: 'app-parent',
  template: `
    <app-child *ngFor="let user of users" 
                    [userCode]="user.code"  
                    [name]="user.username"
                    (sendInfo)="onGetInfo($event)">
    </app-child>
  `
})
export class ParentComponent {

  users = Users;
  userMessage: string;

  onGetInfo(message: string) {
     this.userMessage = message;
  }

}

اما اگه بخوایم برعکس عمل کنیم چطور؟ یعنی داده رو از ChildComponent به ParentComponent منتقل کنیم. تو ChildComponent داریم: 

 @Output() sendInfo = new EventEmitter<string>();

ChildComponent هر جا که لازم بود از طریق این EventEmitter می‌تونه یه رویداد یا event تعریف شده تو کامپوننت والد خودش رو فراخوانی کنه و داده‌ها رو به شکل پارامتر به اون بفرسته. همونطور که در عبارت <EventEmitter<string مشخص شده، یه پارامتر از نوع string ارسال می‌کنه. در اینجا این پارامتر، message هستش و همون داده‌ایه که می‌خوایم منتقلش کنیم:

 sendMessage(message: string) { 
   this.sendInfo.emit(message);   
 }

این پارامترها از هر نوع و به هر تعداد می‌تونن باشن. حالا برای اینکه تو کامپوننت والد این مقدار دریافت بشه، تو تمپلیت ParentChild داریم:

(sendInfo)="onGetInfo($event)"

که یعنی هر موقع sendInfo تو ChildComponent رو emit کردن، ()onGetInfo توی ParentChild فراخوانی بشه و پارامتر هم از طریق event$ ارسال بشه:

onGetInfo(message: string) {
   this.userMessage = message;
}

در حالت خیلی کلی میشه گفت در این روش، از Input@ برای ورود داده و از Output@ برای خروج داده استفاده میشه. 

 

ب) وقتی کامپوننت‌ها مستقل از هم هستند

تو این حالت هر کامپوننت route خودش رو داره. وقتی می‌خوایم یک یا چند پارامتر رو منتقل کنیم، خیلی راحت میشه از queryها در route استفاده کرد. مثلا اگه بخوایم دو تا پارامتر به نام‌های id و category رو از ProducstListComponent به ProductDetailComponent ارسال کنیم:

 
  this.router.navigate(
          ['/product-detail'], 
          {
             queryParams: { id: '3', category = 'mobile'} 
          }
  );
  

و اگه بخوایم تو ProductDetailComponent این پارامترها رو دریافت کنیم:

productId: number;
productCategory: string;
 
ngOnInit() {
    this.route.queryParams.subscribe(params => {
       this.productId= +params['id'];
       this.productCategory = params['category'];
    });
}

 

اما گاهی نیاز داریم که تعداد زیادی پارامتر یا حتی مثلا لیستی از اشیا رو منتقل کنیم به کامپوننت بعدی. در این حالت استفاده از query ها نه راحت هستش نه منطقی. در این حالت می‌تونیم از ویژگی state استفاده کنیم که از نسخه‌ی 7.2 به Angular اضافه شده. برای این‌ کار در ProductsListComponent برای انتقال لیست sameProductsList خواهیم داشت:

this.router.navigate(
              ['/product-detail'], 
              {state: {sameProds: sameProductsList}}
);

سپس برای دریافت این لیست در ProductDetailComponent:

sameProducts: any[];

ngOnInit() {

    const currentNavigationState = 
             this.router.getCurrentNavigation().extras.state;

    if (currentNavigationState) {

      this.sameProducts = currentNavigationState.sameProds;

    } 
 }

 

۰ نظر
فربد صالحی

پیاده‌سازی CORS برای اَپ‌های موبایلی Ionic در Asp.Net Core

کارکرد SOP

فرض می‌کنیم از طریق یه مرورگر به وبسایت http://www.yyy.com مراجعه و در اون لاگین می‌کنیم، و این وبسایت هم شناسه‌ی این لاگین رو به صورت کوکی روی دستگاه ما ذخیره می‌کنه. تا زمانیکه این کوکی روی دستگاه ما ذخیره شده، هر وقت از این دستگاه و با همون مرورگر به این وبسایت مراجعه کنیم، با هر request این کوکی به این وبسایت ارسال میشه و وبسایت می‌تونه حساب کاربری ما رو بشناسه.

اما نکته اینجاست که حتی وقتی ما روی این دستگاه و با اون مرورگر مشخص، وبسایتی متفاوت مثل http://www.xxx.com  رو هم باز می‌کنیم، اگه این وبسایت در بخشی از عملیاتش یه request‌ به دامنه‌ی yyy بفرسته، مثلا API اون رو فراخوانی کنه، باز هم مرورگر همه‌ی کوکی‌های ذخیره شده توسط وبسایت yyy، از جمله کوکی لاگین ما رو، بدون توجه به مبدا ارسال کننده request که در اینجا xxx هستش، به yyy ارسال می‌کنه. در واقع مرورگر برای ارسال کوکی‌ها، به مقصد request توجه می‌کنه و نه ارسال‌کننده‌ی request. به این ترتیب وبسایت yyy ممکنه request ارسالی از سمت وبسایت xxx رو ارسال شده از طرف حساب کاربری ما در yyy تشخیص بده و اطلاعات محرمانه‌ی ما رو برگردونه یا دسترسی‌هایی به این وبسایت بده که نباید. 

سیاست مرورگرهای امروزی برای جلوگیری از این مشکل، Same Origin Policy یا "SOP" هستش. به طور کلی این سیاست به این صورت پیاده میشه که اسکریپت‌های بارگذاری شده توسط یک «منشاء»، نمی‌تونن به منابعی مثل API از منشاء متفاوت دسترسی داشته باشن. اما معیار یکسان بودن منشاء چیه؟

وقتی دو منبع دارای "scheme"، "host" و "port" کاملا یکسان باشند، اونوقت دارای منشاء یکسان درنظر گرفته میشن. به نظرم درک سریع موضوع با دیدن سه دسته‌ی زیر با منشاء‌های متفاوت امکان‌پذیر خواهد بود. دوتای اول فقط scheme های متفاوت دارن (http , https)، سه تای بعدی host های متفاوت دارن (example, www.example, myapp.example) و دوتای آخری پورت‌های متفاوت (8080 و 80 که مقدار پیشفرضه). بنابرین هیچکدوم از سه دسته منشاء یکسانی ندارن.

 

 

حالا میشه متوجه شد که وقتی در حال توسعه‌‌ی یه برنامه روی سیستم خودمون هستیم، با اینکه "backend"  و "frontend" هر دو دارن روی http://localhost اجرا میشن، چرا در حالت پیش فرض frontend  نمی‌تونه API رو از backend فراخوانی کنه. چون در واقع روی پورت‌های متفاوتی در حال اجرا هستن، یکی به فرض روی http://localhost:4200 و دیگری روی http://localhost:44353. بنابراین طبق توضیحات بالا منشاء یکسانی ندارن، با اینکه پروتکل (http) و host اونها (localhost) یکسانه. 

 

کارکرد CORS

گاهی لازم میشه که از محدودیت‌های اعمال شده بوسیله‌ی SOP کم کنیم. مثلا یه اپ تحت وب با انگولار نوشتیم و روی دامنه‌ی https://www.aaa.com قرار دادیم که باید API قرار گرفته روی دامنه‌ای با منشاء متفاوت مثل https://api.aaa.com رو فراخوانی کنه. برای این منظور از مفهوم Cross Origin Resource Sharing یا "CORS" استفاده می‌کنیم. در واقع ما با استفاده از CORS به تعدادی از دامنه‌ها اجازه‌ی عبور از محدودیت‌های اعمال شده بوسیله‌ی SOP رو میدیم. برای این کار، اگه API رو با Asp.Net Core پیاده کرده باشیم، در فایل "Startup.cs" و در تابع "ConfigureServices" خواهیم داشت:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("AllowMyOrigin",
                builder => builder.WithOrigins("https://www.aaa.com").AllowAnyHeader());
            });

           ....
        }

در واقع ما یه Policy با اسم دلخواه "AllowMyOrigin" که هر اسم دیگه‌ای هم می‌تونه باشه تعریف کردیم که میگه به "https://www.aaa.com" اجازه‌ی دسترسی به API رو بده، با اینکه منشاء متفاوتی داره. حالا در همون فایل و در تابع "Configure" این Policy رو اِعمال می‌کنیم:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            ....

            app.UseCors("AllowMyOrigin");

            .....
        }

به این ترتیب، محدودیت SOP برای https://www.aaa.com برداشته میشه. 

 

CORS برای اپ Ionic

می‌دونیم که SOP عملا برای برنامه‌‌هایی که روی مرورگر اجرا میشن مهمه و در واقع اپ‌های native موبایل دچار محدودیت در این زمینه نیستن. برنامه‌های تحت ‌وب هم که یه دامنه‌ی مشخص دارن و دیدیم که می‌تونیم  این دامنه رو استثنا کنیم. اما اپ‌های موبایل تولید شده با Ionic با توجه به «هیبریدی» بودنشون چه وضعی دارن؟

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

public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("AllowMyOrigin",
                builder => builder.WithOrigins("capacitor://localhost", // capacitor: ios
                                               "http://localhost",      // capacitor: android | ionic webview 3.x on cordova: android
                                               "ionic://localhost",     // ionic webview 3.x on cordova : ios
                                               "http://localhost:8080", // ionic webview 2.x on cordova : ios & android
                                               ).AllowAnyHeader());

            });

            ....
        }

 

در واقع به جای اینکه یه دامنه خاص رو استثنا کنیم، چهار منشاء بالا رو که هرکدوم یک یا چند حالت رو برای اپ‌های Ionic پوشش میدن استثنا می‌کنیم و به این ترتیب محدودیت SOP برای اپ‌های Ionic برداشته میشه. 

۰ نظر
فربد صالحی

خطا در هنگام ایجاد پروژه با Ionic و اجرای اولیه

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

/ Downloading and extracting blank starter ionic

و گاهی هم خطای timeout در اتصال به 10.10.34.35:443 اعلام می‌کرد. 

خیلی جستجو کردم، راه‌حل‌هایی پیشنهاد شده بود ولی مشکل رو حل نکردن. یه چیزی که به ذهنم اومده بود ولی بهش بی‌توجهی می‌کردم رو امتحان کردم و مساله حل شد. بله! روشن کردن وی.پی.ان. 

حالا اینکه چرا باید اینطور باشه و مشکل از طرف «دوستان» داخلی هستش یا خارجی رو نمی‌دونم. فقط امیدوارم در ادامه‌ی کار با Ionic از این نوع مشکلات بوجود نیاد، یا حداقل کم باشه تعدادشون.  

بعد از ایجاد پروژه، برای اجرای اولیه‌ و بررسی اینکه همه‌چی خوب پیش رفته، دستور زیر رو تو ترمینالِ VS Code اجرا کردم:

ionic serve

اینبار خطای دیگه‌ای ظاهر شد:

ionic : File C:\...\AppData\Roaming\npm\ionic.ps1 cannot be loaded because running scripts is disabled on this system. For 
more information, see about_Execution_Policies

باز هم با جستجو مشخص شد که این به سیاست‌های امنیتی سیستم عامل ویندوز برای پیشگیری از اجرای اسکریپت‌های مخرب مربوطه. توضیحات کامل رو می‌شه از اینجا مطالعه کرد.

اما برای غیرفعال کردن این ویژگی، یک راه اینه که Windows PowerShell رو تو حالت Administrator اجرا و در اون دستور زیر رو اجرا کنیم:

 powershell Set-ExecutionPolicy RemoteSigned

برای برگردوندن این ویژگی به حالت پیش‌فرض هم می‌تونیم از دستور زیر استفاده کنیم:

powershell Set-ExecutionPolicy Restricted

 

۰ نظر
فربد صالحی

انگولار : two-way data binding و ngFor

 می‌دونیم که «بایندینگ دوطرفه» در «انگولار» با استفاده از "ngModel" کار راحتیه. به این صورت که اگه متغیری به اسم username رو تو کامپوننت مربوطه تعریف کرده باشیم، برای بایندینگ دوطرفه‌ی اون در سمت template می‌نویسیم:

<input [(ngModel)]="username" />

حالا اگه ورودی این input تغییر کنه مقدارش به متغیر username منتقل میشه و هرجای برنامه هم که مقدار این متغیر رو تغییر بده، مقدار جدید توی input نمایش داده میشه.

اما گاهی نیاز میشه که بایندینگ دوطرفه رو روی آرایه‌ای از متغیرها اجرا کنیم. فرض کنیم آرایه‌ای به نام cities داریم:

let cities = ['Tehran','Rasht','urmieh'];

نمایش عناصر این آرایه بوسیله‌ی input ها ساده است:

 <div *ngFor="let item of cities">
     <input [(ngModel)]="item">
 </div>

همونطور که مشخصه در اینجا هم از ngModel برای بایندینگ دوطرفه استفاده شده، اما اگه این کد رو اجرا کنیم نتیجه‌ی دلخواه به دست نمیاد. یعتی اگه کاربر مقدار یک یا چندتا از input ها رو تغییر بده، مقادیر در آرایه‌ی cities تغییر نمی‌کنن. برای رفع این مشکل باید تغییرات زیر رو در کد بالا اعمال کنیم (بخش پر رنگ کد):

<div *ngFor="let item of cities;let index = index;">
  <input [(ngModel)]="cities[index]">
</div>

یعنی به جای متغیر item از اندیس‌های آرایه برای مقداردهی به ngModel استفاده می‌کنیم. به این ترتیب مشکل قبلی حل میشه، ولی در زمان اجرا کاربر در تغییر مقدار input مشکل خواهد داشت. فرض کنید کاربر می‌خواد مقدار urmieh (عنصر سوم آرایه) رو به urmia تغییر بده، تصمیم می‌گیره اول eh رو از انتهای کلمه پاک کنه و بعد a رو اضافه کنه. ولی همینکه h رو پاک می‌کنه، مکان‌نما (curser) از input مربوطه خارج میشه، در واقع focus از روی input برداشته میشه و کاربر باید دوباره روی اون input کلیک کنه تا بتونه تغییر بعدی رو اعمال کنه. این روند با هر تغییری در ورودی‌ها تکرار میشه.

علت اینه که با هر تغییر، انگولار مجددا DOM رو بازسازی می‌کنه و به این ترتیب focus روی input قبلی ازدست میره. برای رفع این مشکل، ابتدا تابعی رو تو کامپوننت مربوطه به صورت زیر تعریف می‌کنیم:

trackByIndex(index: number, item: any) {
  return index;
}

و مجددا کد مربوط به حلقه‌ی ngFor رو تغییر می‌دیم (بخش پر رنگ اضافه شده):

<div *ngFor="let item of cities;let index = index;trackBy:trackByIndex;">
  <input [(ngModel)]="cities[index]">
</div>

در اینجا ما از ویژگی trackBy در حلقه‌ی ngFor استفاده کردیم. به طور کلی، استفاده از این ویژگی یکی از توصیه‌ها برای افزایش کارایی در استفاده از ngFor در انگولار هستش، بخصوص وقتی تعداد عناصر حلقه زیاد و امکان تغییر اونها توسط کاربر وجود داشته باشه. تابعی که به این ویژگی نسبت داده میشه (ما اسمش رو trackByIndex گذاشتیم) همیشه دو پارامتر ورودی می‌گیره، اندیس عنصر مربوطه در آرایه و خود عنصر، و باید یه مقدار منحصر به فرد برای هر عنصر برگردونه تا انگولار بتونه با استفاده از اون فقط عنصری که مقدارش تغییر کرده رو بروز کنه و نیاز به بازسازی کل DOM نداشته باشه. ما در مثال بالا اندیس عنصر در آرایه رو بر می‌گردونیم.

۰ نظر
فربد صالحی

آپدیت ستون‌های یک جدول پایگاه داده بر اساس مقادیر جدولی دیگر

فرض کنیم جدولی به نام Products تو پایگاه داده داریم و ستون جدیدی به نام GroupId بهش اضافه کردیم:

 

 

این جدول ردیف‌های داده‌ی زیادی داره که باعث میشه اضافه کردن مقادیر این فیلد جدید به ردیف‌ها به صورت دستی کار سختی باشه. اما جدول دیگه‌ای به نام Categories داریم که GroupId درش مقدار داره و فیلد ProductCode اون با فیلد Code تو جدول قبلی مقادیرش مشترکه:

 

 

در این صورت تو Sql Server با دستور زیر می‌تونیم فیلد GroupId رو تو جدول Products مقداردهی کنیم:

 

 

UPDATE
    Products
SET
    Products.GroupId = cat.GroupId
FROM
    Products as pro
INNER JOIN
    Categories as cat
ON 
    pro.code = cat.ProductCode

 

در واقع با join جدول‌ها براساس فیلدهای Code و ProductCode (با دستور ON pro.code = cat.ProductCode)، یه جدول (مجازی) به دست میاد و بعد از اون انگار یه آپدیت معمولی روی یه فیلد (Products.GroupId) با استفاده از یه فیلد دیگه (cat.GroupId) داره انجام میشه. 

 

۰ نظر
فربد صالحی

بیسوادی نوین

اکثر افرادی که تو حوزه‌های مرتبط با فناوری اطلاعات و بخصوص توسعه‌ی نرم‌افزار فعالیت ‌می‌کنن، در یک موضوع هم‌نظر هستن و اون هم تغییرات زیاد و نیاز مداوم به مطالعه و بروز شدن هستش. البته این به معنی درجا زدن بقیه علوم نیست، فقط شاید فاصله‌ی زمانی بین یافته‌های جدید و درخواست بازارهای کار برای استفاده از این یافته‌ها در سایر علوم بیشتر باشه.

من این ویژگی کار تو حوزه فناوری اطلاعات رو می‌پسندم و از اینکه برای حذف نشدن از بازار واقعی کار باید مرتبا آماده تغییر و تحول بود رو نکته مثبتی به حساب میارم. منتها این اواخر احساس می‌کنم این ویژگی یه عارضه‌ای با خودش به همراه داره و اون هم یجور حس بیسوادی هستش، که نمیدونم حس درستیه یا نه. 

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

این مساله در سال‌های اخیر با دسترسی راحت‌تر به اینترنت حادتر هم شده. یعنی جدا از اینکه عمیق نمیشی، حتی به صورت سطحی هم روش‌ها رو حفظ نمی‌کنی، چون میدونی که نیازی به این کار نیست و هر لحظه که لازم شد می‌تونی تو اینترنت به چیزی که نیاز داری دسترسی داشته باشی.

در این شرایط، برای شخص من به عنوان یه برنامه‌نویس گاهی این سوال پیش میاد که من واقعا چیزی هم بلدم یا فقط با استفاده از اینترنت دارم کارم رو راه میندازم؟ اصلا چه معیارهایی برای سنجش دانش یه شخص در زمینه حرفه‌ایش باید در نظر گرفت؟ همین که کاری رو بهش بسپاری و بهش زمان بدی و کیفیت نهایی کار رو بسنجی کافیه یا باید دانش تئوریکش رو هم سنجید؟

 

 

۰ نظر
فربد صالحی