Angular 7 - Multiple outlets : Error: Cannot activate an already activated outlet - angular7

Here is the issue that I'm encountering with Angular 7 :
I have two outlets : the main app router outlet, and a secondary outlet named 'administration'.
When I want to navigate through any administration link at start, it works fine. But next time, when I try to navigate, angular throws this error message :
Error: Cannot activate an already activated outlet
So, can someone explain me why ? I haven't found any solution on forums...
Here is a stackblitz : https://stackblitz.com/edit/angular-osnnd4
Thank you all everybody :)

The problem occurs when lazyloading child routes. You have to manually deactivate the outlet everytime you change a route.
I have modified your AdministrationComponent to workaround as follow. It should be able to work for now, until Angular have a way to solve the problem.
import { Component, OnInit, ViewChild } from '#angular/core';
import { RouterOutlet, Router, ActivationStart } from '#angular/router';
#Component({
selector: 'app-administration',
templateUrl: './administration.component.html',
styleUrls: ['./administration.component.css']
})
export class AdministrationComponent implements OnInit {
#ViewChild(RouterOutlet) outlet: RouterOutlet;
constructor(
private router: Router
) { }
ngOnInit(): void {
this.router.events.subscribe(e => {
if (e instanceof ActivationStart && e.snapshot.outlet === "administration")
this.outlet.deactivate();
});
}
}

This might be silly, but for anyone looking for a solution to this:
Make sure that in there's no undefined variable in any of the inner or children outlets.
I fixed this in my project and everything is back to normal.

My problem:
Basically my named outlet wasnt deactivating, in my case I would use it for a bunch of different modals, and while manually deactivating my modal outlet would work, it didnt work when loading a different path and the same component into the outlet so I could not recycle my componet, in my case the modal component.
So as pointed by #Andreas in here (https://github.com/angular/angular/issues/20694#issuecomment-595707956). I didn't have any asigned components for an empty path, so I am guessing it couldnt deactivate the outlet somehow because of that, so I just asigned an emptyComponent for path: "".
The first way: to fix this I will be using an empty component for "" or non truthy routes
app-router-module.ts
...
{
outlet: "modal",
path: "",
component: EmptyComponent,
},
...
Another way: If you want, you can do this instead when closing a view
model.component.ts
this.router.navigate([
{
outlets: {
modal: null,
},
},
]);
either way the router deactivates, better yet I think you should use the second example as it deactivates and destroys the view. in my case I had a json with non truthy routes so I just had to map those to null values.

Check thoroughly if you have imported a lazy-loaded module in its parent module or some other module irrespective of the routing. If so, remove its import from there.
That was it in my case.

My problem was that I have a login guard which performs a redirect via this.router.navigate, and the guard was returning true after redirects.
When I changed the code to return false in the case of a redirect, the issue has been gone.

Related

Trigger AngularComponent-constructor on startup with PreLoadingStrategy

The goal: trigger a component residing in a module, so my subscription in the ctor of the component is activated.
I'm using PreloadAllModules as a preloadingStrategy. But it's not happening.
I need to subscribe to some events in de constructor of my FriendsComponent.
the setup is like this:
FriendsComponent is shown in the template of the SocialComponent, which is part of the SocialModule.
social.component.html
<div>
<friends-component></friends-component>
</div>
the SharedModule declares the FriendsComponent.
AppModule imports SharedModule,
RouterModule for AppModule is like this:
{
path: 'social',
component: SocialModule,
children: [
{
path: 'friends',
component: FriendsComponent
}
]
},
I think the problem is because the FriendsComponent is not part of a router-outlet?
Can it be done without a router-outlet?
If a module would be pre- or eager loaded, would it automatically trigger the constructors (and the subscription)?
Is the issue with my preloading strategy?
I have tried adding: data:{preload:true} to the paths declared in routermodule.
Everything works fine, when the user activates the SocialModule (for instance by clicking on a button with a routerLink to social/friends), but I want it activated on startup (just not shown on any html)
I'm working with Angular Ivy, but think I'm still missing the points. Any help is appreciated
You need to handle your initial subscriptions in a service and have the component subscribe to that service. You won't need to touch the routes. It what services are for.
You subscribe to the value you need in your FriendService and have FriendComponent subscribe to your FriendService.

Angular 6 how can I go back to parent then come to child in router.navigate()

Hi I'm having parent component 'Order' and two child component for it 'History' and 'Orders'. Current url is order/history/id I want to go back to 'Orders' component like order/orders/id so I'm using Router in my app.
this.router.navigate(['../../orders', orderId]);
in history component as suggested in How do I navigate to a parent route from a child route? but it showing cannot match any routes url segment orders/7y6786 What is the best approach to solve this? Thank you.
As you are using the navigate inside a component, you may use the ActivatedRoute service.
So in you History Component, try to do this:
constructor(
private route: ActivatedRoute,
private router: Router
) {}
navigateToOrders() {
this.router.navigate(['orders', orderId], { relativeTo: this.route.parent })
}

Proper way to navigate with React Native, Redux, and Navigator

I have a React Native app using the Redux framework and I'm using the Navigator component to handle navigation. I've had a little bit of trouble getting the navigation working and I'm not able to find any good examples of how to do it correctly so I'm looking for some help and clarification.
Here's the gist of what I currently have, which is working but I don't know if I'm doing it right:
Root Component
...
renderScene(route, navigator) {
console.log('RENDER SCENE', route);
const Component = route.component;
return (
<Component navigator={navigator} route={route} {...this.props} />
)
}
render() {
return (
<Navigator
renderScene={(route, nav) => this.renderScene(route, nav)}
initialRoute={{ name: 'Signin', component: Signin }} />
)
}
Signin Component
...
componentWillReceiveProps(nextProps) {
if (!this.props.signedIn && nextProps.signedIn) {
console.log('PUSHING TO MAIN');
nextProps.navigator.push({ name: 'Main', component: Main });
}
}
Questions:
1: My first thought here is that I should probably move the navigator.push code into an action. However is componentWillReceiveProps the right place to call the action? When the Signin component is loaded it fires an action to sign in the user if they already have an active session. By default they are not signed in so when the next props come through I check if it changed and then push to Main.
2: In my console log immediately after 'PUSH TO MAIN' is logged I see two 'RENDER SCENE' logs:
[info] 'RENDER SCENE', { name: 'Signin', component: [Function: Signin] } (EXPECTED)
[info] 'PUSHING TO MAIN'
[info] 'RENDER SCENE', { name: 'Signin', component: [Function: Signin] } (WHY?)
[info] 'RENDER SCENE', { name: 'Main', component: [Function: Main] }
I'm curious as to why RENDER SCENE is called twice (the first one being the Signin component) if I'm only pushing the Main component.
Also originally in the componentWillReceiveProps method I was only checking:
if (nextProps.signedIn) {
nextProps.navigator.push({ name: 'Main', component: Main });
}
but this caused the Main component to be pushed twice.
NOTE: The GitHub link below is now deprecated, in the comments the
author suggested react-native-router-flux which is by the same
author.
I've just added support for Redux, with my new component https://github.com/aksonov/react-native-redux-router with makes navigation pretty easy, like calling Actions.login
This is not a redux implementation but for routing I found react-native-router-flux to be really useful.
You can do things like call
Actions.login
to navigate to the login view. The routes are defined in your index file and have optional schemas to define navbar elements.
https://github.com/aksonov/react-native-router-flux
1) Yes, move it to method, componentWillReceiveProps is probably not correct. It’s difficult to refactor that part for you as I would not have that logic from that method on that component, i.e. the signinComp should receive state of whether it has an auth session or not (and not figure it out for itself). It leads to it getting processed for no reason; since if he is logged in you redirect. I would personally do that check in renderScene, since the nav gets passed down you can just make a push/pop on child components and handle all your logic in one renderScene.
2) See the navigation stack like a deck of cards. When you set the scene it is one card, but when you push it adds another card to the pile. So when you push and have two cards, it checks to make sure that all the cards are face up and rendered so that when you press back or pop it moves back to a completed card or scene. Imagine pushing 5 scenes immediately onto the stack. So when you change the stack it checks to ensure everthing is rendered and ready for that back button. Personally I don’t find that optimal (but it has to since you can pass different properties with each scene that you push).
You can probably change this line to:
renderScene={this.renderScene}
Question1:
I recommend you process navigator logic in shouldComponentUpdate method, like below,
shouldComponentUpdate(nextProps, nextState){
if(nextProps.isLoggedIn != this.props.isLoggedIn && nextProps.isLoggedIn === true){
//will redirect
// do redirect option
return false;
}
return true;
}
The Question 2:
I think this is the bug of react-native.

How do I programatically select and disable a PaperButton?

I'm trying to migrate from Bootstrap to Polymer/Paper and I've got a lot of situations where I have to enable/disable buttons that were previously accessed as ButtonElements and then .disabled=true/false.
So for example I've got a paper button I'm trying to access and enable/disable via querySelector as such:
PaperButton nextWeekButton = querySelector("#nextweekbutton");
nextWeekButton.disabled=!_status;
But I am getting the error: type 'HtmlElement' is not a subtype of type 'PaperButton' of 'nextWeekButton'.
I get the same variety of message if I try as an InputElement or ButtonElement. If I try to get it as an HtmlElement then of course I get the "setter 'disabled' is not defined..."
I was going to start playing around with attribute setting but really shouldn't there be a way to do this that's like the ButtonElement? Just want to make sure I am not missing anything.
Update: For now I am doing this
void disableButton(Element _e, bool _status) {
if(!_status) {
_e.setAttribute("disabled","true");
} else {
_e.attributes.remove("disabled");
}
}
I'm pretty sure the problem is that your code is executed before Polymer is initialized. See https://stackoverflow.com/a/20982658/217408 how to initialize Polymer when you have a custom main method.

Rebinding a page in AngularDart when the data is loaded via SignalR

I've got an AngularDart application that is working, but I feel like I'm doing it wrong...
The application shows a list of events as they are received. The events get to the app via SignalR, but I don't think that's really relevant - the issue is the way I'm having to update the component's state in order to see the changes in the state get displayed on the page.
Here is a cut down version of my component:
#Component( selector: 'liveEvents', templateUrl: 'live_events.html', useShadowDom: false )
class LiveEvents implements ScopeAware {
VmTurnZone _vmTurnZone;
EventReceiver _eventReceiver;
LiveEvents( this._vmTurnZone, this._eventReceiver );
List<Event> events = new List<Event>();
void _onEventReceived(Event event) {
//TODO: This just does not seem right...
_vmTurnZone.run(() => events.add(event));
}
void set scope(Scope scope) {
var _events = _eventReceiver.subscribeToAllEvents( "localhost", _onEventReceived );
}
}
The EventReceiver class is responsible for connecting to a SignalR server, receiving messages from that server and converting them into Event objects, and then calling whatever function was specified in the subscribeToAllEvents (in this case, _onEventReceived)
The angular template html is very simple:
<ul>
<li ng-repeat="event in events">Event: {{event.Id}}</li>
</ul>
This all works fine, but the bit I don't like is having to inject a VmTurnZone object into the component, and to have to update the events property from within a call to run on that VmTurnZone. It just seems a bit overly complicated to me. But if I DON'T do it in the VmTurnZone.run method, when the events property gets updated, the view is not updated to reflect that change. Only from within VmTurnZone.run does it work.
Is there a simpler way to do what I'm after here? I've looked at adding a watch to the scope, but that didn't seem to work (and looks to me like it should be used for changes that happen the other way around - ie, the view updates the scope, and you want to do something when that happens).
Can any AngularDart experts let me know if there's a better way to do what I'm after?
https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.Stream
You could add your events to stream and then listen for these and signalr will see these changes, for example:
#Component( selector: 'liveEvents', templateUrl: 'live_events.html', useShadowDom: false )
class LiveEvents implements ScopeAware {
EventReceiver _eventReceiver;
LiveEvents( this._eventReceiver );
List<Event> events = new List<Event>();
void set scope(Scope scope) {
subscribe().listen((event){
events.add(event);
});
}
Stream<Event> subscribe(){
var streamController = new StreamController<Event>();
_eventReceiver.subscribeToAllEvents( "localhost", (event){
streamController.add(event);
});
return streamController.stream;
}
}
As far as I know this is necessary when the source of the change is outside of the default Angular zone (for example some JavaScript library) for Angular to recognize model changes.

Resources