ngOnInit method called twice - angularjs

I'm a little bit new in Angular:
When I load my Angular application (Angular 4), a specific component's ngOnInit() method called even if it wasn't rendered. After I navigate my app to a route that renders this component, the ngOnInit() called again. Why is that? Is this some kind of error, or is this how it works? There is no error in the console.
(Because of this, in my component's ngOninit() a subscribe method runs twice.)
Update:
offer-info.component.ts:
import { Component, Input, OnInit, NgZone } from '#angular/core';
import { OfferService } from './offer.service';
import { Offer } from '../models';
declare var $: any;
#Component({
moduleId: module.id,
selector: 's2a-offer-info',
templateUrl: './offer-info.component.html',
})
export class OfferInfoComponent implements OnInit {
private zone: NgZone;
offer: Offer = null;
constructor(
private offerService: OfferService,
) { }
ngOnInit() {
//This is where I get two 'test' output on the console:
console.log('test');
this.zone = new NgZone({enableLongStackTrace: true});
this.offerService.getOfferForOfferInfo().subscribe((offer: Offer) => {
if (offer !== null) {
this.zone.run(() => {
this.offer = offer;
$('#s2aOfferInfoModal').modal();
});
}
});
}
}
In page-packages.component.html:
...
<!-- This tag appers only once in the whole application: -->
<s2a-offer-info></s2a-offer-info>
...
In app-routing.module.ts:
...
{ path: 'packages', component: PagePackagesComponent, canActivate: [LoggedInGuard] },
{ path: 'search', component: PageSearchComponent, canActivate: [LoggedInGuard] },
{ path: '', redirectTo: '/search', pathMatch: 'full' },
...

Related

Angular Jasmine Karma Background Image Component Test

I am new to Angular JS and I would like to test if the background image that I have on my header component is loaded correctly. However, on the console log, it says that the image file is not found.
Here's the whole code for your reference:
HTML:
**<div class="flex-container" [ngStyle]="{'background-image' : 'url('+ bgImage +')'}">**
<div class="text-container">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<a [href]="buttonLink" target="_blank"><button>{{ buttonText }}</button></a>
</div>
</div>
Component.ts:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
#Input() title: string;
#Input() description: string;
#Input() buttonText: string;
#Input() buttonLink: string;
**#Input() bgImage: string;**
constructor() { }
ngOnInit(): void {
}
}
Component.spec.ts:
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
});
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
it('should render all inputs', () => {
component.title = 'test'
component.description = 'test-description'
**component.bgImage = 'bgImageIllustrations.jpg'**
fixture.detectChanges();
const title = fixture.debugElement.query(By.css('h1')).nativeElement as HTMLElement
const description = fixture.debugElement.query(By.css('p')).nativeElement as HTMLElement
**const bgImage = fixture.debugElement.query(By.css('.flex-container')).nativeElement as HTMLElement;**
expect(title.textContent).toBe('test');
expect(description.textContent).toBe('test-description');
**console.log(getComputedStyle(bgImage).backgroundImage);**
});
});
Hope you can help me to fix this issue. Thanks!
I would like to see if I am loading the right background image

How do I transfer the array to the routing parameter?

*I'm making a angular project (version 6+).
I want to transfer array parameter, using routing.
*
const routes: Routes = [
{
path: 'books',
component: BookComponent
}
,
{
path: 'books/:id',
component: BookDetailComponent
},
...
There are two ways you can use,
Method 1:
HTML
<a routerLinkActive="active" [routerLink]="['/home', userIDs.join()]">Home</a>
TS:
userIDs: Array<number> = [1, 3, 4];
Home component typescript:
export class HomeViewComponent implements OnInit {
userIDs: Array<number> = [];
constructor(private router: ActivatedRoute) { }
ngOnInit() {
this.router.params.subscribe(params => {
this.userIDs = params['ids'].split(',');
});
}
}
Router Module:
path: 'home/:ids', component: HomeViewComponent }
Method 2 (Using queryParams):
You don't need to add anything into router module as we are doing in above method.
HTML
<a routerLinkActive="active" [routerLink]="['/home']" [queryParams]="{ids: userIDs}">Home</a>
TS
userIDs: Array<number> = [1, 3, 4];
Home component typescript:
export class HomeViewComponent implements OnInit {
userIDs: Array<number> = [];
constructor(private router: ActivatedRoute) { }
ngOnInit() {
this.router.queryParams.subscribe(p => {
this.userIDs = p.ids;
});
}
}
In html
send data
here first You will import the activated route like following.
import { ActivatedRoute } from '#angular/router';
and also in construction
construction(private _routes:ActivatedRoute) { }
In====== ngOnInit( this._route.params.subscribe(params => {
this.id=param['id'];
)

why subscribe function is not called in angular 2?

I am using observable in angular .Actually my issue when I click button my subscribe function not called why ?
as per documentation subscribe function will call when we call next function
https://plnkr.co/edit/83NaHoVaxiXAeUFoaEmb?p=preview
constructor() {
this.data = new Observable(observer => this.dataObserver = observer);
this.data.subscribe(value => {
console.log('+++')
console.log(value)
})
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
here is documentation
http://reactivex.io/rxjs/manual/tutorial.html
On basis of Volodymyr Bilyachat suggestion i have modified your code. its working now plz check. Problem was in your way of using dataObserver
//our root app component
import {Component, NgModule} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
#Component({
selector: 'my-app',
template: `
<div>
<ul>
<li *ngFor ="let n of name">{{n.name}}</li>
</ul>
<button (click)="hndle()">heelo</button>
</div>
`,
})
export class App {
private data:Observable;
private dataObserver:Observer;
name:string;
name[];
constructor() {
this.dataObserver = new Observable(observer => this.dataObserver = observer);
this.dataObserver.subscribe(value => {
console.log('+++')
console.log(value)
});
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
link https://plnkr.co/edit/PO80y2udrOhsVq4QQXc5?p=preview
I believe you are subscribing to the observable 2 times. You should be able to fix it by adding .share()
constructor() {
this.data = new Observable(observer => this.dataObserver = observer).share();
this.data.subscribe(value => {
console.log('+++')
console.log(value)
})
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
In your case, it's better to use this solution:
constructor() {
this.data = new Subject();
this.data.subscribe(value => {
console.log('+++');
console.log(value);
});
}
hndle() { // TYPO: Probably it was meant to be handle
this.name.push({
name: 'navee'
});
this.data.next(this.name);
}
Don't forget to add:
import { Subject } from 'rxjs/Subject'
Working example:
https://plnkr.co/edit/zB8FHTVEm2QUHiEAYuQB?p=preview

How to test my RouteConfig and other decorators in an Angular 2 Component

I am trying to write unit tests for my Angular 2 component before I go any further but I have no idea how to write a test which checks whether my RouteConfig's path is set to '/documents'. I also would like to have a test which verifies that my template contains <router-outlet></router-outlet> inside.
This is my component:
import { Component } from 'angular2/core';
import { CanActivate, RouteConfig, ROUTER_DIRECTIVES } from angular2/router';
import DocumentsComponent from './documents/documents.component';
#Component({
template: `
dashboard
<router-outlet></router-outlet>
`,
directives: [
ROUTER_DIRECTIVES,
]
})
#CanActivate((next, prev) => {
// check if user has access
return true;
})
#RouteConfig([
{
path: '/documents',
name: 'Documents',
component: DocumentsComponent,
useAsDefault: true,
}
])
export default class DashboardComponent {
public name: string = 'John';
sayHello(): string {
return `Hello ${this.name}`;
}
}
And this is my test (I'm using Jasmine):
import DashboardComponent from './../app/dashboard/dashboard.component';
import {describe, it, beforeEach, expect} from 'angular2/testing';
describe('My DashboardComponent', () => {
var dashboard: DashboardComponent = null;
beforeEach(function() {
dashboard = new DashboardComponent();
});
it('should have its path set to "/documents"', function() {
// ???
});
it('should have "<router-outlet></router-outlet>" in its template', function() {
// ???
});
it('should have name property', function() {
expect(dashboard.name).toBe('John');
});
it('should say hello with name property', function() {
expect(dashboard.sayHello()).toBe('Hello John');
});
});
In case you haven't found any answers yet: https://medium.com/google-developer-experts/angular-2-unit-testing-with-jasmine-defe20421584#.13taw92p0

Angular2. How to get access to the all level routeConfig elements form directives

I want to create a directives which should be check ACL defined on the routeConfig element by extend routerData
How can I get access to the routerData defined in routerConfig? (must have all levels of menu)
Example:
My ACL direvtives
#Directive({
selector: '[myAcl]'
})
export class MyAclDirective {
#Input('routerLink')
routeParams: any[];
/**
*
*/
constructor(private _elementRef: ElementRef, private router:Router) {
}
ngAfterViewInit(){
//Key name to find in 'routeConfig' acl definition of 'routerData'
let componentAliasName = this.routeParams;
//NEED TO GET ACCESS TO ROUTECONFIG DEFINITION
//
//Example:
// #RouteConfig([
// { path:'/user/...', name: 'UserLink', component: UserComponent, data: {res: 'user', priv: 'show'} } ])
// AND GET BY 'name' == 'componentAliasName' my extend acl definition "data: {res: 'user', priv: 'show'}"
console.log("CHECK ACL: " + data.res + " | " + data.priv);
//ACL service
let hasAccess = AclService.checkAccess(data.res, data.priv);
if (!hasAccess) {
let el : HTMLElement = this._elementRef.nativeElement;
el.parentNode.removeChild(el);
}
}
}
MainComponent
#RouteConfig([
{ path:'/home', name: 'HomeLink', component: HomeComponent, useAsDefault: true },
{ path:'/user/...', name: 'UserLink', component: UserComponent, data: {res: 'user', priv: 'show'} }
])
#Component({
selector: 'main-app',
template: `
<ul>
<li><a myAcl [routerLink]="['HomeLink']">Home</a></li>
<li><a myAcl [routerLink]="['UserLink']">User</a>
<ul>
<li><a myAcl [routerLink]="['ProfileLink']">Profile</a>
<ul>
<li><a myAcl [routerLink]="['SubProfileLink']">SubProfile</a>
</ul>
</li>
</ul>
</li>
</ul>
<router-outlet></router-outlet>
`,
directives: [ MY_ACL_DIRECTIVES, ROUTER_DIRECTIVES ],
})
export class MainComponent {
}
UserComponent
#RouteConfig([
{ path: '/profile/...', name: 'ProfileLink', component: ProfileComponent, data: {res: 'profile', priv: 'show'} }
])
#Component({
selector: 'user-id',
template: `<h1>User</h1>`,
directives: [ROUTER_DIRECTIVES, MY_ACL_DIRECTIVES]
})
export class UserComponent {
}
ProfileComponent
#RouteConfig([
{ path: '/subprofile', name: 'SubProfileLink', component: SubProfileComponent, data: {res: 'profile', priv: 'details'} }
])
#Component({
selector: 'profile-id',
template: `<h1>Profile</h1>`,
directives: [ROUTER_DIRECTIVES, MY_ACL_DIRECTIVES]
})
export class ProfileComponent {
}
SubProfileComponent
#Component({
selector: 'subprofile-id',
template: `<h1>Sub Profile</h1>`,
directives: [ROUTER_DIRECTIVES, MY_ACL_DIRECTIVES]
})
export class SubProfileComponent {
}
Related link
You should be able to use the CanActivate decorator. It would look something like this:
#Component({...})
#CanActivate((next, prev) => (hasAccess))
export class UserComponent {
// your component code
}
You can read more here: https://angular.io/docs/ts/latest/api/router/CanActivate-decorator.html

Resources