I have a search bar in app.component.ts that has an input value.
results.component.ts is embedded with a router-outlet in app.component.html
When results.component.ts is the current activated route, the search works fine and so do the results.
However, if someone clicks on a result, results.component.ts is replaced with a different component view detail.component.ts (it provides them with more information about the result.)
On detail.component.ts I have a "back to results" link setup with a routerLink='/'. And mind you, the search query is still present in the search bar because that view never gets replaced.
When this back button is clicked, results.component.ts reloads and fires ngOnInit.
The problem: I can't access the value of the search string in app.component.ts from results.component.ts ngOnInit to repopulate the results. I've tried almost everything I can think of.
I already have a service built, but I don't know how to set it up to communicate that value, if that is the solution.
Updated with code
app.component.html
//other html
<input placeholder="What do you want to learn?" name="searchStr" [(ngModel)]="searchStr" (keyup.enter)="searchCourse($event)">
interaction-service.service.ts:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class InteractionService {
// Observable string sources
private searchStr = new Subject<string>();
// Observable string streams
searchStr$ = this.searchStr.asObservable();
sendString(searchString: string) {
this.searchStr.next(searchString);
}
}
app.component.ts:
//other imports
import {InteractionService} from './interaction-service.service';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
providers: [CourseService, InteractionService]
})
export class AppComponent implements OnInit {
searchStr: string;
constructor(private _courseService: CourseService, private _interactionService: InteractionService, private router: Router) {
}
ngOnInit() {
}
searchCourse(event) {
this._interactionService.sendString(event.target.value);
this.router.navigateByUrl('/');
}
}
course-listings.component.ts (I referred to this as results above)
// other imports
import {Subscription} from 'rxjs';
import {InteractionService} from '../interaction-service.service';
#Component({
selector: 'app-course-listings',
templateUrl: './course-listings.component.html',
providers: [CourseService],
})
export class CourseListingsComponent implements OnInit {
//some other properties defined
#Input() searchStr: string;
subscription: Subscription;
constructor(private _courseService: CourseService, private _interactionService: InteractionService) {
this.subscription = this._interactionService.searchStr$.subscribe(
courses => {
this.searchStr = courses;
// code for returning results here..
}
);
}
ngOnInit() {
}
}
Your right that you want to use a service. Services are singletons so setting in one component and getting from another will return the passed value.
To get a service to work you need to create the service then add it to your app module. Then in the constructor for your component you add it so the dependency injection can add it to the component for you. The constructor looks like this.
constructor( private router: Router){}
Each component should have a reference in its constructor and the service singleton is shared.
interaction-service.service.ts:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class InteractionService {
sharedString = "";
}
app.component.ts:
import {InteractionService} from './interaction-service.service';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
providers: [CourseService, InteractionService]
})
export class AppComponent implements OnInit {
constructor(private _courseService: CourseService, private _interactionService: InteractionService, private router: Router) {
}
ngOnInit() {
this.__interactionService.sharedString = "Some value";
}
searchCourse(event) {
this._interactionService.sendString(event.target.value);
this.router.navigateByUrl('/');
}
}
I'm going to leave the rest of the code out. The above example should be enough. Simply make the injected interaction service available and set and get the values at will. The service values will persist until there is a browser context change. Last thing I want to mention is when routing call the router like this
this.router.navigate(["url", someParam]);
this will preserve context and not cause a browser context switch when moving between components.
Related
Im fairly new to Angular and am having trouble passing an array to a different (not parent/child related) component. What I want in my app is to click on a button to mark it as 'accepted', and to display that same array in a different view (the accepted arrays view) , in a different route. I've tried doing it with #Input and with shared Services and it just wont work. Can you please point me in the right direction? Thank you.
sharedService.ts //My array is called 'Turno'
import {Injectable} from '#angular/core';
import {Turno} from '../models/turno';
#Injectable()
export class SharedService{
public turno:Turno;
constructor(){
}
setArray(turno){
this.turno=turno;
}
getArray(){
return this.turno;
}
}
first component (I mark an accepted array with the accept() method):
#Component({
selector: 'app-turnos',
templateUrl: './turnos.component.html',
styleUrls: ['./turnos.component.css'],
providers:[TurnoService, SharedService]
})
export class TurnosComponent implements OnInit {
public turnos: Turno;
public status;
constructor(
private _turnoService: TurnoService,
private _sharedService: SharedService
) { }
ngOnInit(): void {
this._turnoService.getTurnos().subscribe(
response=>{
if(response.status== 'success'){
this.turnos= response.turnos;
this.status=response.status;
console.log(this.turnos);
}else{
this.status= 'error';
}
},error=>{
console.log('error');
}
);
}
accept(turno){
this._sharedService.setArray(turno);
console.log(turno);
}
second component (receives and lists accepted arrays)
import { Component, OnInit} from '#angular/core';
import {SharedService} from '../../services/shared.service';
import {Turno} from '../../models/turno';
#Component({
selector: 'app-turnoaceptado',
templateUrl: './turnoaceptado.component.html',
styleUrls: ['./turnoaceptado.component.css'],
providers:[SharedService]
})
export class AcceptedTurnoComponent implements OnInit {
public turnos: Turno;
constructor(
private _sharedService: SharedService
) { }
ngOnInit(): void {
this.turnos=this._sharedService.getArray();
console.log(this.turnos);
}
}
Shared service is the way to go in this case.
The service which emits the array every time a new item is added:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class ArrayServiceService {
private acceptedArray = [];
public acceptedArraySubject = BehaviorSubject<string[]>([]);
addToAccepted(item: string) {
this.acceptedArray.push(item);
this.acceptedArraySubject.next(this.acceptedArray);
}
constructor() {}
}
The service which calls the service to Add an item
(Ofcourse where you get your items from is up to you, and I'm sure you know how to pass an item with a click event so I'm not showing html):
import { Component, OnInit } from '#angular/core';
import { ArrayService } from '../array.service';
#Component({
selector: 'app-first-component',
templateUrl: './first-component.component.html',
styleUrls: ['./first-component.component.scss'],
})
export class FirstComponentComponent implements OnInit {
private allItemsArray = ['item1', 'item2', 'item3'];
markAsAccepted(item: string) {
this.arrayService.addToAccepted(item);
}
constructor(private arrayService: ArrayService) {}
ngOnInit(): void {}
}
And finally, the second component which listens to the changes inside the service and shows the accepted array:
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { ArrayService } from '../array.service';
import { share } from 'rxjs/operators';
#Component({
selector: 'app-second-component',
templateUrl: './second-component.component.html',
styleUrls: ['./second-component.component.scss'],
})
export class SecondComponentComponent implements OnInit {
private acceptedArray: Observable<string[]>;
constructor(private arrayService: ArrayService) {
this.arrayService.acceptedArraySubject.pipe(share());
}
ngOnInit(): void {}
}
And to consume the pipe in your html of your second component:
<ul>
<li *ngFor="let item of acceptedArray | async">{{item}}</li>
</ul>
I hope this got you on track. So you have a service which holds the array you want, a component which changes it and another which listens to it. But you can have a lot of components listening to it or changing it..
There are mainly three ways for data communication in Angular.
1. Using Input & Output for passing data to & from child component.
Also you can use ViewChild reference to access data (functions &
variables) of child component in parent component.
2. A shared common service having setter & getter methods.
You can use it to set data. It acts as a temporary state.
3. Using Browser storage in the form of session storage , local storage
or cookie. You can create one service which stores & takes in data
from browser storage & access it across components.
The whole matter come up when we had a shared component #Injectable. Every component in the app.module.ts can inject it into his own constructor.
export class SimpleComponent {
constructor (private sharedComponent : SharedComponent){}
}
I write a method in this class SimpleComponent to set a proprety at the shared component:
setPropretyAtSharedComponent {
this.sharedComponent.setProprety("proprety")
}
Presuming that the SharedComponent held:
#Injectable()
export class SharedComponent {
proprety :any
constructor() {}
}
setProprety (proprety){
this.proprety = proprety;
}
At this point every thing is OK.
So I was wondering if I had two components at the same page which sets the proprety value of the SharedComponent at meanwhile (concurent access) how I can handle that?
I guess that's taken care of by the DI framework once you define your Injectable service in providers in your module. Once your service is defined in the module, you can inject it in different components as a singleton.
You can refer to these docs:https://angular.io/docs/ts/latest/guide/dependency-injection.html
I'm trying to use dependancy injection in angular using an injector. I want to me able to instantiate types at runtime depending on what this component is sent.
#Injectable()
export class ServiceInjectionManager {
private _injector: ReflectiveInjector;
constructor() {
this._injector = ReflectiveInjector.resolveAndCreate([
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backendInstance, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
},
AppSettings,
HierarchyService
]);
}
public resolve<T extends HierarchyService>(type:any): T {
return this._injector.get(type);
}
}
I can't seem to find a way to pass a type. I have taken multiple approaches including:
public resolve<T extends HierarchyService>(T): T {
return this._injector.get(T);
}
It seems that generics in TypeScript are not the same as in .NET.
When ever my home page in angular 2(ionic 2) app is loaded I want call service/function. How to achieve this?
For the first time when the app is loaded(home page is loaded) I can write this in the constructor, but when user start using the app and push new pages into nav controller and pop them to come back to home page, the constructor wont get called again.
I am stuck here.
I would like to know what is the best way to achieve this feature?
I am new to angular2 and ionic2 framework (also don't have experiences in angular1 and ionic1), please help me out.
Many Many Thanks.
update
sample code of what I tried, but didn't worked.
import {Page, NavController, Platform, Storage, SqlStorage} from 'ionic-angular';
#Page({
templateUrl: 'build/pages/page1/page1.html'
})
export class Page1 {
static get parameters(){
return [[NavController],[Platform]];
}
ngOnInit() {
console.log("Showing the first page!");
}
constructor(nav, platform){
this.nav = nav;
this.platform = platform;
}
}
onPageWillEnter() worked for me.
import {Page, NavController, Platform, Storage, SqlStorage} from 'ionic-angular';
#Page({
templateUrl: 'build/pages/page1/page1.html'
})
export class Page1 {
static get parameters(){
return [[NavController],[Platform]];
}
onPageWillEnter() {
console.log("Showing the first page!");
}
constructor(nav, platform){
this.nav = nav;
this.platform = platform;
}
}
Ionic lifecycle hook
IONIC 2 RC1 Update
ionViewWillEnter() {
console.log("this function will be called every time you enter the view");
}
You can leverage the lifeCycle-hooks, specifically ngOnInit() or ngAfterViewInit().
Here is a simple tutorial.
For Example:
// Annotation section
#Component({
selector: 'street-map',
template: '<map-window></map-window><map-controls></map-controls>',
})
// Component controller
class StreetMap {
ngOnInit() { //here you can call the function wanted
// Properties are resolved and things like
// this.mapWindow and this.mapControls
// had a chance to resolve from the
// two child components <map-window> and <map-controls>
}
}
Update: it works for pure Angular2 applications, for IonicFramework specific solution see
#DeepakChandranP's answer.
I think I'm overlooking something very fundamental, and I've been banging my head for a couple of days now. I'm new to Angular and reactive programming.
So, I have one service and two components.
In my versionService.ts I have a helper method which allows components to set a currentVersion in a _dataStore.
private _dataStore: {
currentVersion: any
};
currVersionStream$: Subject<Version>;
constructor(public http: Http) {
this.currVersionStream$ = new BehaviorSubject<Version>(null);
this._dataStore = {
currentVersion: null
};
}
public setCurrentVersion(v: Version): void {
if (v != null) {
this._dataStore.currentVersion = v;
this.currVersionStream$.next(this._dataStore.currentVersion);
}
}
Then in my component select-version.component.ts, through a (click) event from the view/template, I use the setCurrentVersion helper method from the VersionService.ts to store the cliked on version.
In select.version.template.html:
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="#v of availableVersions" (click)="selectedVersion(v)">
<a href="#">
Version {{v.versionID}}
</a>
</li>
<li>No version</li>
</ul>
In version.component.ts: Here I call on the method in the service.
Update: I inject versionService in both of my components.
import {VersionService} from '../../services/services';
#Component({
selector: 'select-version',
templateUrl: 'app/component/selectVersion/select-version.template.html',
providers: [VersionService]
})
export class SelectVersionComponent implements OnInit {
constructor(public versionService: VersionService) {}
public selectedVersion(v: Version) {
console.log("SVC - set this version: " + v.versionID);
this.versionService.setCurrentVersion(v);
}
So, in my second component I want to retrieve the value set/stored in the service (_dataStore), and display it in its view/template (of my second component)
In my second-component.template.html:
<h2>{{selectedVersion}}</h2>
In my second-component.ts:
Update:
import {VersionService} from '../../services/services';
#Component({
selector: 'main-container',
templateUrl: 'app/component/mainContainer/main-container.template.html',
providers: [VersionService],
})
selectedVersion: any;
constructor(public versionService: VersionService) {
this.versionService.currVersionStream$.subscribe((v) => {
if(v != null) {
this.selectedVersion = "Version" + v.versionID
}
}, error => console.error (error));
}
Update:
In my main component, app.component.ts:
import {SidebarComponent} from './component/sidebar/sidebar.component';
import {MainContainerComponent} from './component/mainContainer/main-container.component';
#Component({
selector: 'init-app',
templateUrl: 'app/app-component.template.html',
directives: [SidebarComponent, MainContainerComponent],
})
export class AppComponent {
constructor() {}
}
In my boot.ts:
import {bootstrap} from 'angular2/platform/browser'
import {AppComponent} from './app.component'
import {HTTP_PROVIDERS} from 'angular2/http'
bootstrap(AppComponent, [HTTP_PROVIDERS])
.catch(err => console.error(err));
What am I missing to be able to view the currentVersion in my template of the second component, and will it update each time a new value is set?
Thanks for any reply!
To complement what Eric and pixelbits said, you don't share the same instance of the service. One instance is created per component since you use the providers attribute at this level.
If you want to know more about the hierarchical injection, you could have a look at this answer:
What's the best way to inject one service into another in angular 2 (Beta)?.
Hope it helps you,
Thierry