Angular LAB: حفظ حالت مولفه در سراسر مسیر انتقال

بیایید به یک تمرین جدید سرگرم کننده شیرجه بزنیم!
تصور کنید یک برنامه کاربردی با دو مسیر دارید:
- یکی لیستی از کاربران را نمایش می دهد.
- دیگری جزئیات را برای یک کاربر خاص نشان می دهد.
میتوانید با کلیک کردن روی پیوندها بین این مسیرها حرکت کنید و همچنین میتوانید با استفاده از دکمههای «قبلی» و «بعدی» بین جزئیات کاربر جابهجا شوید.
در اینجا مثالی از آنچه ممکن است به نظر برسد آورده شده است:
چالش اول
هنگام بازگشت به مسیری که قبلاً بازدید کرده اید، برنامه دوباره داده ها را از سرور واکشی می کند.
در حالی که می توان به چندین روش به این موضوع پرداخت – مانند اجرای یک حافظه پنهان یا استفاده از یک فروشگاه جهانی برای حفظ وضعیت در سراسر انتقال مسیرها – یک مسئله ظریف دیگر وجود دارد.
چالش دوم: از دست دادن وضعیت DOM
هنگام دور شدن از یک مسیر، تمام حالت DOM را از دست می دهید. این به ویژه در مورد فرم ها مشکل ساز است. وقتی به فرم باز میگردید، همه دادههای وارد شده، وضعیتهای اعتبارسنجی و تغییرات رابط کاربری از بین میروند:
اگرچه مدیریت صحیح دولتی می تواند این را حل کند، اما ساده نیست. شما باید:
- حالت جزء را ذخیره کنید.
- برای بازیابی ظاهر و رفتار قبلی، همه تغییرات DOM را دوباره اعمال کنید.
این شامل اصلاح مقادیر فرم، حالت ها، خطاها و موارد دیگر است. و این به فرم ها محدود نمی شود – سایر عناصر رابط کاربری مانند آکاردئون نیز نیاز به درمان مشابه دارند.
خوشبختانه، Angular ابزار قدرتمندی برای رفع این مشکل ارائه میکند: RouteReuseStrategy
کلاس
RouteReuseStrategy: چه کاری انجام می دهد؟
شما مقالات عمیق زیادی در این زمینه خواهید یافت RouteReuseStrategy
، اما اصل ماجرا اینجاست: به شما این امکان را می دهد که کنترل کنید که Angular چه زمانی باید یک جزء را در حین پیمایش دفع یا حفظ کند.
در سناریوی ما، ما میخواهیم Angular بهجای از بین بردن مؤلفهها هنگام خروج از مسیر، آنها را کنار بگذارد (پنهان کند)، و هنگام بازگشت به عقب، آنها را همانطور که هست بازیابی کند.
به طور پیش فرض، Angular از یک استراتژی داخلی استفاده می کند، اما ما می توانیم آن را با یک پیاده سازی سفارشی لغو کنیم.
ایجاد یک استراتژی استفاده مجدد سفارشی
در اینجا نحوه تعریف سفارشی آمده است RouteReuseStrategy
:
@Injectable({
providedIn: 'root',
})
export class CustomReuseStrategy implements RouteReuseStrategy {}
// main.ts
bootstrapApplication(AppComponent, {
providers: [
// ...
{
provide: RouteReuseStrategy,
useExisting: CustomReuseStrategy,
},
],
});
توجه داشته باشید: علامت گذاری آن به عنوان Injectable
به شدت مورد نیاز نیست زیرا Angular آن را به صورت داخلی مدیریت می کند، اما این رویکرد انعطاف پذیری را ارائه می دهد – بیشتر در این مورد در یک لحظه.
تعریف رفتار استفاده مجدد از مسیر
بیایید یک کنوانسیون برای مدیریت مسیرها ایجاد کنیم:
- استفاده کنید
storeRoute: true
برای مسیرهایی که نباید تخریب شوند. - استفاده کنید
noReuse: true
برای مسیرهایی که نباید از اجزاء استفاده مجدد کنند، حتی اگر مسیرها یکسان باشند.
را noReuse
پرچم برای مسیرهایی با پارامترها حیاتی است (به عنوان مثال، /users/1
در مقابل /users/2
). بدون آن، این مسیرها نمونه یک مولفه را به اشتراک میگذارند که منجر به اشتراکگذاری حالت ناخواسته میشود.
در اینجا ممکن است مسیرهای شما چگونه به نظر برسند:
const routes: Routes = [
{
path: 'users',
component: UsersComponent,
data: {
storeRoute: true,
},
},
{
path: 'users/:id',
component: UserComponent,
data: {
noReuse: true,
storeRoute: true,
},
},
{
path: '**',
redirectTo: '/users',
},
];
چند تا یاری
برای پیاده سازی ما به چند کمک کننده نیاز داریم:
- تابعی که مسیر کامل یک مسیر را به منظور شناسایی آن می سازد
- تابعی که دو شی را برای مقایسه پارامترها و پارامترهای پرس و جو مقایسه می کند
در اینجا یک مثال برای هر دو:
function compareObjects(a: any, b: any): boolean {
return Object.keys(a).every(prop =>
b.hasOwnProperty(prop) &&
(typeof a[prop] === typeof b[prop]) &&
(
(typeof a[prop] === "object" && compareObjects(a[prop], b[prop])) ||
(typeof a[prop] === "function" && a[prop].toString() === b[prop].toString()) ||
a[prop] == b[prop]
)
);
}
// Returns the full path of a route, as a string
export function getFullPath(route: ActivatedRouteSnapshot): string {
return route.pathFromRoot
.map(v => v.url.map(segment => segment.toString()).join("/"))
.join("/")
.trim()
.replace(/\/$/, ""); // Remove trailing slash
}
در حال اجرا RouteReuseStrategy
بیایید کلاس را پیاده سازی کنیم. ابتدا باید یک شی بسازیم تا کامپوننت های خود را کش کند:
import {
ActivatedRouteSnapshot,
DetachedRouteHandle,
RouteReuseStrategy
} from "@angular/router";
interface StoredRoute {
route: ActivatedRouteSnapshot;
handle: DetachedRouteHandle;
}
@Injectable({
providedIn: 'root'
})
export class CustomReuseStrategy implements RouteReuseStrategy {
storedRoutes: Record<string, StoredRoute | null> = {};
}
من وارد جزئیات نمی شوم DetachedRouteHandle
، اما این چیزی است که وقتی مسیر دوباره بازدید می شود، مؤلفه ما را برمی گرداند.
حال باید روش های مورد نیاز را پیاده سازی کنیم RouteReuseStrategy
.
الف shouldDetach
روش تصمیم می گیرد که آیا یک مسیر را جدا کنیم یا نه: ما بر اساس آن تصمیم خواهیم گرفت storeRoute
ویژگی در پیکربندی مسیر ما.
// Should we store the route? Defaults to false.
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return !!route.data['storeRoute'];
}
الف store
روش به ما امکان می دهد مسیر خود را هنگام دور شدن از آن ذخیره کنیم. ما آن را با مسیر کامل با کمک خود از قبل ذخیره خواهیم کرد:
// Store the route
store(
route: ActivatedRouteSnapshot,
handle: DetachedRouteHandle
): void {
// Ex. users/1, users/2, users/3, ...
const key = getFullPath(route);
this.storedRoutes[key] = { route, handle };
}
الف shouldAttach
متد تصمیم میگیرد که مسیر را از حافظه پنهان خود بگیرد یا خیر. در اینجا، ما تمام پارامترهای مسیر را با هم مقایسه می کنیم queryParams
.
// Should we retrieve a route from the store?
shouldAttach(route: ActivatedRouteSnapshot): boolean {
const key = getFullPath(route);
const isStored = !!route.routeConfig && !!this.storedRoutes[key];
if (isStored) {
// Compare params and queryParams.
// Params, however, have already been compared because the key includes them.
const paramsMatch = compareObjects(
route.params,
this.storedRoutes[key]!.route.params
);
const queryParamsMatch = compareObjects(
route.queryParams,
this.storedRoutes[key]!.route.queryParams
);
return paramsMatch && queryParamsMatch;
}
return false;
}
الف retrieve
متد برای گرفتن مسیر از کش ما استفاده می شود:
// Retrieve from the store (it only needs the handle)
retrieve(route: ActivatedRouteSnapshot) {
const key = getFullPath(route);
if (!route.routeConfig || !this.storedRoutes[key]) return null;
return this.storedRoutes[key].handle;
}
در نهایت، shouldReuseRoute
تصمیم می گیرد که آیا مسیری با همان مسیر قبلی باید دوباره مورد استفاده قرار گیرد یا خیر. باز هم به کنوانسیون خود اشاره می کنیم و true
پیش فرض خواهد بود:
// Should the route be reused?
shouldReuseRoute(
previous: ActivatedRouteSnapshot,
next: ActivatedRouteSnapshot
): boolean {
const isSameConfig = previous.routeConfig === next.routeConfig;
const shouldReuse = !next.data['noReuse'];
return isSameConfig && shouldReuse;
}
روش های راحت برای پاکسازی حافظه پنهان
درک این نکته مهم است که وقتی یک مسیر در کش ذخیره می شود، مؤلفه مرتبط هرگز از بین نمی رود. این نیز به این معنی است ngOnInit
هنگامی که کاربر به مسیر برگشت دوباره فعال نخواهد شد. در حالی که این می تواند برای حفظ وضعیت مفید باشد، اما در صورت عدم مدیریت صحیح، خطر نشت حافظه را نیز به همراه دارد.
برای کاهش این مشکل، من توصیه میکنم روشهای راحت را برای پاکسازی دستی حافظه پنهان پیادهسازی کنید: یکی برای پاک کردن یک مسیر و دیگری برای پاک کردن همه مسیرها.
// Destroys the components of all stored routes
clearAllRoutes() {
for (const key in this.storedRoutes) {
if (this.storedRoutes[key]!.handle) {
this.destroyComponent(this.storedRoutes[key]!.handle);
}
}
this.storedRoutes = {};
}
// Destroys the component of a particular route.
clearRoute(fullPath: string) {
if (this.storedRoutes[fullPath]?.handle) {
this.destroyComponent(this.storedRoutes[fullPath].handle);
this.storedRoutes[fullPath] = null;
}
}
// A bit of a hack: manually destroy a particular component.
private destroyComponent(handle: DetachedRouteHandle): void {
const componentRef: ComponentRef<any> = (handle as any).componentRef;
if (componentRef) {
componentRef.destroy();
}
}
نتیجه گیری
در اینجا نتیجه نهایی است:
هنگام بازگشت به یک مسیر ذخیره شده، کامپوننت تمام حالت خود را یکپارچه حفظ می کند. علاوه بر این، هر زمان که نیاز بود، می توانید کش را به صورت دستی پاک کنید.
این راه حل بسیار “plug-and-play” است و می تواند به راحتی در پروژه های شما ادغام شود. با این حال، از آن عاقلانه استفاده کنید—استفاده نادرست می تواند برنامه شما را مستعد نشت حافظه کند.
لینک نسخه ی نمایشی کامل در اینجا.
AccademiaDev: دوره های توسعه وب مبتنی بر متن!
من معتقدم به ارائه محتوای مختصر و ارزشمند بدون طولانی شدن و پرکننده های غیرضروری موجود در کتاب های سنتی. این منابع که بهعنوان دورههای آنلاین تعاملی طراحی شدهاند، برگرفته از سالهای من به عنوان مشاور و مربی، بینشهای عملی را از طریق متن، تکههای کد و آزمونها ارائه میکنند و یک تجربه یادگیری کارآمد و جذاب را ارائه میدهند.