Angular Routing: Retrieving the latest route link inside the component state - angular2-routing

In order to provide a minimum example, suppose I have the following two routes specified in my RouterModule.forRoot, both linked to the same component SomeComponent.
const routes: Routes = [{ path: 'one-path', component: SomeComponent },
{ path: 'another-path', component: SomeComponent }];
And in the app html template I have the two router links along with the router-outlet
<a routerLink="/one-path">someLink</a>
<a routerLink="/another-path">anotherLink</a>
...
<router-outlet> </router-outlet>
What I want is have as part of the state of SomeComponent the last router link path due to which the component was rendered on the page. The purpose is to display a slightly different view depending on which link was used.
export class SomeComponent implements OnInit {
let route_path = ... //this should either be 'one-path' or 'another-path' depending on which link was clicked
}
How can this be achieved?

You need to subscribe to the actived route url observable. when component is initialize, route_path will either be 'one-path' or 'another-path' depending on which link was clicked or if you are on the home route.
export class SomeComponent implements OnInit {
route_path ='';
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.url.subscribe((url) => {
this.route_path = (url.length > 0 )? url[url.length-1].path: '';
});
}
}

Related

Storybook: how to validate form inside component on user interaction?

A Storybook newbie here. I have an Angular component with a FormControl, which when set to touched, might show an error (depending on the user interaction with the component itself). I've read this: https://storybook.js.org/docs/angular/writing-stories/decorators and tried adding a wrapper for the component which only contained a button. The idea was that user can click a button and thus validate the control inside the component. Didn't happen. Here is my setup:
export default {
title: 'Some title',
component: Component,
decorators: [
moduleMetadata: [...],
componentWrapperDecorator((story)=>`
<div>${story}</div>
<button (click)="doValidate"></button>
`),
]
} as Meta<Component>;
const template: Story<Component> = ( args: Component ) => {
component: Component,
props: args,
methods: {
doValidate(): void {
args.formControl.markAsTouched();
}
}
});
export const validate = template.bind({});
I concur that the method definition is out of place here. So here is the question: how do I trigger/set/mutate something inside a component from that wrapper?

Imported angular module doesn't work the first time a route is visited, but it works in the subsequent visits

I have a simple Loader Module hosted in a private npm repository. This module only intercepts http calls and displays a css loader. The code is simple: it has a public api that exports the module and a component. The module has an http interceptor that calls a service which sets a variable isLoading.
The component code is split into three files, as usual (ts, html and scss).
loader.component.ts
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { LoaderService } from '../service/loader.service';
#Component({
selector: 'loader',
templateUrl: './loader.component.html',
styleUrls: ['./loader.component.scss']
})
export class LoaderComponent implements OnInit {
public isLoading: boolean;
constructor(private loaderService: LoaderService) { }
ngOnInit(){
this.loaderService.isLoading.subscribe(status => this.isLoading = status);
}
}
loader.component.html
<div *ngIf="isLoading" class="overlay">
<div class="loader center-block"></div>
<p class="wait">
<strong>Wait... </strong>
</p>
</div>
The loader.component.scss file defines the loader, overlay and wait css classes (not shown for brevity).
The interceptor code is here:
#Injectable()
export class LoaderInterceptor implements HttpInterceptor {
constructor(private loaderService: LoaderService) {}
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<any>> {
this.loaderService.show();
return next.handle(request).pipe(finalize(() => this.loaderService.hide()));
}
}
And finally, the service code:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class LoaderService {
public isLoading: Subject<boolean>;
constructor() {
this.isLoading = new Subject<boolean>();
}
show() {
this.isLoading.next(true);
}
hide() {
this.isLoading.next(false);
}
}
This module is imported at the app.module.ts of an application and declared in the imports array of the decorator.
The application has some routes with lazy load feature modules.
The loader module is used at the app.component.html file, as follows:
<loader></loader>
<router-outlet></router-outlet>
The problem: the loader does not work the first time we visit a route, but it works if we click the previous button at the browser or we click to go back to homepage or any other way I tested visiting a route twice or more. Otherwise, it doesn't work.
I tried to find similar questions, I tried to see the order of css imports, I also tried to analise the loader module, the import order, but no clue about what is happening.

Angular 2 Opening a different form with a passed in parameter

Hope someone can advise the best way to achieve what I want.
I have a user grid where you can click edit it then goes to the manage user form.
What I want to do is pass the selected user id over to the manage user form and display this user, how best would I go about achieving this?
Thanks
Andy
In your grid.component.html, when user clicks on a grid edit button, call a function and pass the corresponding grid data id as shown below.
<button (click)="goToManageForm(gridId)"> Edit</button>
In your grid.component.ts, pass the id as a param to the manageForm component.
private goToManageForm(id:any){
this.router.navigate([`/manageForm/${id}`]);
}
In your routing component, add a route to manageForm component which expects a parameter.
const routes: Routes = [
{
path: '',
component: MyMainComponent,
canActivate: [AuthGuard],
children: [
{ path: 'grid', component: GridComponent },
{ path: 'manageForm/:gridId', component: ManageFormComponent },
]
}
You can access the passed grid id in the manageForm.component.ts as shown below.
import { Component, OnInit } from '#angular/core';
import { Router,ActivatedRoute } from '#angular/router';
import { Subscription } from 'rxjs';
export class manageFormComponent {
private subscription:Subscription;
private gridId:any;
constructor(private activatedRoute:ActivatedRoute){}
ngOnInit(): void {
// subscribe to router event
this.subscription = this.activatedRoute.params.subscribe(
(param: any) => {
if(typeof param['gridId'] !== 'undefined' && param['gridId'].trim !='' ){
this.gridId = param['gridId'];
}
});
}
}
Now the passed gridId will be accessible in this.gridId

How to change the behavior of Meteor accounts-ui so when a user logs in, the log out form automatically appears and vice versa

When a user logs in, I've tried adding a class to the 'logout form' that has a 'display: block' even with the '!important' tag which would override any display property on the logout form. I've tried reloading the page because that does bring up the logout form once a user logs in but it gets stuck in an infinite loop.
import React from 'react';
import ReactDOM from 'react-dom';
export default class AccountsUI extends React.Component {
componentDidMount() {
Accounts._loginButtonsSession.set('dropdownVisible', true);
this.view = Blaze.render(Template.loginButtons,
ReactDOM.findDOMNode(this.refs.container));
setTimeout(function () {
window.requestAnimationFrame(function() {
var node = ReactDOM.findDOMNode();
if (node !== undefined){
Accounts.onLogin(function(user){
document.getElementById('login-dropdown-list').className = "accounts-dialog hide-div"
console.log(document.getElementById('login-dropdown-list').className)
})
}
});
}, 250)
}
componentWillUnmount() {
Blaze.remove(this.view);
}
render() {
return <span ref="container" />
}
}
I'm also going to change how the class additions are triggered. I know that waiting 1/4 a second is very primitive and won't always work.
The Meteor.userId() function is reactive, which means if you call it in getMeteorData() it will be called again each time the userId changes. Save it to this.data, and use it in render().
I'd also suggest you create a React wrapper called LogoutUIWrapper for the Blaze component that only does wrapping and nothing else, just to make your life easier. See here: https://www.meteor.com/tutorials/react/adding-user-accounts
So you'll need to do something like this:
export default class AccountsUI extends React.Component {
getMeteorData() {
return {
userId: Meteor.userId(),
};
}
render() {
return (
<div>
{ this.data.userId ? null : <LogoutUIWrapper /> }
</div>
);
}
}
This way the LogoutUIWrapper component will only appear when the user is logged in.

Updating a template with a component input

Preface: I'm new to Meteor, Angular, and Typescript, so there is a very real possibility of an XY problem somewhere in here.
I'm working on a simple project management app using Meteor and Angular 2 (using the angular2-meteor package) where the structure (for now) consists of projects which have events. One view is a list of projects. Clicking on a project shows a modal of the project's details, including a list of the project's events. So, three components: ProjectList, ProjectDetails, and ProjectEventsList. ProjectDetails uses a Session variable to know which project to show, and that works. However, the list of events in the modal doesn't update after it is created for the first project clicked on.
ProjectEventsList.ts
import {Component, View} from 'angular2/core';
import {MeteorComponent} from 'angular2-meteor';
import {ProjectEvents} from 'collections/ProjectEvents';
#Component({
selector: 'projectEventsList',
inputs: ['projectId']
})
#View({
templateUrl: '/client/projectEventsList/projectEventsList.html'
})
export class ProjectEventsList extends MeteorComponent {
projectEvents: Mongo.Cursor<ProjectEvent>;
projectId: string;
constructor() {
super();
this.subscribe('projectEvents', this.projectId, () => {
this.autorun(() => {
this.projectEvents = ProjectEvents.find({projectId: this.projectId});
}, true);
});
}
}
As I understand it (though I may be way off here), I'm having difficulty getting autorun to, well, automatically run. I've tried putting a getter and setter on projectId and it does get updated when I click on a project, but the code inside autorun doesn't run after the first click. Things I've tried:
Switching the nesting of subscribe() and autorun().
Adding/removing the autobind argument to both subscribe() and autorun(). I don't really understand what that's supposed to be doing.
Moving the subscribe code to a setter on projectId:
private _projectId: string = '';
get projectId() {
return this._projectId;
}
set projectId(id: string) {
this._projectId = id;
this.subscribe('projectEvents', this._projectId, () => {
this.projectEvents = ProjectEvents.find({projectId: this._projectId});
}, true);
}
When I do this the list stops displaying any items.
If this all seems like it should work, I'll create a small test case to post, but I am hoping that something in here will be obviously wrong to those who know. Thanks!
this.subscribe() and this.autorun() doesn't seem to be part of the Angular component class. If this is an external library you might need to explicitly run it in an Angular zone for change detection to work:
constructor(private zone: NgZone) {
this.subscribe('projectEvents', this.projectId, () => {
this.autorun(() => {
zone.run(() => {
this.projectEvents = ProjectEvents.find({projectId: this.projectId});
});
}, true);
});
}
If you want to subscribe to events fired from the component itself use host-binding
#Component(
{selector: 'some-selector',
host: {'projectEvents': 'projectsEventHandler($event)'}
export class SomeComponent {
projectsEventHandler(event) {
// do something
}
}
I eventually got the setter method working, as shown below. It feels clunky, so I'm hoping there's a cleaner way to do this, but the below is working for me now (i.e., the list of events is updated when the parent component (ProjectList) sends a new projectId to the input.
ProjectEventsList.ts
import {Component, View} from 'angular2/core';
import {MeteorComponent} from 'angular2-meteor';
import {ProjectEvents} from 'collections/ProjectEvents';
#Component({
selector: 'projectEventsList',
inputs: ['projectId']
})
#View({
templateUrl: '/client/projectEventsList/projectEventsList.html'
})
export class ProjectEventsList extends MeteorComponent {
projectEvents: Mongo.Cursor<ProjectEvent>;
set projectId(id: string) {
this._projectId = id;
this.projectEventsSub = this.subscribe('projectEvents', this._projectId, () => {
this.projectEvents = ProjectEvents.find({projectId: this._projectId}, {sort: { startDate: 1 }});
}, true);
}
get projectId() {
return this._projectId;
}
constructor() {
super();
this.subscribe('projectEvents', this.projectId, () => {
this.projectEvents = ProjectEvents.find({projectId: this.projectId});
}, true);
}
}

Resources