Angular Dart component events - events

I am trying to pass custom events from a component to its parent component/controller
confirm.html
<div class="comfirm-component">
<content></content>
Yes
No
</div>
confirm.dart
#Component(
selector: "confirm-component",
templateUrl: 'confirm.html',
useShadowDom: false,
publishAs: "ctrl"
)
class ConfirmComponent {
void yes(){
print('yes');
// Fire confirm-yes event
}
void no(){
print('no');
// Fire confirm-no event
}
}
is there something like this?:
<confirm-component on-confirm-yes="doSomething()" on-confirm-no="doSomethingElse()">
Do you want to delete
</confirm-component>
I could use a normal StreamController but then i'd had to connect my components with code.
confirmComponent.onConfirmYes.listen()
confirmComponent.onConfirmNo.listen()
I also found this:
How to communicate between Angular DART controllers
And this:
angulardart components - dispatch custom event
In both treads scope.emit is mentioned. But i didn't find a way to use it with a component instead of a controller. Is there a full example vor angular.dart v0.14.0?
Is scope.emit the thing i'm searching for?

This should be the same, just add a scope argument to the constructor so the component gets the scope injected.
There was a related change in Angular 0.14.0 https://github.com/angular/angular.dart/commit/181f01448555c475869505491159045904e5dc89
I haven't yet tried this.
From the description you need to implement ScopeAware
#Component(...)
class MyComponent implements ScopeAware {
Watch watch;
MyComponent(Dependency myDep) {
// It is an error to add a Scope / RootScope argument to the ctor and will result in a DI
// circular dependency error - the scope is never accessible in the class constructor
}
void set scope(Scope scope) {
// with this scope you should be able to use emit
// This setter gets called to initialize the scope
watch = scope.rootScope.watch("expression", (v, p) => ...);
}
}

Based on the answer from Günter i built this working example:
#Component(
selector: "confirm-component",
templateUrl: 'component/confirm.html',
useShadowDom: false,
publishAs: "ctrl"
)
class ConfirmComponent implements ScopeAware {
Scope scope;
void yes(){
scope.emit('confirm', 'yes');
}
void no(){
scope.emit('confirm', 'no');
}
}
#Component(
selector: "my-component",
templateUrl: 'component/my.html',
useShadowDom: false,
publishAs: "ctrl"
)
class MyComponent implements ScopeAware{
void set scope(Scope scope) {
Stream mystream = scope.on('confirm');
mystream.listen((event){
print('confirmed: ' + event.data);
});
}
}

Related

How to build a Model Layer in Vue3 just like other MVC language?

my name is DP, I have 2 years Vue2 experience, but I am new to Vue3. I am learning Vue3 recently, as I found the "setup(Composition API)" just like the "Controller(in MVC)" that I did in other language, so I am trying to build my test Vue3 project in MVC way, but I go some problem can anyone help? thx!
MVC Plan
M - use class
V - use <template> ... </template>
C - use setup
My Problem
working: using loadTopic_inSetup().then() in setup is working, because topicList_inSetup is defined in setup() too.
not working: using loadTopic_inModel() in setup is not working, I guess some kind data keep problem, because in console I can see the data already got from API
as u can see, I am not expert for js/ts, I am a backend developer, so if you know how to do it, plz help thx very much.
BTW, VUE is greet, I love it.
My Code
//APIBased.ts
import { ajax } from "#/lib/eeAxios"
export class APIBased {
//load data with given url and params
loadData(apiPath: string, params?: object): Promise<any> {
apiPath = '/v1/'+apiPath
return ajax.get(apiPath, params)
}
}
//Topic.ts
import { APIBased } from "./APIBased";
import { ref } from 'vue'
export class Topic extends APIBased {
//try keep data in model
topicList: any = ref([]);
constructor() {
super()
}
//direct return ajax.get, let setup do the then+catch
loadTopic_inSetup() {
return super.loadData('topics', { t_type_id: 1 })
}
//run ajax get set return data to this.topicList, keep data in model
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList = re.data
})
}
}
//EETest.vue
<template>
<EELayoutMainLayout>
<template v-slot:mainContent>
<h1>{{ "Hello Vue3 !!" }}</h1>
<hr/>
{{to.topicList}} //not working... just empty array
<hr/>
{{topicList_inSetup}} //working... topic list return from API show here.
</template>
</EELayoutMainLayout>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, ref } from 'vue'
import EELayoutMainLayout from '#/components/eeLayout/EELayoutMainLayout.vue'
import { Topic } from "#/models/Topic";
export default defineComponent({
name: 'EETest',
props: {
},
setup() {
let topicList_inSetup = ref([])
const to = new Topic()
//try keep data in setup, it's working
to.loadTopic_inSetup().then((re) => {
topicList_inSetup.value = re.data
console.log(re.data)
})
//try keep data in model, the function is run, api return get, but data not show, even add ref in model
to.loadTopic_inModel()
return {
topicList,
to,
}
},
components: {
EELayoutMainLayout,
},
})
</script>
A few digressions before solving the problem. Maybe you are a java developer. I personally think it is inappropriate to write the front end with Java ideas. The design of vue3's setup is more inclined to combined functional programming
To fully understand why you need some pre knowledge, Proxy and the get and set method of Object
They correspond to the two core apis in vue, reactive and ref,
The former can only be applied to objects( because proxy can only proxy objects),The latter can be applied to any type(primary for basic javascript types, get and set can apply for any type)
You can modify the code to meet your expectations
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList.value = re.data
})
}
You cannot modify a ref object directly, a test case to explain what is reactive
when ref function is called, a will be like be wrapped in a class has value properties, and has get and set method
the effect function will call the arrow function, and in this time, the get method of a will be called and it will track as a dependence of the effect function, when a changed, the set method of a will be called, and it will trigger the arrow function,
so when you direct modify the a, the setter method will never trigger, the view will not update
const a = ref(1)
let dummy
let calls = 0
effect(() => {
calls++
dummy = a.value
})
expect(calls).toBe(1)
expect(dummy).toBe(1)
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)
// same value should not trigger
a.value = 2
expect(calls).toBe(2)

Guideline for memoized selectors in state base class

I have a question regarding NGXS Store and the usage of memoized #Selector()s in a state class hierarchy.
What would be the recommended approach to the issue described below?
The NGXS Store documentation does not provide a guideline/recommendation in this regard.
Given this sample state setup,
export interface BaseModel { data: string[]; }
// base class not a state!
export class BaseState {
#Selector()
static dataSelect(state: BaseModel) { return state.data; }
// ...
}
export interface DerivedModel extends BaseModel {}
#State<DerivedModel>({ name: 'derived'; })
export class DerivedState extends BaseState {
// ...
}
export interface UsingModel { thing: number; }
#State<UsingModel>({ name: 'using'; })
export class UsingState implements NgxsOnInit {
constructor(private _store: Store) {}
ngxsOnInit(ctx: StateContext<UsingModel>) {
// have this state update when derived state changes
this._store.select(DerivedState.dataSelect).subscribe(data => console.log(data));
}
// ...
}
When letting this piece of code run it will print out undefined because the state argument
of the dataSelect(...) method will not be set.
I tracked the cause to BaseState not being a NGXS State and therefore missing an internal NGXS_META
property which in turn causes the undefined argument.
Adding BaseState to the selector (such as #Selector([BaseState])) to force the state to still be
included also does not have the desired effect, because now NGXS cannot navigate to a matching state slice.
I found two ways to make this work as desired:
1. duplicate the #Selector(...) method in every derived state implementation. This though defeats the reasons why the state class hierarchy was originally designed.
2. use something like #DerivedSelector(...) that works according to the standard decorator but dynamically creates selectors on use for each of the encountered derived state classes.
Thank you.
As far as I know this can not be achived using the #Selector annotation, but with createSelector().
export class BaseState {
static dataSelect() {
return createSelector(
[this],
(state: BaseModel) => {
return state.data;
}
);
}
//...
}
If you change your base state like this, your code will work. For details refer to the NGXS docs

Override registration decorator for Aurelia container in Jasmine test

We have a component that is registered as a transient service in the Aurelia DI container using a decorator, like this:
#transient()
export class EntityGraphObserver {
...
}
So that whenever it is injected, it is a new instance.
However when used in Jasmine specs, we want to override this default registration by passing a spy which is registered as an instance, so that we can mock the component, but it never works - the instance received by the test is always a new instance of the real component, not the spy, for example:
import { Container, Aurelia } from "aurelia-framework";
import { bootstrap } from "aurelia-bootstrapper";
import { StageComponent, ComponentTester } from "aurelia-testing";
import { Bootstrapper } from "...";
import { EntityGraphObserver } from "...";
describe("shareholder-step", () => {
let component: ComponentTester;
let observer: any;
let container: Container;
beforeEach(() => {
observer = jasmine.createSpyObj("EntityGraphObserver", ["attach"]);
component = StageComponent
.withResources("modules/business-details/shareholder-step")
.inView("<shareholder-step></shareholder-step>");
component.bootstrap((aurelia: Aurelia) => {
Bootstrapper.configure(aurelia);
container = aurelia.container;
container.registerInstance(EntityGraphObserver, observer);
});
});
afterEach(() => {
component.dispose();
});
it("initializes with shareholder data", async done => {
// arrange / act
await component.create(bootstrap);
const vm = component.viewModel as ShareholderStep;
await vm.activate(...); // some activation data
// assert
expect(vm.shareholders.length).toBe(1);
expect(observer.attach).toHaveBeenCalledTimes(1); // NEVER WORKS
done();
});
};
It looks like the transient decorator always overrides the registration we specify when bootstrapping the component for testing, which stops us from being able to isolate components with decorators.
The child container injected into the component is not available during the bootstrap phase, but I suspect that's where the transient registration is occurring, however I would expect it to only use that as the default registration where there is no existing registration in the container hierarchy, so maybe it's a bug in the Aurelia framework.
Is there a way to control the container setup so that registrations from decorators can be ignored or overridden, or is this a bug?

Angular 4: Using Custom RouteReuseStrategy causes Cannot read property 'prototype' of undefined

The custom Route reuse strategy is as follows,
import { RouteReuseStrategy,ActivatedRouteSnapshot,DetachedRouteHandler} from '#angular/router';
export class CustomRouteReuseStrategy extends RouteReuseStrategy {
shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; }
store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {}
shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; }
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { return null ; }
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
console.log("inside shouldReuseRoute...");
return future.routeConfig === curr.routeConfig ;
}
}
I get this below error
Error: TypeError: Cannot read property 'prototype' of undefined
at __extends (http://localhost:8000/app/UMSClient/com/ipc/ums/modules/shared/CustomRouteReuse.service.js:7:73)
I read through stack overflow posts and couldn't locate a solution. Once this works I want to ensure one of my child component gets reloaded instead of Re-use and then attach component going forward in the project.
I provided in AppModule
{
provide: RouteReuseStrategy,
useClass: CustomRouteReuseStrategy
},
I change extends to implements and the error goes away but the custom route re-use does not work.
i am using angular 4.3.1
The issue was my workspace structure. Angular 2 version was picked up by build wrongly when angular 4 was intended. When I corrected this, it started working fine.

How to publish event not in component angular 2?

How to publish event not in component of angular 2?
I try:
let events = new Events(//Need agruments here);
events.publish('myevent');
But that need to pass in class arguments. How to do this?
I want global event catch in component. And event to publish anythere.
Explain:
I have component where I want to catch event.
#Component({
selector: 'category-blocks-listproductspage',
templateUrl: '/mobilesiteapp/template/?path=pages/category/blocks/listproductspage'
})
export class CatalogBlocksListProductsPage extends AbstractBlock{
constructor(
public events: Events
){
this.events.subscribe('category-blocks-filterspage.apply', (eventsData) => {
this.applyFilters(eventsData[0]);
});
}
}
And I have class where I want to publish Event.
export class ProductItem{
updateFavorite(response: Object, data: any)
{
this.product.toggleInFavorite(response, data);
let events = new Events(//Need agruments here);
events.publish('favorite-block-favoritepage.refresh', response['response']);
}
}
How to do this? Sorry for bad English.

Resources