{"id":13808778,"url":"https://github.com/universal-model/universal-model-angular","last_synced_at":"2025-05-14T03:31:26.366Z","repository":{"id":43879609,"uuid":"234738893","full_name":"universal-model/universal-model-angular","owner":"universal-model","description":"Universal Model for Angular","archived":false,"fork":false,"pushed_at":"2023-10-18T20:13:34.000Z","size":1493,"stargazers_count":8,"open_issues_count":3,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-08T19:18:45.014Z","etag":null,"topics":["angular","model","mvc","universal","universal-model"],"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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"patreon":"pksilen","custom":["paypal.me/pksilen"]}},"created_at":"2020-01-18T13:30:01.000Z","updated_at":"2023-03-22T19:20:37.000Z","dependencies_parsed_at":"2024-08-04T01:09:47.315Z","dependency_job_id":"4c323a7f-2f1e-4ed6-9401-ca6c5bca2f5a","html_url":"https://github.com/universal-model/universal-model-angular","commit_stats":{"total_commits":129,"total_committers":3,"mean_commits":43.0,"dds":"0.15503875968992253","last_synced_commit":"c0ff441f6f45ff8bf01e67696047df778b1cead0"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model-angular","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model-angular/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model-angular/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/universal-model%2Funiversal-model-angular/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/universal-model","download_url":"https://codeload.github.com/universal-model/universal-model-angular/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225273273,"owners_count":17448076,"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","model","mvc","universal","universal-model"],"created_at":"2024-08-04T01:01:51.677Z","updated_at":"2024-11-19T00:31:04.995Z","avatar_url":"https://github.com/universal-model.png","language":"TypeScript","readme":"# Universal Model for Angular\n\n[![version][version-badge]][package]\n[![Downloads][Downloads]][package]\n[![build][build]][circleci]\n[![coverage][coverage]][codecov]\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=universal-model_universal-model-angular\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=universal-model_universal-model-angular)\n[![MIT License][license-badge]][license]\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Funiversal-model%2Funiversal-model-angular.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Funiversal-model%2Funiversal-model-angular?ref=badge_shield)\n\nUniversal model is a model which can be used with any of following UI frameworks:\n\n- Angular 2+ \n- React 16.8+ [universal-model-react]\n- Svelte 3+ [universal-model-svelte]\n- Vue.js 3+ [universal-model-vue]\n\nIf you want to use multiple UI frameworks at the same time, you can use single model\nwith [universal-model] library\n\n## Install\n\n    npm install --save universal-model-angular\n\n## Prerequisites for universal-model-react\n\n     Angular \u003e= 2\n\n## Clean UI Architecture\n\n![alt text](https://github.com/universal-model/universal-model-vue/raw/master/images/mvc.png 'MVC')\n\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      |- 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\n## API\n\n### Common API (Angular/React/Svelte/Vue)\n\n```ts\ncreateSubState(subState);\nconst store = createStore(initialState, combineSelectors(selectors))\n\nconst { componentAState, componentBState } = store.getState();\nconst { selector1, selector2 } = store.getSelectors();\nconst [{ componentAState }, { selector1, selector2 }] = store.getStateAndSelectors();\n```\n\n### Angular specific API\n\n```ts\nuseState(this, { componentAState, componentBStateProp1: () =\u003e componentBState.prop1 });\nuseSelectors(this, { selector1, selector2 });\nuseStateAndSelectors(this, { componentAState }, { selector1, selector2 });\n```\n\n[Detailed API documentation](https://github.com/universal-model/universal-model-angular/blob/master/docs/API.md)\n\n## API Examples\n**Create initial states**\n\n```ts\nconst initialComponentAState = {\n  prop1: 0,\n  prop2: 0\n};\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```ts\nconst 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\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```ts    \nconst initialState = {\n  componentAState: createSubState(initialComponentAState),\n  componentBState: createSubState(initialComponentBState)\n};\n\nexport type State = typeof initialState;\n\nconst componentAStateSelectors = createComponentAStateSelectors\u003cState\u003e();\nconst componentBStateSelectors = createComponentBStateSelectors\u003cState\u003e();\n\nconst selectors = combineSelectors\u003cState, typeof componentAStateSelectors, typeof componentBStateSelectors\u003e(\n  componentAStateSelectors,\n  componentBStateSelectors\n);\n\nexport default createStore\u003cState, typeof selectors\u003e(initialState, selectors);\n```\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```ts\nexport const initialComponentsBState = { \n  componentBState: createSubState(initialComponentBState),\n  componentB_1State: createSubState(initialComponentB_1State),\n  componentB_2State: createSubState(initialComponentB_2State),\n};\n\nconst componentBStateSelectors = createComponentBStateSelectors\u003cState\u003e();\nconst componentB_1StateSelectors = createComponentB_1StateSelectors\u003cState\u003e();\nconst componentB_2StateSelectors = createComponentB_2Selectors\u003cState\u003e('componentB');\n\nconst componentsBStateSelectors = combineSelectors\u003cState, typeof componentBStateSelectors, typeof componentB_1StateSelectors, typeof componentB_2StateSelectors\u003e(\n  componentBStateSelectors,\n  componentB_1StateSelectors,\n  componentB_2StateSelectors\n);\n```\n    \n**store.js**\n\n```ts\nconst initialState = {\n  ...initialComponentsAState,\n  ...initialComponentsBState,\n  .\n  ...initialComponentsNState\n};\n      \nexport type State = typeof initialState;\n    \nconst selectors = combineSelectors\u003cState, typeof componentsAStateSelectors, typeof componentsBStateSelectors, ... typeof componentsNStateSelectors\u003e(\n  componentsAStateSelectors,\n  componentsBStateSelectors,\n  .\n  componentsNStateSelectors\n);\n    \nexport default createStore\u003cState, typeof selectors\u003e(initialState, selectors);\n```\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```ts\nexport 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\n**Use actions, state and selectors in Views (Angular Components)**\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. You can also\naccess foreign state with a state getter.\n\n```ts    \nexport default class AComponent {\n  state: typeof initialComponentAState;\n  foreignStateProp1: number,\n  selector1: string,\n  selector2: number\n  // Action\n  changeComponentAState = changeComponentAState\n  \n  constructor() {\n    const [{ componentAState, componentBState }, { selector1, selector2 }] = store.getStateAndSelectors();\n    useStateAndSelectors(this, { state: componentAState, foreignStateProp1: () =\u003e componentBState.prop1  }, { selector1, selector2 });\n  }\n}\n```\n\n# Example\n\n## View\napp.component.ts\n\n```ts\nimport { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  template: `\n    \u003capp-header-view\u003e\u003c/app-header-view\u003e\n    \u003capp-todo-list-view\u003e\u003c/app-todo-list-view\u003e\n  `,\n  styleUrls: []\n})\nexport class AppComponent {}\n```\n\nheader.component.ts\n\n```ts\nimport { Component } from '@angular/core';\nimport initialHeaderState from '@/header/model/state/initialHeaderState';\nimport changeUserName from '@/header/model/actions/changeUserName';\nimport store from '@/store/store';\n\n@Component({\n  selector: 'app-header-view',\n  template: `\n    \u003cdiv\u003e\n      \u003ch1\u003e{{ headerText }}\u003c/h1\u003e\n      \u003clabel for=\"userName\"\u003eUser name:\u003c/label\u003e\n      \u003cinput #userNameInput id=\"userName\" (change)=\"changeUserName(userNameInput.value)\" /\u003e\n    \u003c/div\u003e\n  `,\n  styleUrls: []\n})\nexport class HeaderComponent {\n  headerText: string;\n  changeUserName = changeUserName;\n\n  constructor() {\n    const { headerText } = store.getSelectors();\n    store.useSelectors(this, { headerText });\n  }\n}\n```\n\ntodolist.component.ts\n\n```ts\nimport { Component, OnDestroy, OnInit } from '@angular/core';\nimport initialTodosState, { Todo } from '@/todolist/model/state/initialTodosState';\nimport toggleShouldShowOnlyDoneTodos from '@/todolist/model/actions/toggleShouldShowOnlyUnDoneTodos';\nimport toggleIsDoneTodo from '@/todolist/model/actions/toggleIsDoneTodo';\nimport removeTodo from '@/todolist/model/actions/removeTodo';\nimport store from '@/store/store';\nimport fetchTodos from '@/todolist/model/actions/fetchTodos';\nimport todoListController from '@/todolist/controller/todoListController';\n\n@Component({\n  selector: 'app-todo-list-view',\n  template: `\n    \u003cdiv\u003e\n      \u003cinput\n        id=\"shouldShowOnlyUnDoneTodos\"\n        type=\"checkbox\"\n        [checked]=\"todosState.shouldShowOnlyUnDoneTodos\"\n        (click)=\"toggleShouldShowOnlyUnDoneTodos()\"\n      /\u003e\n      \u003clabel for=\"shouldShowOnlyUnDoneTodos\"\u003eShow only undone todos\u003c/label\u003e\n      \u003cdiv *ngIf=\"todosState.isFetchingTodos\"\u003eFetching todos...\u003c/div\u003e\n      \u003cdiv *ngIf=\"todosState.hasTodosFetchFailure; else todoList\"\u003eFailed to fetch todos\u003c/div\u003e\n      \u003cng-template #todoList\u003e\n        \u003cul\u003e\n          \u003cli *ngFor=\"let todo of shownTodos\"\u003e\n            \u003cinput id=\"todo.name\" type=\"checkbox\" [checked]=\"todo.isDone\" (click)=\"toggleIsDoneTodo(todo)\" /\u003e\n            \u003clabel for=\"todo.name\"\u003e{{ userName }}: {{ todo.name }}\u003c/label\u003e\n            \u003cbutton (click)=\"removeTodo(todo)\"\u003eRemove\u003c/button\u003e\n          \u003c/li\u003e\n        \u003c/ul\u003e\n      \u003c/ng-template\u003e\n    \u003c/div\u003e\n  `,\n  styleUrls: []\n})\nexport class TodoListComponent implements OnInit, OnDestroy {\n  todosState: typeof initialTodosState;\n  shownTodos: Todo[];\n  userName: string;\n  toggleShouldShowOnlyUnDoneTodos = toggleShouldShowOnlyDoneTodos;\n  toggleIsDoneTodo = toggleIsDoneTodo;\n  removeTodo = removeTodo;\n\n  constructor() {\n    const [{ todosState }, { shownTodos, userName }] = store.getStateAndSelectors();\n    store.useStateAndSelectors(this, { todosState }, { shownTodos, userName });\n  }\n\n  ngOnInit(): void {\n    // noinspection JSIgnoredPromiseFromCall\n    fetchTodos();\n    document.addEventListener('keydown', todoListController.handleKeyDown);\n  }\n\n  ngOnDestroy(): void {\n    document.removeEventListener('keydown', todoListController.handleKeyDown);\n  }\n}\n```\n\n## Controller\n\ntodoListController.ts\n\n```ts\nimport addTodo from \"@/todolist/model/actions/addTodo\";\nimport removeAllTodos from \"@/todolist/model/actions/removeAllTodos\";\n\nexport default {\n  handleKeyDown(keyboardEvent: KeyboardEvent): void {\n    if (keyboardEvent.code === 'KeyA' \u0026\u0026 keyboardEvent.ctrlKey) {\n      keyboardEvent.stopPropagation();\n      keyboardEvent.preventDefault();\n      addTodo();\n    } else if (keyboardEvent.code === 'KeyR' \u0026\u0026 keyboardEvent.ctrlKey) {\n       keyboardEvent.stopPropagation();\n       keyboardEvent.preventDefault();\n      removeAllTodos();\n    }\n  }\n};\n```\n    \n## Model\n\n### Store\n\nstore.ts\n\n```ts\nimport { combineSelectors, createStore, createSubState } from 'universal-model-angular';\nimport initialHeaderState from '@/header/model/state/initialHeaderState';\nimport initialTodoListState from '@/todolist/model/state/initialTodosState';\nimport createTodoListStateSelectors from '@/todolist/model/state/createTodoListStateSelectors';\nimport createHeaderStateSelectors from '@/header/model/state/createHeaderStateSelectors';\n\nconst initialState = {\nheaderState: createSubState(initialHeaderState),\ntodosState: createSubState(initialTodoListState)\n};\n\nexport type State = typeof initialState;\n\nconst headerStateSelectors =  createHeaderStateSelectors\u003cState\u003e();\nconst todoListStateSelectors = createTodoListStateSelectors\u003cState\u003e();\n\nconst selectors = combineSelectors\u003cState, typeof headerStateSelectors, typeof todoListStateSelectors\u003e(\nheaderStateSelectors,\ntodoListStateSelectors \n);\n\nexport default createStore\u003cState, typeof selectors\u003e(initialState, selectors);\n```\n\n### State\n\n#### Initial state\n\ninitialHeaderState.ts\n\n```ts\nexport default {\n  userName: 'John'\n};\n```\n\ninitialTodoListState.ts\n\n```ts\nexport interface Todo {\n  id: number,\n  name: string;\n  isDone: boolean;\n}\n\nexport default {\n  todos: [] as Todo[],\n  shouldShowOnlyUnDoneTodos: false,\n  isFetchingTodos: false,\n  hasTodosFetchFailure: false\n};\n```\n\n#### State selectors\n\ncreateHeaderStateSelectors.ts\n\n```ts\nimport { State } from '@/store/store';\n\nconst createHeaderStateSelectors = \u003cT extends State\u003e() =\u003e ({\n  userName: (state: T) =\u003e state.headerState.userName,\n  headerText: (state: T) =\u003e {\n    const {\n      todoCount: selectTodoCount,\n      unDoneTodoCount: selectUnDoneTodoCount\n    } = createTodoListStateSelectors\u003cT\u003e();\n  \n    return `${state.headerState.userName} (${selectUnDoneTodoCount(state)}/${selectTodoCount(state)})`;\n  }\n});\n\nexport default createHeaderStateSelectors;\n```\n\ncreateTodoListStateSelectors.ts\n\n```ts\nimport { State } from '@/store/store';\nimport { Todo } from '@/todolist/model/state/initialTodoListState';\n\nconst createTodoListStateSelectors = \u003cT extends State\u003e() =\u003e ({\n  shownTodos: (state: T) =\u003e\n    state.todosState.todos.filter(\n      (todo: Todo) =\u003e\n        (state.todosState.shouldShowOnlyUnDoneTodos \u0026\u0026 !todo.isDone) ||\n        !state.todosState.shouldShowOnlyUnDoneTodos\n    ),\n    todoCount: (state: T) =\u003e state.todosState.todos.length,\n    unDoneTodoCount: (state: T) =\u003e state.todosState.todos.filter((todo: Todo) =\u003e !todo.isDone).length\n});\n\nexport default createTodoListStateSelectors;\n```\n\n### Service\n\nITodoService.ts\n\n```ts\nimport { Todo } from '@/todolist/model/state/initialTodoListState';\n\nexport interface ITodoService {\n  tryFetchTodos(): Promise\u003cTodo[]\u003e;\n}\n```\n\nFakeTodoService.ts\n\n```ts\nimport { ITodoService } from '@/todolist/model/services/ITodoService';\nimport { Todo } from '@/todolist/model/state/initialTodoListState';\nimport Constants from '@/Constants';\n\nexport default class FakeTodoService implements ITodoService {\n  tryFetchTodos(): Promise\u003cTodo[]\u003e {\n    return new Promise\u003cTodo[]\u003e((resolve: (todo: Todo[]) =\u003e void, reject: () =\u003e void) =\u003e {\n      setTimeout(() =\u003e {\n        if (Math.random() \u003c 0.95) {\n          resolve([\n            { id: 1, name: 'first todo', isDone: true },\n            { id: 2, name: 'second todo', isDone: false }\n          ]);\n        } else {\n          reject();\n        }\n      }, Constants.FAKE_SERVICE_LATENCY_IN_MILLIS);\n    });\n  }\n}\n```\n\ntodoService.ts\n\n```ts\nimport FakeTodoService from \"@/todolist/model/services/FakeTodoService\";\n\nexport default new FakeTodoService();\n```\n\n### Actions\n\nchangeUserName.ts\n\n```ts\nimport store from '@/store/store';\n\nexport default function changeUserName(newUserName: string): void {\n  const { headerState } = store.getState();\n  headerState.userName = newUserName;\n}\n```\n\naddTodo.ts\n\n```ts\nimport store from '@/store/store';\n\nlet id = 3;\n\nexport default function addTodo(): void {\n  const { todosState } = store.getState();\n  todosState.todos.push({ id, name: 'new todo', isDone: false });\n  id++;\n}\n```\n\nremoveTodo.ts\n\n```\nimport store from '@/store/store';\nimport { Todo } from '@/todolist/model/state/initialTodoListState';\n\nexport default function removeTodo(todoToRemove: Todo): void {\n  const { todosState } = store.getState();\n  todosState.todos = todosState.todos.filter((todo: Todo) =\u003e todo !== todoToRemove);\n}\n```\n\nremoveAllTodos.ts\n\n```ts\nimport store from '@/store/store';\n\nexport default function removeAllTodos(): void {\n  const { todosState } = store.getState();\n  todosState.todos = [];\n}\n```\n\ntoggleIsDoneTodo.ts\n\n```ts\nimport { Todo } from '@/todolist/model/state/initialTodoListState';\n\nexport default function toggleIsDoneTodo(todo: Todo): void {\n  todo.isDone = !todo.isDone;\n}\n```\n\ntoggleShouldShowOnlyUnDoneTodos.ts\n\n```ts\nimport store from '@/store/store';\n\nexport default function toggleShouldShowOnlyUnDoneTodos(): void {\n  const { todosState } = store.getState();\n  todosState.shouldShowOnlyUnDoneTodos = !todosState.shouldShowOnlyUnDoneTodos;\n}\n```\n\nfetchTodos.ts\n\n```ts\nimport store from '@/store/store';\nimport todoService from '@/todolist/model/services/todoService';\n\nexport default async function fetchTodos(): Promise\u003cvoid\u003e {\n  const { todosState } = store.getState();\n\n  todosState.isFetchingTodos = true;\n  todosState.hasTodosFetchFailure = false;\n\n  try {\n    todosState.todos = await todoService.tryFetchTodos();\n  } catch (error) {\n    todosState.hasTodosFetchFailure = true;\n  }\n\n  todosState.isFetchingTodos = false;\n}\n```\n\n### Full Examples\n\nhttps://github.com/universal-model/universal-model-angular-todo-app\n\nhttps://github.com/universal-model/universal-model-react-todos-and-notes-app\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\n\nMIT License\n\n[license-badge]: https://img.shields.io/badge/license-MIT-green\n[license]: https://github.com/universal-model/universal-model-angular/blob/master/LICENSE\n[version-badge]: https://img.shields.io/npm/v/universal-model-angular.svg?style=flat-square\n[package]: https://www.npmjs.com/package/universal-model-angular\n[build]: https://img.shields.io/circleci/project/github/universal-model/universal-model-angular/master.svg?style=flat-square\n[circleci]: https://circleci.com/gh/universal-model/universal-model-angular/tree/master\n[example]: https://github.com/universal-model/react-todo-app-with-dependency-injection\n[universal-model-react]: https://github.com/universal-model/universal-model-react\n[universal-model-svelte]: https://github.com/universal-model/universal-model-svelte\n[universal-model-vue]: https://github.com/universal-model/universal-model-vue\n[Downloads]: https://img.shields.io/npm/dm/universal-model-angular\n[coverage]: https://img.shields.io/codecov/c/github/universal-model/universal-model-angular/master.svg?style=flat-square\n[codecov]: https://codecov.io/gh/universal-model/universal-model-angular\n\n\n","funding_links":["https://patreon.com/pksilen","paypal.me/pksilen","https://paypal.me/pksilen"],"categories":["Table of contents"],"sub_categories":["Third Party Components"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funiversal-model%2Funiversal-model-angular","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funiversal-model%2Funiversal-model-angular","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funiversal-model%2Funiversal-model-angular/lists"}