An unexpected error happened; package X-editable has no version null.
مدیریت سراسری خطاها در یک برنامهی Angular
npm install -g @angular/cli
ng new HowToKeepAngularSizeSmall
ng serve --open --prod
"@agm/core": "^1.0.0-beta.5", "@angular/flex-layout": "^7.0.0-beta.23", "@angular/material": "^7.3.3", "@angular/platform-browser": "~7.2.0", "@asymmetrik/ngx-leaflet": "^5.0.1", "@ngx-loading-bar/router": "1.3.1", "@ngx-translate/core": "^11.0.1", "@ngx-translate/http-loader": "^4.0.0", "@progress/kendo-angular-buttons": "^4.3.3", "@progress/kendo-angular-dateinputs": "^3.6.0", "@progress/kendo-angular-dialog": "^3.10.1", "@progress/kendo-angular-dropdowns": "^3.5.1", "@progress/kendo-angular-excel-export": "^2.3.0", "@progress/kendo-angular-grid": "^3.13.0", "@progress/kendo-angular-inputs": "^4.2.0", "@progress/kendo-angular-intl": "^1.7.0", "@progress/kendo-angular-l10n": "^1.3.0", "@progress/kendo-angular-layout": "^3.2.0", "@progress/kendo-data-query": "^1.5.0", "@progress/kendo-drawing": "^1.5.8", "@progress/kendo-theme-default": "^3.3.1", "@swimlane/ngx-datatable": "^14.0.0", "angular-calendar": "0.23.7", "angular-tree-component": "7.0.1", "bootstrap": "^3.4.0", "chart.js": "2.7.2", "d3": "4.13.0", "dragula": "3.7.2", "hammerjs": "2.0.8", "intl": "1.2.5", "leaflet": "1.3.1", "moment": "2.21.0", "ng2-charts": "1.6.0", "ng2-dragula": "1.5.0", "ng2-file-upload": "1.3.0", "ng2-validation": "4.2.0", "ngx-perfect-scrollbar": "5.3.5", "ngx-quill": "2.2.0", "screenfull": "3.3.2", "font-awesome": "^4.7.0", "jalali-moment": "^3.3.1", "jquery": "^3.3.1", "ng-snotify": "^4.3.1", "normalize.css": "^8.0.1",
@import "~bootstrap/dist/css/bootstrap.css"; @import "~@progress/kendo-theme-default/scss/all"; @import '@angular/material/prebuilt-themes/pink-bluegrey.css'; @import '~perfect-scrollbar/css/perfect-scrollbar.css'; @import "~ng-snotify/styles/material";
"scripts": [ "node_modules/jquery/dist/jquery.js", "node_modules/bootstrap/dist/js/bootstrap.min.js", "node_modules/hammerjs/hammer.min.js" ],
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; // Import Material import { MatFormFieldModule, MatInputModule, MatButtonModule, MatButtonToggleModule, MatDialogModule, MatIconModule, MatSelectModule, MatToolbarModule, MatDatepickerModule, DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatTableModule, MatCheckboxModule, MatRadioModule, MatCardModule, fadeInContent, MatListModule, MatProgressBarModule, MatTabsModule, MatSidenavModule, MatSlideToggleModule, MatMenuModule } from '@angular/material'; // Import kendo angular ui import { ButtonsModule } from '@progress/kendo-angular-buttons'; import { GridModule, ExcelModule } from '@progress/kendo-angular-grid'; import { DropDownsModule } from '@progress/kendo-angular-dropdowns'; import { InputsModule } from '@progress/kendo-angular-inputs'; import { DateInputsModule } from '@progress/kendo-angular-dateinputs'; import { DialogsModule } from '@progress/kendo-angular-dialog'; import { RTL } from '@progress/kendo-angular-l10n'; import { LayoutModule } from '@progress/kendo-angular-layout'; import { WindowService, WindowRef, WindowCloseResult, DialogService, DialogRef, DialogCloseResult } from '@progress/kendo-angular-dialog'; import { SnotifyModule, SnotifyService, SnotifyPosition, SnotifyToastConfig, ToastDefaults } from 'ng-snotify'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, // material MatSidenavModule, MatSlideToggleModule, MatInputModule, MatFormFieldModule, MatButtonModule, MatButtonToggleModule, MatDialogModule, MatIconModule, MatSelectModule, MatToolbarModule, MatDatepickerModule, MatCheckboxModule, MatRadioModule, MatCardModule, MatMenuModule, MatListModule, MatProgressBarModule, MatTabsModule, // kendo-angular ButtonsModule, GridModule, ExcelModule, DropDownsModule, InputsModule, DateInputsModule, DialogsModule, LayoutModule, SnotifyModule, ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
همانطور که میبینید بدون افزودن کامپوننت جدیدی، حجم خروجی از 222KB به 582KB رسیدهاست. معمولا در هر پروژه نیاز به تعدادی دایرکتیو و سرویس پایه نیز میباشد که کم کم به حجم خروجی صفحات میافزاید. در نظر بگیرید که هنوز هیچ قالب خاصی برای صفحه مورد نظرمان استفاده نشده و به حجم 582KB رسیدهایم. برای نمونه میتوانیم سری به سایت madewithangular.com بزنیم و حجم خروجی تعدادی از سایتهای نوشته شدهی با انگیولار را بررسی کنیم. سایتهای زیر خروجی بالای 1.5MB دارند. همچنین سایتی را که خودم تقریبا یک سال پیش شروع کرده بودم، حجم خروجی آن 2.7MB است:
دلیل بالا رفتن حجم خروجی، اضافه شدن فایلهای JavaScript و style-sheet به bundle اصلی پروژه است. برای مثال حجم فایل main.js را در نمونههای ذکر شده بررسی کنید.
در قسمتهای بعدی، به نحوهی کار صحیح با انگیولار میپردازیم و حجم صفحات را بررسی میکنیم.
اما ... چقدر خوب میشد که امکان ترکیب هردوی اینها با هم در یک برنامه وجود میداشت؛ یعنی داشتن یک آغاز سریع، به همراه تعاملات سریع با DOM. به همین جهت Auto Render Mode به Blazor 8x اضافه شدهاست.
نحوهی عملکرد حالت رندر تعاملی خودکار در Blazor 8x
زمانیکه از قرار است از Auto Render Mode استفاده شود، یعنی در نهایت به سراغ حالت رندر وباسمبلی رفتن؛ اما به شرطیکه که فریمورک، مطمئن شود میتواند تمام فایلهای مرتبط را خیلی سریع و در کمتر از 100 میلیثانیه تامین کند که عموما یک چنین حالتی به معنای از پیش دریافت کردن این فایلها و کش شده بودن آنها در مرورگر است. اما اگر یک چنین تضمینی وجود نداشته باشد، از همان ابتدای کار تصمیم میگیرد که باید کامپوننت را از طریق نگارش Blazor Server آن ارائه دهد، تا آغاز سریعی را سبب شود. در این بین هم در پشت صحنه (یعنی زمانیکه کاربر مشغول به کار با نگارش Blazor Server کامپوننت است)، شروع به دریافت فایلهای مرتبط با نگارش وباسمبلی کامپوننت و برنامه میشود تا آنها را کش کرده و برای بار بعدی بارگذاری صفحه و نمایش اطلاعات آن، به سرعت از آنها استفاده کند.
یک چنین حالتی برای کاربران به این معنا است که به محض گشودن برنامه و صفحهای، قادر به استفادهی از آن هستند و برای بارهای بعدی استفاده، دیگر نیازی به اتصال دائم SignalR یک جزیرهی تعاملی Blazor Server نداشته و در نتیجه بار کمتری به سرور تحمیل خواهد شد (مقیاس پذیری بیشتر) و همچنین پردازش DOM بسیار سریعتری را نیز شاهد خواهند بود (کار با نگارش Blazor WASM درون مرورگر).
همانطور که در این تصویر هم مشخص است، برای بار اول نمایش یک چنین جزیرههایی، یک اتصال وبسوکت برقرار میشود که به معنای فعال شدن حالت جزیرهای Blazor Server است که در قسمت پنجم بررسی کردیم. در این بین فایلهای Blazor WASM این جزیره هم دریافت و کش میشوند که در کنسول توسعه دهندههای مرورگر، لاگ شدهاست. این اتصال وبسوکت، در بار اول نمایش این کامپوننت، بسته نخواهد شد؛ تا زمانیکه کاربر به صفحهای دیگر مراجعه کند. در دفعهی بعدی که درخواست نمایش این صفحه را داشته باشیم، چون اطلاعات نگارش وباسمبلی آن کش شدهاست، از همان ابتدای کار نگارش وب اسمبلی را بارگذاری و راهاندازی میکند.
تفاوت قالب پروژههای Auto Render Mode با سایر حالتهای رندر در Blazor 8x
برای ایجاد قالب ابتدایی پروژهی یک چنین حالت رندری، از دستور dotnet new blazor --interactivity Auto استفاده میشود که حالت تعاملی آن به Auto تنظیم شدهاست. در نگاه اول، Solution ایجاد شدهی آن، بسیار شبیه به Solution جزیرههای تعاملی Blazor WASM است که در قسمت هفتم به همراه یک مثال کامل بررسی کردیم؛ یعنی از دو پروژهی سمت سرور و سمت کلاینت تشکیل میشود و دارای این تفاوتها است:
در فایل Program.cs پروژهی سمت سرور آن، افزوده شدن هر دو حالت جزایر تعاملی Blazor Server و همچنین Blazor WASM را مشاهده میکنیم:
// ... builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); // ... app.MapRazorComponents<App>() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(Counter).Assembly);
الف) امکان تعریف صفحات فقط SSR در پروژهی سمت سرور
ب) امکان داشتن جزیرههای تعاملی فقط Blazor Server در پروژهی سمت سرور
ج) امکان داشتن جزیرههای تعاملی فقط Blazor Wasm در پروژهی سمت کلاینت
د) به همراه امکان تعریف جزیرهای تعاملی Auto Render Mode در پروژهی سمت کلاینت
یک نکته: در این تنظیمات، متد AddAdditionalAssemblies، امکان استفاده از کامپوننتهای قرار گرفتهی در سایر اسمبلیها و پروژهها را میسر میکند.
نحوهی تعریف کامپوننتهایی که قرار است توسط Auto Render Mode ارائه شوند
باتوجه به اینکه این نوع کامپوننتها در نهایت قرار است به صورت وباسمبلی رندر شوند، آنها را باید در پروژهی سمت کلاینت قرار داد و به نکات مرتبط با توسعهی آنها که در قسمت هفتم پرداختیم، توجه داشت.
همچنین مانند سایر حالتهای رندر، به دو طریق میتوان مشخص کرد که یک کامپوننت باید به چه صورتی رندر شود:
الف) استفاده از دایرکتیو حالت رندر با مقدار InteractiveAuto در ابتدای تعریف یک کامپوننت
@rendermode InteractiveAuto
<Banner @rendermode="@InteractiveAuto" Text="Hello"/>
@using static Microsoft.AspNetCore.Components.Web.RenderMode
نحوه استفاده از این کامپوننت به این شکل هست:
1- تعریف ستونها:
const columns = [ { field: "fullname", headerName: "First & last Name", description: "name of user", width: 50, }, { field: "age", headerName: "Age", description: "age of user", width: 50, renderCell:(info)=><strong>Age is : {info.data.age}</strong> } ]
2- تعریف استیتهای لازم برا ست کردن سطر ها، لودر، تعداد کل, شماره صفحه و بزرگی هر صفحه(دقت شود که امکان استفاده بدون پیجینگ هم وجود دارد و امکانات کامل را در لینک لایبرری میتوانید مطالعه نمایید)
... const [rows,setRows] = useState([]); const [loading,setLoading] = useState(false); const [totalCount,setTotalCount] = useState(0);
const [filter,setFilter] = useState({ pageSize:10, pageNumber:1 });
const _fetchData = async () => { if (!active) return; //mock api, you can call your api then set data to table like commented line below return new Promise((resolve) => { setLoading(true); setTimeout(() => { setRows(data);// SetRows, note that data comes from api and must be array of objects setTotalCount(7);//=== set total page size for pagination part resolve(); setLoading(false); }, 2000); }); }
import Angrid from "rect-angrid"; ... _handlePageChange = (newPage)=>{ setFilter(p=>({...p,pageNumber:newPage})) } ... <AnGrid loading={loading} columns={columns} rows={rows} showRowNumber={true} pageSize={filter.pageSize} pageNumber={filter.pageNumber} totalCount={totalCount} onPageChange={_handlePageChange} theme="dark" minHeight={300} emptyList={<strong>There Is No Info</strong>} />
آشنایی با امکانات بستهی angular/platform-browser@
در ماژول angular/platform-browser@، دو سرویس Meta و Title، امکان تغییر پویای متاتگهای صفحهی جاری را مهیا میکنند. برای نمونه فرض کنید قصد دارید یک چنین متاتگهایی را به صفحهی جاری اضافه کنید:
<head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <title>newTitle ...</title> <base href="/"> <meta name="description" content="Angular meta service"> <meta name="author" content="DNT"> <meta name="keywords" content="Angular, Meta Service"> <meta name="twitter:card" content="summary"> <meta name="twitter:site" content="@my_site"> <meta name="twitter:title" content="Front-end Web Development"> <meta name="twitter:description" content="Learn frontend web development..."> <meta name="twitter:image" content="https://site/images/image.png"> <meta name="author" content="Other Author"> </head>
import { Component, OnInit } from "@angular/core"; import { Meta, Title } from "@angular/platform-browser"; @Component({ selector: "app-seo-tests", templateUrl: "./seo-tests.component.html", styleUrls: ["./seo-tests.component.css"] }) export class SeoTestsComponent implements OnInit { constructor(private metaService: Meta, private titleService: Title) { }
افزودن یک یا چند متاتگ
متد addTag سرویس Meta، کار افزودن پویای یک متا تگ جدید را به همراه ویژگیهای name و content آن، انجام میدهد. در ذیل چندین مثال از آنرا مشاهده میکنید. در اینجا یا میتوان از متد addTag استفاده کرد که تنها یک متاتگ را به صفحه اضافه میکند و یا از متد addTags کمک گرفت که میتواند آرایهای از متاتگها را به صورت پویا به صفحهی جاری اضافه کند:
// addTag & addTags this.metaService.addTag({ name: "description", content: "How to optimize your Angular App for search engine and other crawlers." }); this.metaService.addTag({ name: "author", content: "DNT" }); this.metaService.addTag({ name: "keywords", content: "Angular, Meta Service" }); // Or this.metaService.addTags([ { name: "description", content: "How to optimize your Angular App for search engine and other crawlers." }, { name: "author", content: "DNT" }, { name: "keywords", content: "Angular, Meta Service" } ], false); // --> forceCreation = false this.metaService.addTag({ name: "twitter:card", content: "summary_large_image" }); this.metaService.addTag({ name: "twitter:site", content: "@my_site" }); this.metaService.addTag({ name: "twitter:title", content: "Front-end Web Development" }); this.metaService.addTag({ name: "twitter:description", content: "Learn frontend web development..." }); this.metaService.addTag({ name: "twitter:image", content: "https://site/images/image.png" }); // Or this.metaService.addTags([ { name: "twitter:card", content: "summary_large_image" }, { name: "twitter:site", content: "@my_site" }, ], false); // --> forceCreation = false
دریافت محتوای متاتگهای موجود
با استفاده از متد getTag میتوان یک متاتگ مشخص را به صورت HTMLMetaElement دریافت کرد:
// getTag & getTags const viewport = this.metaService.getTag("name=viewport"); if (viewport) { console.log(viewport.content); // width=device-width, initial-scale=1 } const author = this.metaService.getTag("name=author"); if (author) { console.log(author.content); // DNT } this.metaService.addTag({ name: "author", content: "DNT" }); this.metaService.addTag({ name: "author", content: "Other Author" }, true); const authors = this.metaService.getTags("name=author"); console.log(authors[0]); // <meta name="author" content="DNT"> console.log(authors[1]); // <meta name="author" content="Other Author">
به روز رسانی متاتگهای موجود
میتوان از متد updateTag برای تغییر محتوای متاتگی موجود، استفاده کرد:
// updateTag this.metaService.addTag({ name: "twitter:card", content: "summary_large_image" }); this.metaService.updateTag({ name: "twitter:card", content: "summary" }, `name='twitter:card'`); this.metaService.updateTag({ name: "description", content: "Angular meta service" });
حذف تگهای موجود
در اینجا میتوان از دو متد removeTag که یک attribute-selector را دریافت میکند و یا removeTagElement که یک HTMLMetaElement را توسط متد getTag دریافت میکند، برای حذف کامل این تگها استفاده کرد:
// removeTag & removeTagElement this.metaService.removeTag("charset"); // Or const chartsetTag = this.metaService.getTag("charset"); if (chartsetTag) { this.metaService.removeTagElement(chartsetTag); }
تنظیم عنوان صفحهی جاری
سرویس توکار دیگری به نام Title امکان تغییر عنوان صفحهی جاری را به صورت پویا میسر میکند:
// Setting the browser page Title in an Angular app const currentTitle = this.titleService.getTitle(); console.log(currentTitle); this.titleService.setTitle("newTitle ...");
طراحی سرویسی برای افزودن پویای متاتگها به صفحات مختلف سایت
میتوان شبیه به مطلب «نمایش Breadcrumbs در برنامههای Angular» به قسمت data مسیریابی، اطلاعات عنوان صفحه و همچنین metaTags آنرا اضافه کرد:
const routes: Routes = [ { path: "seo", component: SeoTestsComponent, data: { title: "Page Title", metaTags: { description: "Page Description or some content here", keywords: "some, keywords, here, separated, by, a comma" } } } ];
به همین جهت سرویس SEO را در مسیر src\app\core\seo-service.ts به صورت ذیل ایجاد میکنیم:
import { Injectable } from "@angular/core"; import { Title, Meta } from "@angular/platform-browser"; import { Router, NavigationEnd, ActivatedRouteSnapshot } from "@angular/router"; @Injectable() export class SeoService { constructor(private titleService: Title, private metaService: Meta, private router: Router) { } enableSeo() { this.router.events .filter(event => event instanceof NavigationEnd) .distinctUntilChanged() .subscribe(() => { this.addMetaData(this.router.routerState.snapshot.root); }); } private addMetaData(root: ActivatedRouteSnapshot): void { if (root.children && root.children.length) { this.addMetaData(root.children[0]); } else if (root.data) { this.setTitle(root.data); this.setMetaTags(root.data); } } private setMetaTags(routeData: { [name: string]: any; }) { const routeDataMetaTagsKey = "metaTags"; const metaTags = routeData[routeDataMetaTagsKey]; if (!metaTags) { return; } for (const tag in metaTags) { if (metaTags.hasOwnProperty(tag)) { const newTag = { name: tag, content: metaTags[tag] }; console.log("new tag", newTag); this.metaService.addTag(newTag); } } } private setTitle(routeData: { [name: string]: any; }) { const routeDataTitleKey = "title"; const title = routeData[routeDataTitleKey]; if (title) { console.log("new title", title); this.titleService.setTitle(title); } } }
در اینجا در ابتدای کار مشترک رخداد NavigationEnd سیستم مسیریابی خواهیم شد:
this.router.events .filter(event => event instanceof NavigationEnd) .distinctUntilChanged() .subscribe(() => { this.addMetaData(this.router.routerState.snapshot.root); });
سپس در این متد خاصیت data مسیرنهایی را خوانده و کلیدهای title و metaTags آنرا استخراج میکنیم و سپس توسط متدهای this.titleService.setTitle و this.metaService.addTag، این عنوان و متاتگهای جدید را به صورت پویا به صفحه اضافه خواهیم کرد.
پس از تعریف این سرویس، برای معرفی آن به برنامه، ابتدا آنرا به قسمت providers مربوط به CoreModule اضافه میکنیم:
import { SeoService } from "./seo-service"; @NgModule({ providers: [ SeoService ] }) export class CoreModule {}
import { SeoService } from "./core/seo-service"; export class AppComponent { constructor(private seoService: SeoService) { this.seoService.enableSeo(); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.