{"id":22489936,"url":"https://github.com/universal-model/universal-model","last_synced_at":"2025-08-02T22:31:49.213Z","repository":{"id":43881535,"uuid":"236800937","full_name":"universal-model/universal-model","owner":"universal-model","description":"Universal Model for Angular/React/Vue/Svelte","archived":false,"fork":false,"pushed_at":"2023-07-18T20:46:08.000Z","size":7057,"stargazers_count":7,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-08-24T08:14:58.814Z","etag":null,"topics":["angular","management","model","mvc","react","state","svelte","universal","universal-model","vue"],"latest_commit_sha":null,"homepage":"https://universal-model.github.io","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/universal-model.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null},"funding":{"patreon":"pksilen","custom":["paypal.me/pksilen"]}},"created_at":"2020-01-28T17:55:16.000Z","updated_at":"2023-09-23T16:16:08.325Z","dependencies_parsed_at":"2023-09-23T16:30:20.522Z","dependency_job_id":null,"html_url":"https://github.com/universal-model/universal-model","commit_stats":{"total_commits":48,"total_committers":2,"mean_commits":24.0,"dds":"0.27083333333333337","last_synced_commit":"e60d6146e7e9c46df8e8598c4566924c980b32a8"},"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/universal-model","download_url":"https://codeload.github.com/universal-model/universal-model/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228500570,"owners_count":17930090,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","management","model","mvc","react","state","svelte","universal","universal-model","vue"],"created_at":"2024-12-06T17:21:14.654Z","updated_at":"2024-12-06T17:23:32.950Z","avatar_url":"https://github.com/universal-model.png","language":"TypeScript","readme":"# Universal Model for Angular/React/Vue/Svelte\n\n[![version][version-badge]][package]\n[![Downloads][Downloads]][package]\n[![build][build]][circleci]\n[![MIT License][license-badge]][license]\n\nUniversal model is a model which can be used with any combination of following UI frameworks:\n* Angular 2+ \n* React 16.8+ \n* Svelte 3+\n* Vue.js 3+\n\n## Install\n\n    npm install --save universal-model-ng-react-svelte-vue\n     \n## Clean UI Architecture\n![alt text](https://github.com/universal-model/universal-model-vue/raw/master/images/mvc.png \"MVC\")\n* Model-View-Controller (https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n* User triggers actions by using view or controller\n* Actions are part of model and they manipulate state that is stored\n* Actions can use services to interact with external (backend) systems\n* State changes trigger view updates\n* Selectors select and calculate a transformed version of state that causes view updates\n* Views contain NO business logic\n* There can be multiple interchangeable views that use same part of model\n* A new view can be created to represent model differently without any changes to model\n* View technology can be changed without changes to the model\n    \n## Clean UI Code directory layout\nUI application is divided into UI components. Common UI components should be put into common directory. Each component\ncan consist of subcomponents. Each component has a view and optionally controller and model. Model consists of actions, state\nand selectors. In large scale apps, model can contain sub-store. Application has one store which is composed of each components'\nstate (or sub-stores)\n\n    - src\n      |\n      |- common\n      |  |- component1\n      |  |- component2\n      |  . |- component2_1\n      |  . \n      |  . \n      |  .\n      |- componentA\n      |- componentB\n      |  |- componentB_1\n      |  |- componentB_2\n      |- componentC\n      |  |- view\n      |  .\n      |  .\n      |- componentN\n      |  |- controller\n      |  |- model\n      |  |  |- actions\n      |  |  |- services\n      |  |  |- state\n      |  |- view\n      |- store\n      \n## API\n\n### Common \u0026 Vue\n    createSubState(subState);\n    const store = createStore(initialState, combineSelectors(selectors));\n    \n    const { componentAState } = store.getState();\n    const { selector1, selector2 } = store.getSelectors();\n    const [{ componentAState }, { selector1, selector2 }] = store.getStateAndSelectors();\n    \n[Detailed Common \u0026 Vue API documentation](https://github.com/universal-model/universal-model-vue/blob/master/docs/API.md)\n\n    \n### React\n    useStateReact([componentAState]);\n    useSelectorsReact([selector1, selector2]);\n    useStateAndSelectorsReact([componentAState], [selector1, selector2]);\n\n[Detailed React API documentation](https://github.com/universal-model/universal-model/blob/master/docs/REACT_API.md)\n\n     \n### Angular\n    useStateNg(this, { componentAState });\n    useSelectorsNg(this, { selector1, selector2 });\n    useStateAndSelectorsNg(this, { componentAState }, { selector1, selector2 });\n    \n[Detailed Angular API documentation](https://github.com/universal-model/universal-model/blob/master/docs/ANGULAR_API.md)\n\n    \n### Svelte\n    const [componentAState] = useStateSvelte(id, [state.componentAState]);\n    const [selector1, selector2] = useSelectorsSvelte(id, [selectors.selector1, selectors.selector2]);\n    \n[Detailed Svelte API documentation](https://github.com/universal-model/universal-model/blob/master/docs/SVELTE_API.md)\n\n\n## API Examples\n**Create initial states**\n\n    const initialComponentAState = {\n      prop1: 0,\n      prop2: 0\n    };\n    \n**Create selectors**\n\nWhen using foreign state inside selectors, prefer creating foreign state selectors and accessing foreign\nstate through them instead of directly accessing foreign state inside selector. This will ensure  better\nencapsulation of component state.\n\n    const createComponentASelectors = \u003cT extends State\u003e() =\u003e ({\n      selector1: (state: State) =\u003e state.componentAState.prop1  + state.componentAState.prop2\n      selector2: (state: State) =\u003e {\n        const { componentBSelector1, componentBSelector2 } = createComponentBSelectors\u003cState\u003e();\n        return state.componentAState.prop1 + componentBSelector1(state) + componentBSelector2(state);\n      }\n    });\n    \n**Create and export store in store.ts:**\n\ncombineSelectors() checks if there are duplicate keys in selectors and will throw an error telling which key was duplicated.\nBy using combineSelectors you can keep your selector names short and only namespace them if needed.\n    \n    const initialState = {\n      componentAState: createSubState(initialComponentAState),\n      componentBState: createSubState(initialComponentBState)\n    };\n    \n    export type State = typeof initialState;\n    \n    const componentAStateSelectors = createComponentAStateSelectors\u003cState\u003e();\n    const componentBStateSelectors = createComponentBStateSelectors\u003cState\u003e();\n    \n    const selectors = combineSelectors\u003cState, typeof componentAStateSelectors, typeof componentBStateSelectors\u003e(\n      componentAStateSelectors,\n      componentBStateSelectors\n    );\n    \n    export default createStore\u003cState, typeof selectors\u003e(initialState, selectors);\n    \nin large projects you should have sub-stores for components and these sub-store are combined \ntogether to a single store in store.js:\n\n**componentBSubStore.js**\n\n    export const initialComponentsBState = { \n      componentBState: createSubState(initialComponentBState),\n      componentB_1State: createSubState(initialComponentB_1State),\n      componentB_2State: createSubState(initialComponentB_2State),\n    };\n    \n    const componentBStateSelectors = createComponentBStateSelectors\u003cState\u003e();\n    const componentB_1StateSelectors = createComponentB_1StateSelectors\u003cState\u003e();\n    const componentB_2StateSelectors = createComponentB_2Selectors\u003cState\u003e('componentB');\n    \n    const componentsBStateSelectors = combineSelectors\u003cState, typeof componentBStateSelectors, typeof componentB_1StateSelectors, typeof componentB_2StateSelectors\u003e(\n      componentBStateSelectors,\n      componentB_1StateSelectors,\n      componentB_2StateSelectors\n    );\n    \n**store.js**\n\n    const initialState = {\n      ...initialComponentsAState,\n      ...initialComponentsBState,\n      .\n      ...initialComponentsNState\n    };\n          \n    export type State = typeof initialState;\n        \n    const selectors = combineSelectors\u003cState, typeof componentsAStateSelectors, typeof componentsBStateSelectors, ... typeof componentsNStateSelectors\u003e(\n      componentsAStateSelectors,\n      componentsBStateSelectors,\n      .\n      componentsNStateSelectors\n    );\n        \n    export default createStore\u003cState, typeof selectors\u003e(initialState, selectors);\n    \n**Access store in Actions**\n\nDon't modify other component's state directly inside action, but instead \ncall other component's action. This will ensure encapsulation of component's own state.\n\n    export default function changeComponentAAndBState(newAValue, newBValue) {\n      const { componentAState } = store.getState();\n      componentAState.prop1 = newAValue;\n      \n      // BAD\n      const { componentBState } = store.getState();\n      componentBState.prop1 = newBValue;\n      \n      // GOOD\n      changeComponentBState(newBValue);\n    }\n    \n**Vue views**\n\nComponents should use only their own state and access other components' states using selectors\nprovided by those components. This will ensure encapsulation of each component's state.\n\n    export default {\n      setup(): object {\n        const [ { componentAState }, { selector1, selector2 }] = store.getStateAndSelectors();\n      \n      return {\n        componentAState,\n        selector1,\n        selector2,\n        // Action\n        changeComponentAState\n      };\n    }\n    \n**React views**\n\nComponents should use only their own state and access other components' states using selectors\nprovided by those components. This will ensure encapsulation of each component's state.\n    \n    const View = () =\u003e {\n      const [{ componentAState, { selector1, selector2 }] = store.getStateAndSelectors();\n      useStateAndSelectorsReact([componentAState], [selector1, selector2]);\n      \n      // NOTE! Get the value of a selector using it's 'value' property!\n      console.log(selector1.value);\n    }\n    \n**Angular views**\n\nComponents should use only their own state and access other components' states using selectors\nprovided by those components. This will ensure encapsulation of each component's state.\n    \n    export default class AComponent {\n      state: typeof initialComponentAState;\n      selector1: string,\n      selector2: number\n      // Action\n      changeComponentAState = changeComponentAState\n      \n      constructor() {\n        const [{ componentAState, { selector1, selector2 }] = store.getStateAndSelectors();\n        useStateAndSelectors(this, { componentAState: state }, { selector1, selector2 });\n      }\n    }\n\n**Svelte views**\n\nComponents should use only their own state and access other components' states using selectors\nprovided by those components. This will ensure encapsulation of each component's state.\n    \n    \u003cscript\u003e  \n      const [componentAState] = useState('componentA', [store.getState().componentAState]);\n      const selectors = store.getSelectors();\n      const [selector1, selector2] = useSelectors('componentA', [selectors.selector1, selectors.selector2]);    \n    \u003c/script\u003e\n    \n    \u003cdiv\u003e\n      {$componentAState.prop1}\n      {$selector1} ...\n    \u003cdiv\u003e\n\n### Dependency injection\nIf you would like to use dependency injection (noicejs) in your app, check out this [example],\nwhere DI is used to create services.\n\n### Donate/Sponsor\nIf you would like to help me to develop more great stuff, you can [donate](https://paypal.me/pksilen) or [sponsor](https://patreon.com/pksilen). Thank you!\n\n### License\nMIT License\n\n[license-badge]: https://img.shields.io/badge/license-MIT-green\n[license]: https://github.com/universal-model/universal-model/blob/master/LICENSE\n[version-badge]: https://img.shields.io/npm/v/universal-model-ng-react-svelte-vue.svg?style=flat-square\n[package]: https://www.npmjs.com/package/universal-model-ng-react-svelte-vue\n[build]: https://img.shields.io/circleci/project/github/universal-model/universal-model/master.svg?style=flat-square\n[circleci]: https://circleci.com/gh/universal-model/universal-model/tree/master\n[example]: https://github.com/universal-model/react-todo-app-with-dependency-injection\n[Downloads]: https://img.shields.io/npm/dm/universal-model-ng-react-svelte-vue\n","funding_links":["https://patreon.com/pksilen","paypal.me/pksilen","https://paypal.me/pksilen"],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funiversal-model%2Funiversal-model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funiversal-model%2Funiversal-model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funiversal-model%2Funiversal-model/lists"}