jdanyow / aurelia-breeze Goto Github PK
View Code? Open in Web Editor NEWEverything you need to use Breeze with Aurelia
License: MIT License
Everything you need to use Breeze with Aurelia
License: MIT License
Symptom: other subsequent breeze entity PropertyChanged subscriptions get propertyChangedEventArgs with a random propertyName for an entity-wide change when it should be blank
Cause: aurelia-breeze subscribes to the Breeze propertyChanged event. If it is an entity event, during its processing the handleEvent method is setting the propertyName. When it exits it leaves a random propertyName in the propertyChangedEventArgs "change" object parameter.
e.g. mergeEntity after manager.saveChanges() raises PropertyChanged event on an entity with entityState.isUnchanged. Subscriptions after aurelia-breeze see these events with a random propertyName.
Use Case: I need to know of any propertyChange, and as I sometimes start with an added entity I cant use entityState changes. As I want any property change I dont want to subscribe to every properties observer.
I want to build an app with the skeleton-typescript-aspnetcore and use breeze. The skeleton works fine, but when I followed the aurelia-breeze guide to set breeze up, I used
import breeze from 'breeze';
in a ts file, but got an error: cannot found module 'breeze', and then I googled for a while and found the d.ts file in dist folder(https://github.com/jdanyow/aurelia-breeze/tree/master/dist), but after adding the file, a new error- cannot find module 'breeze-client' - appeared, which came from the d.ts file. I checked the package.json, it has the both aurelia-breeze and breeze-client in dependencies section, but the typings.json not.
also, I found the content of aurelia-breeze.d.ts file is not similar as other index.d.ts file in typings folder(like: typings/modules/aurelia-binding/index.d.ts), they all have a typings.json file.
can you give me a hint how to make it work with the skeleton, thanks in advance.
Hi, here is the stacktrace I get when I add aurelia-breeze to my app :
DEBUG [aurelia] Loading plugin aurelia-breeze.
inner error: TypeError: _aureliaPal.DOM.createMutationObserver is not a function
at makeRequestFlushFromMutationObserver (http://localhost:9000/jspm_packages/npm/[email protected]/aurelia-task-queue.js:13:36)
at new TaskQueue (http://localhost:9000/jspm_packages/npm/[email protected]/aurelia-task-queue.js:59:41)
Id there a precise version of aurelia-pal, having a createMutationObserver ?
Aurelia is the configured this way :
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin("aurelia-breeze")
.feature("resources");
I have added the following dependencies :
"aurelia-binding": "npm:aurelia-binding@^1.0.0-beta.1.0.1",
"aurelia-bootstrapper": "github:aurelia/bootstrapper@^0.18.0",
"aurelia-breeze": "npm:aurelia-breeze@^0.13.1",
"aurelia-dependency-injection": "npm:aurelia-dependency-injection@^1.0.0-beta.1",
"aurelia-event-aggregator": "npm:aurelia-event-aggregator@^1.0.0-beta.1",
"aurelia-framework": "github:aurelia/framework@^0.17.0",
"aurelia-http-client": "npm:aurelia-http-client@^1.0.0-beta.1",
"aurelia-logging-console": "github:aurelia/logging-console@^0.8.0",
"aurelia-router": "github:aurelia/router@^0.13.0",
"bootstrap": "github:twbs/bootstrap@^3.3.5",
"breeze": "npm:breeze-client@^1.5.5",
"font-awesome": "npm:font-awesome@^4.4.0",
"jonmiles/bootstrap-treeview": "github:jonmiles/bootstrap-treeview@^1.2.0",
"jquery": "github:components/jquery@^2.1.4"
Thank you
It is conventional for the file that defines a new adapter to "self-register" that adapter with Breeze ... a step that must be taken eventually anyway.
Here is the line I was expecting to see, perhaps near the bottom of the file:
breeze.config.registerAdapter("ajax", AjaxAdapter);
And having done that, there would be no need to register it as you do later in index.js and no reason to export AjaxAdapter
at all.
Any reason you went this other way?
Perhaps you were thinking testing? Well the standard way any consumer of breeze would access this adapter is by requesting it from Breeze
var ajaxAdapter = breeze.config.getAdapterInstance("ajax", 'aurelia'); // get the current instance
var AjaxAdapter = breeze.config.getAdapter("ajax", 'aurelia'); // get the constructor function (rare)
That's how I'd do it in my tests ... in part because in tests I want to follow as closely as I can the way I expect developers to work with this component.
Thoughts?
Why check both entityAspect
and entityType
? Why not assume that if an object has an entityType
, it must be a breeze entity. Who else is going to define that property for an object in an application that uses Breeze?
Could replace this:
// breeze entities have entityAspect and entityType properties.
if (!object.entityAspect || !(entityType = object.entityType))
return false;
// get or create the lookup used to avoid reflecting on the breeze entityType multiple times.
canObserve = entityType.__canObserve__ || createCanObserveLookup(entityType);
// return canObserve- coerce undefined values to false.
return !!canObserve[propertyName];
With this:
var type = object.entityType
return type ? !!(type.__canObserve__ || createCanObserveLookup(type))[propertyName] : false;
Hi there,
I'm wondering how do you want to integrate this plugin with aurelia-validation
(as it's seems to be the next aurelia big step).
Do you have any plan to merge both validation systems (breeze & aurelia-validation) ? Because I've started a JSData v3 plugin and I don't want to duplicate the validation system in model & controller layer.
If I call an API (which throws an exception) using breeze's executeQuery method, the errorCallback function is never called.
var eq = new breeze.EntityQuery('TestError'); instance.manager.executeQuery( eq, function(data) { console.log('success') }, function(err) { debugger; });
this is api method
[HttpGet] public void TestError() { throw new Exception("test exception"); }
nor does it work with what is described here (see executeQuery method)
I suppose this is because aurelia-breeze replaces Q with ES6 Promises.
Is it possible to have the errorCallback called anyway? Or am I just doing something wrong?
I am not sure if this has to do with this issue:
#44
If so, please feel free to remove this issue.
This line concerns me:
requestInfo.config.headers = clone(this.defaultHeaders || {});
That will *_replace_ rather than extend any headers that happen to be specified in the config
from the caller. Now I admit that there will be no such headers when the caller is the breeze EntityManager
... at least as of today. But that could change and maybe someone else wants to use this adapter for some reason.
That's why we play it safe in other ajax adapter implementations ... like this:
var core = breeze.core;
var headers = core.extend({}, this.defaultSettings.headers); // copy default headers 1st
requestInfo.config.headers = core.extend(headers, requestInfo.config.headers);
While investigating splice-vs-null setting (see this issue), I tripped over a few adjustments you might want to make.
All of them can be found in this gist.
Instead of repeatedly executing this.callbacks[propertyName]
, I would capture that array. Your splicing version would look like this:
function subscribe(propertyName, callback){
var callbacks = this.callbacks[propertyName];
if (!callbacks) {
this.callbacks[propertyName] = [callback];
} else if (callbacks.indexOf(callback) === -1) {
callbacks.push(callback);
} else {
return; // already registered; ignore.
}
if (this.callbackCount === 0) {
this.subscription = this.obj.entityAspect.propertyChanged.subscribe(this.handleChanges.bind(this));
}
this.callbackCount += 1;
return this.unsubscribe.bind(this, propertyName, callback);
}
I think you are correct to ignore an attempt to re-register the same callback function rather than throw. OTOH, I prefer moving the
elses
around because I find it easier to read a positive (=== -1
) rather than a negative (!=== -1
). Don't you?
Instead of calculating the index of the callback twice, I would call it once.
I guard against a failure to find the callback for a particular property, whether that's because the callback isn't registered or because there aren't any subscriptions for that property. Both cases I treat as "no-ops".
I also think it is safer (from a memory leak perspective) to zap the this.callbacks
when there are no more subscriptions.
My revised version looks like this:
function unsubscribe(propertyName, callback) {
var callbacks = this.callbacks[propertyName];
if (!callbacks) { return; } // no subscriptions for this property; ignore.
var index = callbacks.indexOf(callback);
if (index === -1) { return; } // this callback was not found; ignore.
callbacks.splice(index, 1);
this.callbackCount -= 1;
if (this.callbackCount === 0) {
this.obj.entityAspect.propertyChanged.unsubscribe(this.subscription);
this.callbacks={};
}
}
Per Crockford, I'm using
this.callbackCount += 1
andthis.callbackCount -= 1
instead ofthis.callbackCount++
andthis.callbackCount--
. That change is really picky.
Installation using jspm install npm:aurelia-breeze
fails with the following error:
Looking up npm:aurelia-breeze
Updating registry cache...
Downloading npm:[email protected]
warn Error on processPackageConfig
Package.json dependency aurelia-binding set to npm:aurelia-binding@^1.0.0-beta.1, which is not a valid dependency format for npm.
It's advisable to publish jspm-style packages to GitHub or another registry so conventions are clear.
err Error: Error processing package config for npm:aurelia-breeze.
at /Users/BBosman/Development/ExternalApps/Aurelia/node_modules/systemjs-builder/lib/builder.js:27:9
at Object.lib$rsvp$events$$default.trigger (/Users/BBosman/Development/ExternalApps/Aurelia/node_modules/rsvp/dist/rsvp.js:245:13)
at null._onTimeout (/Users/BBosman/Development/ExternalApps/Aurelia/node_modules/rsvp/dist/rsvp.js:779:47)
at Timer.listOnTimeout (timers.js:92:15)
err Error processing package config for npm:aurelia-breeze.
warn Installation changes not saved.
I put together a skeleton project with an MVC/EF backend.
The database has just one table with two fields, FirstName and LastName. The last name has the Required
attribute.
I then had the EntityManager fetch the metadata which included a nullable:false
for LastName which then should be validated in the UI.
It all wired up OK and I can save the contact to the database.
However, when I add "breeze-validation.bind='currentContact'", it throws the following error:
TypeError: Cannot read property 'subscribe' of undefined
at BreezeValidation.subscribe (http://localhost:56321/jspm_packages/github/jdanyow/[email protected]/breeze-validation.js:104:70)
Stepping through the code in the debugger, it shows that the subscribe function is expecting and entityManager, but it is getting the entity instead.
This is the line of code that throws an error:
this.errorsSubscription = entityManager.validationErrorsChanged.subscribe
This is the viewmodel:
import breeze from "breeze";
export class Welcome {
currentContact;
manager;
constructor() {
this.manager = new breeze.EntityManager('breeze/data');
this.manager.fetchMetadata().then(() => {
this.currentContact = this.manager.createEntity("Contact");
this.currentContact.FirstName = "Greg";
this.currentContact.LastName = "Gum";
});
}
submit() {
this.manager.saveChanges();
}
}
<div class="form-horizontal" breeze-validation.bind="newItem" show.bind="!readOnly"> all is ok!</divb>
<div class="form-horizontal" breeze-validation.bind="newItem" if.bind="!readOnly"> the form start validated!</div>
The current version of aurelia-breeze 2.2.0 does not work correctly with this example (https://plnkr.co/edit/XMmSqT?p=preview), it validates but does not render the error. Is there any fix that I can do? Any other example with a more current version? Do you have any other way to show your mistakes? thank you
Obs: the last version that I managed to make it work was 1.0.0
The current unsubscribe method returned from a subscription is kind of heavy and holds several captures.
return function() {
callbacks.splice(callbacks.indexOf(callback), 1);
if (callbacks.length > 0)
return;
this.obj.entityAspect.propertyChanged.unsubscribe(this.subscription);
this.observing = false;
}.bind(this);
This might matter because you'll create so many of them.
I'd rather see it be ...
return function() {this.unsubscribe(callback, propertyName);}.bind(this);
and delegate the details to unsubscribe
.
Better still if you write:
callback.propertyName = propertyName;
return function() {this.unsubscribe(callback);}.bind(this);
I am consistently getting an error while trying to resolve a promise from aurelia-breeze
in a test method for an aurelia(typescript + breeze)
application.
Testing framework used is karma+jasmine
.
Version of the aurelia-breeze
package is 1.0.0
Aurelia breeze makes use of es promise resolver instead of Q.js
, but the test method is searching for Q.js
to resolve the promise returned from breeze.
I have tried to add the Q
library to the window object from the spec file. But the breeze.debug.js
is trying to access the Q j
s from the window object before it is set in the spec file.
this didn't resolve the issue.
Please help me to solve this issue.
I am using this Aurelia Breeze plugin.
My use case is that I have to pass Uer Name/Password to the REST API that I am consuming for Authentication.
Could you please show a direction on how I can achieve the authentication. Googled a lot, but left clueless
Not sure if this is an issue here, or with aurelia-bundler...
I've tried creating a new project using the new release version of the cli tool (Typescript, default settings for everything else) and then adding aurelia-breeze (adding that, aurelia-fetch-client and breeze-client to the dependencies in aurelia_project/aurelia.json). When I do au build
it looks like it's deriving the wrong filename for breeze - which should be breeze.min.js
Tracing aurelia-binding...
Tracing aurelia-bootstrapper...
Tracing aurelia-breeze...
error D:\dev\AuTest\BreezeTest\node_modules\breeze-client\breeze.js
Tracing aurelia-event-aggregator...
Tracing aurelia-dependency-injection...
Tracing aurelia-framework...
Tracing aurelia-history...
Hi,
I'm trying to setup this plugin with the webpack skeleton app, it does well when webpack creates the bundles, but then when I run the app throws an error:
Unhandled rejection Error: Cannot find module './aurelia-breeze/validation/breeze-validation'
I think the problem is when it executes this line: frameworkConfig.globalResources('./validation/breeze-validation');
Maybe I should register breeze-validation as a plugin in my aurelia config?
I used the navigation skeleton for asp dot net to build an app, and it runs well, then I want integrate breeze. I followed the guide but get an error : cannot find the module 'aurelia-breeze', according to other issues, I am sure it is caused by the missed ts file, but I cannot find it, is there a way to make work? thanks.
The adapter definition has this static method (setHttpClientFactor
) for defining the method that will determine the runtime instance of the httpClient
used by the adapter.
I'm personally leery of statics, especially when there is no default implementation. I don't see the need in this case. IMO, it should be an adapter instance method such as;
setHttpClient(client) {
this.client = client;
}
Configuration now looks like this:
var adapter = breeze.config.initializeAdapterInstance('ajax', 'aurelia', true);
adapter.setHttpClient(aurelia.container.get(HttpClient));
That's the way it's done with the Angular ajax adapter. It seems clearer to me and slightly easier to test. And it's one fewer method to export.
Thoughts?
aurelia-fetch-client (replacing aurelia-http-client) is now the Aurelia project's official ajax library (e.g. in skeleton projects) ref: aurelia/fetch-client#7
Can we please have an ajax adapter that uses aurelia-fetch-client
Who needs it? It's really an object whose definition is internal to breeze itself. It's really a "private" contract between an ajax adapter and the Breeze EntityManager
.
You don't use it elsewhere in the repo and know consumer of Breeze should know it exists.
I recommend minimizing exports as much as possible.
If you combine this with my other recommendations, you'll have a module that exports an empty object. Nothing wrong with that IMO. It's a module with a side-effect; it defines an adapter and registers it with Breeze.
Hi,
I have used the newest aurelia with same webpack config as latest typescript skeleton
get error
cannot read property extend of undefined
in file node_modules/aurelia-breeze/dist/commonjs/aurelia-breeze
var extend = _breeze2.default.core.extend;
using "aurelia-breeze": "^1.2.0",
Greetings
Felix
This file https://github.com/jdanyow/aurelia-breeze/blob/master/dist/aurelia-breeze.js, has a method called configure
.
The only way I have found to get webpack and aurelia-breeze to work together involves modifying this file to return the instance of breeze that is configured in there. I then can use that instance in my application.
Would you be willing to make this modification. It can be done by adding:
return breeze;
as the last line before the method closes? (Right after adapter.setHttpClientFactory(() => frameworkConfig.container.get(HttpClient));
.)
I am trying to set a custom header (an Auth Header) but I don't see the header being passed to the server:
var ajaxAdapter: any = breeze.config.getAdapterInstance('ajax');
ajaxAdapter.defaultSettings = { headers: { "X-Test": "123" } };
Have also tried:
var ajaxAdapter: any = breeze.config.getAdapterInstance('ajax', 'aurelia');
ajaxAdapter.defaultSettings = { headers: { "X-Test": "123" } };
Any idea what is going on?
The current aurelia-breeze.d.ts file has this as the first line:
import breeze from 'breeze-client';
This gives me the error:
ERROR in C:\my\path\my-project\node_modules\aurelia-breeze\dist\aurelia-breeze.d.ts
(1,8): error TS1192: Module '"C:/my/path/my-project/node_modules/breeze-client/index"' has no default export.
If I change the first line to this:
import * as breeze from 'breeze-client';
Then it works fine.
Please consider changing this in the source.
The "ajax adapter didn't report error properly" v2.2.0 commit no longer sets an error's data.
Use case: 500 errors (e.g. invalid save) no longer return error.message.
The commit no longer processes response.json in promise reject.
While the standard fetch api only returns network errors via the promise reject, aurelia-fetch adds a response rejectOnError interceptor. The interceptor returns any response with ok = false via the promise reject.
I am not sure how to correct as I dont know what the "ajax adapter didn't report error properly" problem was that this commit was trying to fix. I cant find the issue.
The readme.md file only shows how to setup using JSPM.
It would be really nice to see how this should be setup for Webpack. (I get a breeze is not defined
error.)
I'm trying to execute a query with parameters, but the generated url is wrong:
http://localhost:16746/api/v1/CountryItem?$orderby=Id&$top=10&$inlinecount=allpages?q=Sa
let manager = new breeze.EntityManager('/api/v1');
var query = breeze.EntityQuery
.from('CountryItem')
.withParameters({
q: 'Sa'
})
.orderBy('id')
.skip(0)
.take(10)
.inlineCount();
manager.executeQuery(query.using(breeze.FetchStrategy.FromServer), data => {
return data;
});
Is this a bug or I'm doing something wrong?
jspm-install aurelia-breeze results in:
err Error: Repo npm:breeze@breeze-client not found!
I have tried jspm clean, jspm cc, and jspm install -f.
I get the same error with both skeleton-typescript and skeleton-es2016.
When I put the Required attribute on a Entity Model, an appropriate error message is displayed.
[Required]
LastName string {get;set;}
For example, on the LastName field, I get a LastName is required
message.
However, what would be even better is if the message was taken from the Required attribute:
[Required(ErrorMessage = "Last Name is required.")]
public string LastName { get; set; }
For a production app, these details are important such as a user friendly field name and a period at the end.
However, I don't think breeze actually supports this. The message does not get included in the metadata. I know there was discussion about this in the past on the breeze site, as I tried to resolve this once before and gave up on it and stopped using annotations and instead use Aurelia-validation so it is all configured on the client. But perhaps we could get Ward to consider adding this in to breeze.
But the point is, I can't use breeze-validation.bind without some way of being able to configure the error messages, either on the client or the server.
I would love to use just breeze-validation, and not use Aurelia-validation if that is possible.
I have tried everything I can think of and cannot get aurelia-breeze, breeze and typescript to play together. I've tried re-editing the type definitions and all sorts... I just always end up with errors along the lines of...
node_modules/breeze-client/breeze.debug".export=' has no exported member 'EntityManager'.
Also my understanding is that I need to still use the webpack alias fix, but... if I do that I have to reference the import as breeze (instead of the real breeze-client), and then typescript can no longer find it...
Pulling my hair out here... any help greatly appreciated!
When running in IE9, the results part of my query comes back as an empty array. This causes a promise rejection when the promise tries to resolve an undefined entity. The network response shows that all the results are there. The response is exactly the same as other browsers where the app works.
The same app works fine in IE11 and Chrome.
When testing in IE 11, Error Messages do not seem to display. Worked OK in Chrome and Firefox.
I am trying to implement the aurelia-breeze library into a new aurelia application, and when the first query is run, the library is trying to download the Metadata and it fails returning the error:
"Metadata query failed for: http://localhost:8080/api/CE/CE/Metadata; TypeError: Cannot read property 'isPrototypeOf' of undefined"
I have been successful in hitting that same service using basic Breeze, but I found this library and thought I would implement it to take advantage of some of the out of the box features. (e.g. removing Q from the list of libraries, etc)
I'm using the entity-manager-factory to encapsulate the entityManager as seen in the aureliea-breeze-northwind demo. It is used by a service object to extract the data from my web.api. Here is the service object:
import breeze from 'breeze';
import {createEntityManager} from '../entity-manager-factory';
export class PartyDataService {
/**********************************************
* loadExistingHospital
* id - integer Id of the hospital you are needing
**********************************************/
loadExistingHospital(id) {
var hospitalQuery = new breeze.EntityQuery().from('Hospitals')
.where('HospitalId', '==', id)
.select('Organization.Name', 'Organization.Url', 'Organization.IsActive', 'IsPending');
return createEntityManager()
.then(em => em.executeQuery(hospitalQuery))
.then(queryResult => {
return {
entity: queryResult.results[0],
entityManager: queryResult.entityManager
};
})
.catch(error => {
alert('error on Load by Id');
});
}
/**********************************************
* loadHospitalList - loads all hospitals from this list.
**********************************************/
loadHospitalList() {
var hospitalQuery = new breeze.EntityQuery().from('Hospitals');
//.select('Organization.Name', 'Organization.Url', 'Organization.IsActive', 'IsPending')
//.orderBy('Organization.Name');
return createEntityManager()
.then(em => em.executeQuery(hospitalQuery))
.then(queryResult => {
return {
entity: queryResult.results,
entityManager: queryResult.entityManager
};
})
.catch(error => {
alert('error on Load List');
});
}
}
I have the plugin being instantiated from my main.js file:
import {LogManager} from 'aurelia-framework';
import {ConsoleAppender} from 'aurelia-logging-console';
import {ConventionalViewStrategy} from 'aurelia-framework';
import {Configure} from 'aurelia-configuration';
LogManager.addAppender(new ConsoleAppender());
LogManager.setLevel(LogManager.logLevel.debug);
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-configuration',
config => {
config.setCascadeMode(false);
config.setEnvironments({
Code: ['localhost'],
Dev: ['zzzzzzzzzz'],
QA: ['yyyyyyyyy'],
Prod: ['xxxxxxxxxx']
});
config.setDirectory('CeApps/configs');
config.setConfig('config.json');
})
.plugin('aurelia-kendoui-bridge', (kendo) => kendo.pro())
.plugin('aurelia-breeze'); // install the aurelia-breeze integration plugin.
;
aurelia.start().then(a => a.setRoot('CeApp/app'));
}
I must be missing something - any thoughts??
Any chance for a simple typescript definition file for this?
I am trying to use it in a visual studio project that compiles with typescript. When I do this:
import {ErrorRenderer} from 'aurelia-breeze';
I get an error of:
Cannot find module 'aureila-breeze'
I can import other framework stuff:
import {Container, autoinject} from 'aurelia-framework'
This works fine (and seems like a similar example to 'aurelia-breeze'.
However if I exclude aurelia-framework.d.ts from my project then it gives a similar error:
Cannot find module 'aurelia-framework'
And when I put aurelia-framework.d.ts back in, that error goes away.
So this leads me to believe that I need an aurelia-breeze.d.ts.
I would make my own, but the setup with index.js as the core of the exports for the project is confusing me. (I am new to typescript and javascript so I may be in over my head a bit.)
I have an app built using the webpack skeleton. I tried:
npm install aurelia-breeze
Everything seems to have come down except that there is no module 'breeze' rather a module 'breeze-client'
When I try to bundle with webpack I get a bunch of errors of this type:
Module not found: Error: Cannot resolve module 'breeze'
from the aurelia-breeze source.
Would be very helpful to update the aurelia-breeze plugin to breezejs 2.0 and update readme to include installation with aurelia-cli
aurelia-binding has a new major version, so to avoid jspm forks should change dependency to
"aurelia-binding": "^1.0.0-rc.1.0.0 || ^2.0.0"
However jspm does allow semver ranges, so have to change dependency to
"aurelia-binding": "2.0.0"
Caveat: everything I say here is based on my reading the code, not executing it. I could easily have misread something and I invite you to correct me.
It appears that you are adding a new subscription to each entity’s entityAspect.propertyChanged
event for every data bound property. That works. But will it scale? If we bind to 1000 entities each with 10 bound properties, that’s 10,000 event handlers.
I think we can get it down to one handler per entity.
Imagine for each observed entity you have a BreezeObjectObserver
(AKA boo
) ... as you do now. The boo
has a dictionary (a hash) of {propertyName, callbacks} where the callbacks are the Aurelia listeners; let's call that thing watches
.
For efficiency we also keep a count of the number of subscriptions in the dictionary; call this subscriptionCount
.
When Aurelia asks for a property observer, we get or create the new BreezeObjectObserver
.
Then we add a property to boo.watches
(if necessary) and add to its array of callbacks (which will have one element the first time). we bump the boo.subscriptionCount
by 1. We return the unsubscribe function … as you do.
The first time we create a boo
, we register a SINGLE breeze propertyChanged
handler to entityAspect.propertyChanged
.
Because breeze raises the changed event by property name, it’s trivial for that handler to find the watch for that property and get its array of callbacks: boo.watches[propertyName]
. Pretty much the logic you have.
Unsubscribe is a matter of calling the unsubscribe function (as you do) and decrementing the boo.propertyCount
.
Or perhaps setting the slot in the array to
null
instead! Jay found that it is significantly faster to stick nulls in an array rather than keep expanding and shrinking it. That repeated restructuring can force a lot of garbage collection apparently. It’s a little more work to fill with nulls and watch for nulls but he found it worthwhile. See the implementation of the BreezeEntityGroup
. This is a refinement to be sure and not critical to the proposal.
When the boo.propertyCount
=== 0, we know we can unsubscribe from entityAspect.propertyChanged
.
I think I would also discard the
boo
(object.entityAspect.__breezeObserver__ = undefined
).We'd have to harden the unsubscribe function against the risk that it was associated with a
boo
that no longer exists ... or is no longer valid. I don't think that's hard.That is a refinement. You could always keep it and re-subscribe when the count === 1.
Currently you allow Aurelia to continue binding to detached entities.
Why? In theory, one shouldn't be displaying a detached entity and there is no good reason to care about subsequent changes to a detached entity. You might as well unsubscribe.
I realize that Breeze still raises propertyChanged
when you set the property of a detached entity. Do we care?
If we don't care, we should discard the BreezeObservableObject
(boo
) when the entity becomes detached.
An entity can become detached for a number of reasons.
rejectChanges
on an unsaved new entityEntityManager
Hmmm. If a view is bound to entity "x" . You detach it by one of the mechanisms just described. Then somehow you re-attached it behind the scenes without ever changing its view binding?
I'm not sure what happens. Aurelia probably thinks that the object's bound properties are observable and will be waiting for those callbacks to fire.
I personally don't think this scenario makes practical sense. You shouldn't put the user in this situation anyway. When an entity is detached it should be removed from view.
But I'm open to a counter argument.
Hi Jeremy
I looked again at the code. I think it is much better. Again, I'm only reading it but it seems much improved to me.
I had a lingering question about whether it is "better" to modify the subscription callback arrays during unsubscribe or to set the array slot with null and then fill the null holes with the next subscription.
I wrote a gist to test both ways of doing it, jacked up the iteration counts, and looked at it in the chrome profiler.
If I'm doing it right, the set-and-fill-with-nulls approach is faster. Not crazy faster but faster. For example, in one set of runs (alternating slice and null version trials three times), the slice approach took 6 seconds while the null approach took 5 seconds.
Each trial:
observer.subscription
).Is that significant? Probably not in the grand scheme of things. But Jay did it this way in breeze so maybe we should do it here too. Your call.
btw, I followed the profiling instructions here.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.