Angular: JSON html markup with angular component dynamic - php

EDIT 1:
Thank you for #Narm for the solution. I got it working!
In let myTemplate = '<div class="test" (tap)="test();">Test</div>';, I have a tap function.
When I click on it to invoke the function, it does not work and gives an error:
ERROR TypeError: _co.another_test is not a function
Here is what I have so far:
ngOnInit(){
...
let myTemplate = `<div class="test" (tap)="test();">Test</div>`;
...
}
test(){
console.log("Test");
}
Any thoughts?
Original Question Below
From php using REST, I am getting html markup with Angular components:
From php:
function send_html(){
$html = '<div class="test" *ngIf="data">This is an example</div>';
return $html;
};
Then in my angular project, I am trying to add this html dynamically using componentFactoryResolver: (I understand that it only accepts Angular component)
Here is my though process:
In main.ts (shown below): call the getDynamicREST() and get the $html from php.
When the data is fetched, then send this to my_component.ts to make this as an Angular component.
Once the html markup becomes a part of Angular component, then use createComponent to create the component.
Of course, it doesn't work...
This is what I have so far: Please feel free to tear it apart.
main.html
<div class="top">
<ng-template #main></ng-template>
</div>
main.ts
import { Component, ViewChild, ComponentFactoryResolver, ViewContainerRef } from '#angular/core';
import { my_component } from './my_component';
#Component({
selector: 'page-main_page',
templateUrl: 'main_page.html'
})
export class main_page {
#ViewChild('main', { read: ViewContainerRef }) entry: ViewContainerRef;
data: any;
constructor(public resolver: ComponentFactoryResolver,
public mine: my_component ){
};
ngOnInit(){
this.getDynamicREST().then((res)=>{
this.mine.data = res;
const factory = this.resolver.resolveComponentFactory(my_component);
this.entry.createComponent(factory);
})
};
}
my_component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my_component ',
template: '<div class="my_component">{{data}}</div>'
})
export class my_component {
data: any;
}
How would I achieve this so that I can fetch angular components dynamically and display them?
Any help will be much appreciated.
Thank you.

You're on the right path. I ran into this very same scenario just a few months ago, here is the solution to accomplish your goals.
Here I attached a StackBlitz if you want to see the code running live and have a chance to play with it. Dynamic components can be a challenge to understand at first.
Live Demo
app.component.ts
This component acts as the container to the dynamic component and is where it will be created.
import {
Component, ViewChild, OnInit, OnDestroy,
AfterViewInit, ComponentFactoryResolver,
Input, Compiler, ViewContainerRef, NgModule,
NgModuleRef, Injector
} from '#angular/core';
import { CommonModule } from '#angular/common';
import { BrowserModule } from '#angular/platform-browser';
#Component({
selector: 'app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
#ViewChild('vc', { read: ViewContainerRef }) _container: ViewContainerRef;
private cmpRef;
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private compiler: Compiler,
private _injector: Injector,
private _m: NgModuleRef<any>) { }
ngOnInit() { }
ngAfterViewInit() {
let myTemplate = `<h2> Generated on the fly</h2>
<p>You can dynamically create your component template!</p>`
#Component({
template: myTemplate
})
class dynamicComponent {
constructor(){}
}
#NgModule({
imports: [
BrowserModule
],
declarations: [dynamicComponent]
})class dynamicModule {};
const mod = this.compiler.compileModuleAndAllComponentsSync(dynamicModule);
const factory = mod.componentFactories.find((comp) =>
comp.componentType === dynamicComponent
);
this.cmpRef = this._container.createComponent(factory);
}
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
Then in the template for your app component
app.component.html
<ng-template #vc></ng-template>
Then in your app.module you need to import the compiler and declare it as a provider:
app.module.ts
import {Compiler, COMPILER_OPTIONS, CompilerFactory} from '#angular/core';
import {JitCompilerFactory} from '#angular/platform-browser-dynamic';
export function createCompiler(compilerFactory: CompilerFactory) {
return compilerFactory.createCompiler();
}
#NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
RouterModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
routing
],
providers: [
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
{provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
{provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory]}
],
bootstrap: [AppComponent]
})
export class AppModule {}
*** Edit for 'tap' question
What you want to do is bind to the (click) event, I am not aware of any (tap) events and there are none listed in the Events reference by MDN Events Reference MDN
In your dynamic component add the following:
let myTemplate = `<h2> Generated on the fly</h2>
<p>You can dynamically create your component template!</p>
<div class="test" (click)="test();">Test</div>`
#Component({
template: myTemplate
})
class dynamicComponent {
constructor(){}
public test(){
alert("I've been clicked!");
}
}
Note I updated the StackBlitz if you want to see it in action!

Related

how to use react-query with a Laravel api

I am following a example that uses react-query, the example works fine with the test data, but when it hits a backend that I am building is not working as expected, I mean, it retrieves the information, but don't cache the information, always hits the server, but using the example, that was not happening.
hooks/user.js
import { useQuery } from "react-query";
import axios from "axios";
export const useUsers = (activePage) => {
return useQuery(
// Add activePage as a dependency
["users", activePage],
async () => {
const { data } = await axios.get(
//works fine here
`https://reqres.in/api/users?page=${activePage}`
//Here not works properly
//`http://127.0.0.1:8000/api/users?page=${activePage}`
);
return data;
},
// This tells React-Query that this is Query is part of
// a paginated component
{ keepPreviousData: true }
);
};
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
const queryClient = new QueryClient(); // Global Store Instance
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<React.StrictMode>
<App />
</React.StrictMode>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>,
document.getElementById('root')
);
reportWebVitals();
App.js
import { useState } from "react";
import TableHeader from "./components/tableHeader/TableHeader";
import TableRow from "./components/tableRow/TableRow";
import Pagination from "./components/pagination/Pagination";
// Import the hook
import { useUsers } from "./hooks/users";
const App = () => {
const [activePage, setActivePage] = useState(1);
// React Query takes care of calling
// userUsers hook when App.js is rendered.
const usersList = useUsers(activePage);
return (
<>
<TableHeader />
{/* map through users list */}
{usersList.data &&
usersList.data.data.map((user) => <TableRow user={user} key={user.id} />)}
<Pagination
activePage={activePage}
setActivePage={setActivePage}
pages={2}
/>
</>
);
};
export default App;
In the example, when I hit the api again, I mean, I go to other page twice, in the network tab is says this: (disk cache)
Which is the expected behaviour, but when it is using my laravel api, then is not working properly, it is able to retrieve the information, but always hits the server, not the cache
In my laravel app I have this:
routes/api.php
Route::get('/users', [UsersController::class, 'index']);
UsersController.php
...
public function index()
{
return User::paginate(10);
}
...
The frontend is using this url:
http://localhost:3000/
and the backend is using this:
http://localhost:8000/
maybe is because of the port? but using the external api: https://reqres.in/api/users?page=1 it works without problem, it uses the cache as expected, do weird. I think I need to modify my api
This is the response of my local api:
{
"current_page":1,
"data":[
{
"id":1,
"first_name":"sergio",
"avatar_url":"https:\/\/url",
"age":30,
"created_at":"2022-09-11T22:29:52.000000Z",
"updated_at":"2022-09-11T22:29:52.000000Z"
},
{
"id":2,
"first_name":"jhon",
"avatar_url":"https:\/\/url",
"age":39,
"created_at":"2022-09-11T22:30:03.000000Z",
"updated_at":"2022-09-11T22:30:03.000000Z"
},
...
...
],
"first_page_url":"http:\/\/127.0.0.1:8000\/api\/users?page=1",
"from":1,
"last_page":9,
"last_page_url":"http:\/\/127.0.0.1:8000\/api\/users?page=9",
"next_page_url":"http:\/\/127.0.0.1:8000\/api\/users?page=2",
"path":"http:\/\/127.0.0.1:8000\/api\/users",
"per_page":3,
"prev_page_url":null,
"to":3,
"total":25
}
What can I do? thanks.
Replace return in your controller by this one
return response()->json(User::paginate(10));

Next js - Call a function on server side each time a route is changed

I am moving an old stack to next js and i need some help.
I have a php backend and I want to check a controller from this backend each time a route is changed, and I want to do it on server side.
For changing route, I use Link of next-routes, which have an effect only on the client side.
Is there any way I can call the controller on server-side without refreshing the app?
You can take advantage of Router's events and Custom App watching for a route change and perform the actions you need. Below you can find two sample implementations, one using class component and one using hooks. Both should do the trick.
// class component
import NextApp from 'next/app';
import Router from 'next/router';
class App extends NextApp {
componentDidMount() {
Router.events.on('routeChangeStart', () => {
// call to your backend
});
}
render() {
const { Component, pageProps } = this.props;
return <Component {...pageProps} />;
}
}
export default App;
// using hooks
import Router from 'next/router';
const App = ({ Component, pageProps }) => {
React.useEffect(() => {
const doMagic = () => {
// do your thing
}
Router.events.on('routeChangeStart', doMagic); // add listener
return () => {
Router.events.off('routeChangeStart', doMagic); // remove listener
}
}, []);
return <Component {...pageProps} />;
}
export default App;

Call service from outside app

I have 2 different apps, one is based on Angular2 and the other with PHP.
I need to call a service's function of the first one (Angular) from the second one (PHP). Is it possible even if it is weird and ugly?
I know how to call a PHP file inside an Angular app, but not the other way.
Here are my test files:
webService.component.ts
import { Component } from '#angular/core';
import {Observable} from "rxjs/Observable";
import {ActivatedRoute, Params} from "#angular/router";
import {WebService} from "./webService.service";
#Component({
selector: 'webService-element',
template: `{{result}}`
})
export class WebServiceComponent {
webService: WebService;
result: any;
constructor(private route: ActivatedRoute, webService: WebService) {
this.webService = webService;
}
ngOnInit() {
this.route.params.subscribe((params: Params) => {
let aId = params['aId'];
let bId = params['bId'];
this.getPif(aId, bId);
});
}
getPif(aId: any, bId: any): any {
this.result = this.webService.getPif();
return this.webService.getPif();
}
}
webService.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class WebService {
constructor(private http: Http) {}
//getPif(): Observable<any> {
getPif(): any {
console.log("rraaahhhhh");
return "pif";
}
}
webService.routing.ts
import { Routes, RouterModule } from "#angular/router";
import {WebServiceComponent} from "./webService.component";
const WEBSERVICE_ROUTES: Routes = [
{ path: '', component: WebServiceComponent },
{ path: 'getTest/:aId/:bId', component: WebServiceComponent },
];
export const webServiceRouting = RouterModule.forChild(WEBSERVICE_ROUTES);
webService.module.ts
import { NgModule } from "#angular/core";
import {WebService} from "./webService.service";
import {WebServiceComponent} from "./webService.component";
import {webServiceRouting} from "./webService.routing";
#NgModule({
imports: [
webServiceRouting
],
declarations: [
WebServiceComponent
],
providers: [
WebService
]
})
export class WebServiceModule { }
I tried to launch "http://localhost:3000/webService/getTest/1/2"
Thanks a lot for your help!

ERROR Error: Uncaught (in promise): TypeError: users is undefined

I have an error in this Component and that error is not caught by command prompt.
Dashboard.component.ts
import { Component, OnInit } from '#angular/core';
import { User } from './user.component';
import { UserService } from './user.service';
#Component({
selector : 'dashboard',
templateUrl : './views/dashboard-component.html',
styleUrls: ['./views/css/dashboard-component.css'],
providers: [UserService]
})
export class DashboardComponent {
users: User[] = [];
constructor(private userservice: UserService){}
ngOnInit() : void{
this.userservice.getusers().then(users => this.users = users.slice(1,5) );
}
}
I can't understand what the problem is because i have defined "users" in the class.
The method is called from this service.
user.service.ts
import { Injectable } from '#angular/core';
import { User } from './user.component';
import { USERS } from './mock-users';
import { Headers, Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class UserService {
private usersUrl = "http://localhost/user/src/app/userlist.php";
constructor(private http: Http) { }
getusers() : Promise<User[]>{
return this.http.get(this.usersUrl)
.toPromise()
.then(response => response.json().data as User[])
.catch(this.handleError);
}
getuser(id: number): Promise<User> {
const url = '${this.usersUrl}/${id}';
return this.http.get(url)
.toPromise()
.then(response => response.json().data as User)
.catch(this.handleError);
}
private handleError(error : any): Promise<any> {
console.error('an error occured', error);
return Promise.reject(error.message || error);
}
}
The data I am getting is :
[{"id":"1","fname":"Vishwas","lname":"Jadav","email":"vjadav#live.com","dpic":"2017-10-7--09-12-19.jpeg","phone":"7621823474","passw":"illj123","type":"2"}]
You don't have a data property in your response, as we can see you are simply getting an array, so you should return that. So in your service method getusers, instead of...
.then(response => response.json().data as User[])
do:
.then(response => response.json() as User[])
Sidenote, you might have the same problem with your getuser (?) just for future reference if you run into same problem there :)
Try assigning to array and then remove
ngOnInit() : void{
this.userservice.getusers().then(users => this.users = users;
this.users =this.users.slice(1,5);
});

Angular 2 stable: templateUrl variable

I am fairly new to AngularJS (using 2 stable). I have an existing PHP/Codeigniter3 app and my job is to make an SPA. I am running into a problem where I can't access router params at all to add them in a templateUrl.
For example:
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
#Component({
selector: 'apps_container',
// template: `You just accessed app: {{app_name}}` // This binding works obviously.
templateUrl: (() => {
// return 'app/' + this.route.params['app_name'] // Will never work no matter what because I have no access to route
return 'app/:app_name'; // Treated as a string.
})()
})
export class AppViewComponent {
app_name: any;
constructor(
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.route.params.forEach((params: Params) => {
this.app_name = params['app_name'];
});
}
}
That being said, you could to do that via dynamic component loading.
In angular2 final it might look like this:
#Component({
selector: 'app-container',
template: '<template #vcRef></template>'
})
export class AppContainerComponent {
#ViewChild('vcRef', { read: ViewContainerRef }) vcRef: ViewContainerRef;
constructor(
private route: ActivatedRoute,
private cmpFactoryResolver: ComponentFactoryResolver,
private compiler: Compiler
) { }
ngOnInit() {
this.route.params.forEach((params: Params) => {
this.loadDynamicComponent(params['app_name']);
});
}
loadDynamicComponent(appName) {
this.vcRef.clear();
#Component({
selector: 'dynamic-comp',
templateUrl: `src/templates/${appName}.html`
})
class DynamicComponent { };
#NgModule({
imports: [CommonModule],
declarations: [DynamicComponent]
})
class DynamicModule { }
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
.then(factory => {
const compFactory = factory.componentFactories
.find(x => x.componentType === DynamicComponent);
const cmpRef = this.vcRef.createComponent(compFactory);
cmpRef.instance.prop = 'test';
cmpRef.instance.outputChange.subscribe(()=>...);;
});
}
}
Plunker Example
I guess there are other ways to do that like ngSwitch or ngTemplateOutlet
Severals interresting answers related to your question can be found here : Dynamic template in templatURL in angular2
templateUrl callback will be executed before creating component, in other words before route injection, so there are two ways to do what you want:
Use window.location to get current url and manually parse parameters.
Add router listener to detect RoutesRecognized event and use its state:RouterStateSnapshot property to find parameters and save it to static variable to use in template callback.
yurzui - does the dynamically added component gets all the binding in the parent component - AppContainerComponent in your example?

Categories