mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(router): emit activate/deactivate events when an outlet gets attached/detached (#43333)
Previously the events of `RouterOutlet` (activate/deactivate) were not fired when an outlet got attached/detached with `RouteReuseStrategy`. The changes configure `RouterOutlet` to emit events when an outlet gets attached/detached. Fixes #25521, #20501 PR Close #43333
This commit is contained in:
committed by
Alex Rickabaugh
parent
8d2b6affcd
commit
4f3beffdbf
@@ -615,6 +615,7 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
// (undocumented)
|
||||
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null): void;
|
||||
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): void;
|
||||
attachEvents: EventEmitter<unknown>;
|
||||
// (undocumented)
|
||||
get component(): Object;
|
||||
// (undocumented)
|
||||
@@ -622,6 +623,7 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
// (undocumented)
|
||||
deactivateEvents: EventEmitter<any>;
|
||||
detach(): ComponentRef<any>;
|
||||
detachEvents: EventEmitter<unknown>;
|
||||
// (undocumented)
|
||||
get isActivated(): boolean;
|
||||
// (undocumented)
|
||||
@@ -637,10 +639,12 @@ export interface RouterOutletContract {
|
||||
activateEvents?: EventEmitter<unknown>;
|
||||
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null): void;
|
||||
attach(ref: ComponentRef<unknown>, activatedRoute: ActivatedRoute): void;
|
||||
attachEvents?: EventEmitter<unknown>;
|
||||
component: Object | null;
|
||||
deactivate(): void;
|
||||
deactivateEvents?: EventEmitter<unknown>;
|
||||
detach(): ComponentRef<unknown>;
|
||||
detachEvents?: EventEmitter<unknown>;
|
||||
isActivated: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,18 @@ export interface RouterOutletContract {
|
||||
* Emits a deactivate event when a component is destroyed.
|
||||
*/
|
||||
deactivateEvents?: EventEmitter<unknown>;
|
||||
|
||||
/**
|
||||
* Emits an attached component instance when the `RouteReuseStrategy` instructs to re-attach a
|
||||
* previously detached subtree.
|
||||
**/
|
||||
attachEvents?: EventEmitter<unknown>;
|
||||
|
||||
/**
|
||||
* Emits a detached component instance when the `RouteReuseStrategy` instructs to detach the
|
||||
* subtree.
|
||||
*/
|
||||
detachEvents?: EventEmitter<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,12 +125,17 @@ export interface RouterOutletContract {
|
||||
* `http://base-path/primary-route-path(outlet-name:route-path)`
|
||||
*
|
||||
* A router outlet emits an activate event when a new component is instantiated,
|
||||
* and a deactivate event when a component is destroyed.
|
||||
* deactivate event when a component is destroyed.
|
||||
* An attached event emits when the `RouteReuseStrategy` instructs the outlet to reattach the
|
||||
* subtree, and the detached event emits when the `RouteReuseStrategy` instructs the outlet to
|
||||
* detach the subtree.
|
||||
*
|
||||
* ```
|
||||
* <router-outlet
|
||||
* (activate)='onActivate($event)'
|
||||
* (deactivate)='onDeactivate($event)'></router-outlet>
|
||||
* (deactivate)='onDeactivate($event)'
|
||||
* (attach)='onAttach($event)'
|
||||
* (detach)='onDetach($event)'></router-outlet>
|
||||
* ```
|
||||
*
|
||||
* @see [Routing tutorial](guide/router-tutorial-toh#named-outlets "Example of a named
|
||||
@@ -137,6 +154,16 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
|
||||
@Output('activate') activateEvents = new EventEmitter<any>();
|
||||
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
|
||||
/**
|
||||
* Emits an attached component instance when the `RouteReuseStrategy` instructs to re-attach a
|
||||
* previously detached subtree.
|
||||
**/
|
||||
@Output('attach') attachEvents = new EventEmitter<unknown>();
|
||||
/**
|
||||
* Emits a detached component instance when the `RouteReuseStrategy` instructs to detach the
|
||||
* subtree.
|
||||
*/
|
||||
@Output('detach') detachEvents = new EventEmitter<unknown>();
|
||||
|
||||
constructor(
|
||||
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
|
||||
@@ -203,6 +230,7 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
const cmp = this.activated;
|
||||
this.activated = null;
|
||||
this._activatedRoute = null;
|
||||
this.detachEvents.emit(cmp.instance);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@@ -213,6 +241,7 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
this.activated = ref;
|
||||
this._activatedRoute = activatedRoute;
|
||||
this.location.insert(ref.hostView);
|
||||
this.attachEvents.emit(ref.instance);
|
||||
}
|
||||
|
||||
deactivate(): void {
|
||||
|
||||
@@ -5831,6 +5831,60 @@ describe('Integration', () => {
|
||||
expect(router.routeReuseStrategy).toBeInstanceOf(AttachDetachReuseStrategy);
|
||||
});
|
||||
|
||||
it('should emit an event when an outlet gets attached/detached', fakeAsync(() => {
|
||||
@Component({
|
||||
selector: 'container',
|
||||
template:
|
||||
`<router-outlet (attach)="recordAttached($event)" (detach)="recordDetached($event)"></router-outlet>`
|
||||
})
|
||||
class Container {
|
||||
attachedComponents: any[] = [];
|
||||
detachedComponents: any[] = [];
|
||||
|
||||
recordAttached(component: any): void {
|
||||
this.attachedComponents.push(component);
|
||||
}
|
||||
|
||||
recordDetached(component: any): void {
|
||||
this.detachedComponents.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Container],
|
||||
imports: [RouterTestingModule],
|
||||
providers: [{provide: RouteReuseStrategy, useClass: AttachDetachReuseStrategy}]
|
||||
});
|
||||
|
||||
const router = TestBed.inject(Router);
|
||||
const fixture = createRoot(router, Container);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
router.resetConfig([{path: 'a', component: BlankCmp}, {path: 'b', component: SimpleCmp}]);
|
||||
|
||||
cmp.attachedComponents = [];
|
||||
cmp.detachedComponents = [];
|
||||
|
||||
router.navigateByUrl('/a');
|
||||
advance(fixture);
|
||||
expect(cmp.attachedComponents.length).toEqual(0);
|
||||
expect(cmp.detachedComponents.length).toEqual(0);
|
||||
|
||||
router.navigateByUrl('/b');
|
||||
advance(fixture);
|
||||
expect(cmp.attachedComponents.length).toEqual(0);
|
||||
expect(cmp.detachedComponents.length).toEqual(1);
|
||||
expect(cmp.detachedComponents[0] instanceof BlankCmp).toBe(true);
|
||||
|
||||
// the route will be reused by the `RouteReuseStrategy`
|
||||
router.navigateByUrl('/a');
|
||||
advance(fixture);
|
||||
expect(cmp.attachedComponents.length).toEqual(1);
|
||||
expect(cmp.attachedComponents[0] instanceof BlankCmp).toBe(true);
|
||||
expect(cmp.detachedComponents.length).toEqual(1);
|
||||
expect(cmp.detachedComponents[0] instanceof BlankCmp).toBe(true);
|
||||
}));
|
||||
|
||||
it('should support attaching & detaching fragments',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
Reference in New Issue
Block a user